Skip to content

feat: add feature adoption commands and enhancements - #13

Merged
sthadka merged 18 commits into
mainfrom
feat/feature-adoption
Jul 23, 2026
Merged

feat: add feature adoption commands and enhancements#13
sthadka merged 18 commits into
mainfrom
feat/feature-adoption

Conversation

@sthadka

@sthadka sthadka commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • New commands: open, clone, watch/unwatch, completion (shell completions for bash/zsh/fish/powershell)
  • Enhanced commands: link now supports remote URL links, create gains template variable support
  • SQL snippets: user-defined SQL snippets in config with recursive expansion, shown via jai schema --snippets
  • Template variables: built-in ($me, $project, $today, etc.) and parameterized ($recent(7d)) variables for queries
  • Documentation: Jira CLI feature assessment, feature adoption recommendations, updated README and user guide

Changes

Area What
cli/open.go Open issues in browser or print URL
cli/clone.go Clone issues with field overrides
cli/watch.go Watch/unwatch Jira issues
cli/completion.go Shell completion generation
cli/link.go Extended with remote URL link support
query/engine.go Built-in + parameterized template variables, SQL snippet expansion
config/config.go Snippets field in config
cli/schema.go --snippets flag to list available snippets
jira/write.go Watch/unwatch + remote link API methods
docs/ Jira CLI assessment, feature adoption plan, user guide updates

Test plan

  • Unit tests for all new commands (clone, open, watch, completion)
  • Unit tests for template variable resolution and snippet expansion
  • Unit tests for extended link command
  • Lint warnings resolved (staticcheck SA5011)
  • Manual: jai open PROJ-123 opens browser
  • Manual: jai clone PROJ-123 --summary "New title" creates clone
  • Manual: jai completion zsh outputs valid completions

sthadka added 18 commits July 22, 2026 21:56
Extend resolveTemplates() with new built-in time variables
(yesterday, month_ago, quarter_ago, this_week, this_month,
this_quarter), parameterized time variables (days_ago:N,
weeks_ago:N, months_ago:N), and a projects variable that
expands to comma-separated quoted project keys from config.
Add jai watch and jai unwatch commands for managing issue watchers
directly via the Jira API. Also add AddWatcher, RemoveWatcher,
CreateRemoteLink methods and a delete HTTP helper to the jira client.
Detect whether the second argument to jai link is a URL or an issue
key. URLs create remote links via the Jira remotelink API with an
optional title argument. Existing issue-to-issue link behavior is
preserved unchanged.
Add clone command that reads an issue from the local DB, extracts
cloneable fields from raw_json (project, summary, type, priority,
labels, components, assignee, parent, fix versions, description,
and all custom fields), and creates a new issue via the Jira API.

Supports --summary override, --set field=value for arbitrary field
overrides (reusing field resolution from create), and --replace
find:replace for text substitution in summary and ADF description.
Add support for reusable SQL fragments defined under a `snippets` key
in config YAML, referenced as {{snippet_name}} in queries. Snippets
can reference other snippets and built-in variables with recursive
expansion (max depth 10). Circular references produce a clear error.

- Add Snippets map[string]string to config.Config struct
- Implement resolveSnippets() with cycle detection via seen-set
- Add jai schema snippets subcommand for agent discoverability
- Export ExpandSnippet() for showing expanded values in schema output
- Change resolveTemplatesAt signature to (string, error) for cycle errors
Add documentation for: shell completions, open command, clone command,
watch/unwatch, remote links, template variables, SQL snippets, and
issue templates.
extractCloneFields copied every customfield_* value verbatim from the
source issue's raw_json, including Rank (a lexoRank string on read).
Jira's create-issue API expects an object shape for Rank, so sending
the string back caused a 400 (rankBeforeIssue/rankAfterIssue: expected
Object). Skip known non-clonable, Jira-managed fields via the field map.
jai watch/unwatch passed the configured "me" email (or a user-supplied
email) straight through as the Jira accountId. Jira Cloud's watchers
endpoint requires an actual account ID and rejects emails, causing a
404 on POST /rest/api/3/issue/{key}/watchers. Add ResolveAccountID,
which looks up the account ID via the user search API when given an
identifier that looks like an email, and use it in watch/unwatch.
Same root cause as the watch/unwatch fix (b8db35d): jai create --assignee
and jai clone --set assignee=... both document accepting an email but
passed it straight through as accountId, which Jira Cloud rejects.
create.go now resolves via jira.ResolveAccountID before building the
payload. clone.go's applyFieldOverride takes an optional resolver
callback (nil in tests) so it stays decoupled from the Jira client.

Verified against staging: jai create --assignee <email> and
jai clone --set assignee=<email> both correctly set the assignee.
jai set <key> priority/assignee/reporter/components/fixVersions always
sent bare strings (or arrays of bare strings) to Jira's write API, which
rejects them for these reference fields (e.g. 400: "Specify the Priority
(id or name) in the string format"). Add wrapScalarFieldValue and
wrapArrayItemValue to shape values correctly (name/accountId/array-of-
objects) based on the Jira field ID, with assignee/reporter resolved
through jira.ResolveAccountID. UpdateFieldOp's value param is now
interface{} so --add/--remove ops can also carry object values.

Also fix setBulk (comma-separated keys and --query) to run the same
optimistic local DB update that the single-key path already did —
bulk changes previously queued correctly but left the local cache
stale until the next sync.

Verified against staging: set priority/assignee/components on real
issues now pushes successfully and reflects live on Jira; bulk set
updates the local DB immediately.
processTransition pushed the transition to Jira and marked the pending
change synced, but never updated the issues table — status (and any
workflow side effects like resolution) stayed stale until the next
sync, breaking the DB-first invariant that local state reflects the
latest write immediately.

Re-fetch and re-denormalize the issue after a successful transition,
mirroring the same pattern jai clone already uses after create.

Verified against staging: after transitioning an issue, a local query
immediately shows the new status without a manual sync.
projectKeys() only read cfg.SyncSources[].Projects (the explicit
'projects:' YAML list), so {{projects}} silently expanded to an empty
string for sources defined via 'jql:' — a documented, valid config
style this project's own staging.yaml uses ('jql: PROJECT = ROX').

Add a best-effort projectKeysFromJQL fallback that extracts
'project = KEY' / 'project in (KEY1, KEY2)' clauses when no explicit
projects list is set. Arbitrary JQL isn't fully parsed, but this
covers the common single/multi-project sync source pattern.

Verified against staging: {{projects}} now expands to 'ROX' using the
real staging.yaml config.
@sthadka
sthadka merged commit 31ba89d into main Jul 23, 2026
2 checks passed
@sthadka
sthadka deleted the feat/feature-adoption branch July 23, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant