feat: add feature adoption commands and enhancements - #13
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
open,clone,watch/unwatch,completion(shell completions for bash/zsh/fish/powershell)linknow supports remote URL links,creategains template variable supportjai schema --snippets$me,$project,$today, etc.) and parameterized ($recent(7d)) variables for queriesChanges
cli/open.gocli/clone.gocli/watch.gocli/completion.gocli/link.goquery/engine.goconfig/config.gocli/schema.go--snippetsflag to list available snippetsjira/write.godocs/Test plan
clone,open,watch,completion)jai open PROJ-123opens browserjai clone PROJ-123 --summary "New title"creates clonejai completion zshoutputs valid completions