diff --git a/skills/reference/rhdh-jira-api/SKILL.md b/skills/reference/rhdh-jira-api/SKILL.md index 24283a63..043fba98 100644 --- a/skills/reference/rhdh-jira-api/SKILL.md +++ b/skills/reference/rhdh-jira-api/SKILL.md @@ -10,7 +10,7 @@ description: >- does Release Pending require". Reading and query mechanics only — deciding what to file, judging readiness, or changing an issue belongs to the Jira skill that owns that verb. -compatibility: "acli on PATH with a Jira session; Python 3.9+ and uv for the bundled scripts; an authenticated host Atlassian adapter for GraphQL and REST fallbacks. Windows, macOS, Linux." +compatibility: "acli on PATH with a Jira session; Python 3.9+ and uv for the bundled scripts; an authenticated host Atlassian adapter for GraphQL, REST fallbacks, and MCP tool calls. Windows, macOS, Linux." --- # RHDH Jira API @@ -27,7 +27,7 @@ here. | Question | Load | |---|---| -| Is Jira reachable? Is `acli` authenticated? | [references/auth.md](references/auth.md), then `uv run scripts/setup.py --json` | +| Which adapter for this operation? | [references/auth.md](references/auth.md) | | Which `acli` flag does this, and what breaks? | [references/acli-commands.md](references/acli-commands.md) | | What JQL answers this? Which board or sprint? | [references/jql-patterns.md](references/jql-patterns.md) | | Which custom field, label, link type, component, or priority? | [references/fields.md](references/fields.md) | @@ -39,20 +39,60 @@ here. Load the one branch the question needs. -## The two traps that produce wrong answers +## Adapter choice -Both fail silently, and both have burned this pack before. +Two adapters cover the full Jira surface. Use the one that fits the operation; +do not default to `acli` for every call. + +**Prefer the authenticated host Atlassian adapter (MCP tools) for:** +- All single-issue reads — no silent empty-field trap, and custom fields come back when asked for. +- All custom-field writes — Story Points, Size, Team, Release Note Type — native in the `fields` dict, no REST fallback step. +- Issue create — priority, components, and parent link in one call; `acli create` rejects those flags. +- Remote links (PR/web-link attachment) — `acli` has no equivalent. +- Comment visibility restrictions (role/group-scoped comments via `commentVisibility`). +- Worklog authoring. +- Confluence operations. +- Cross-product Teamwork Graph relationships (Atlas projects, goals). + +**Prefer `acli` for:** +- Batch transitions by JQL (`--jql "..." --yes`). +- Batch edits by JQL. +- Board and sprint reads — the host adapter has them, but only behind `discover` plus `executeRead`. +- All-pages auto-walk (`--paginate`), where explicit `nextPageToken` looping is inconvenient. +- Saved filter lookup and attachment list/delete — same shape: host adapter `discover` first, `acli` in one call. + +**Compass has no host-adapter tool.** MCP v2 dropped the Compass tools that MCP v1 exposed, and +`acli` has no equivalent, so Compass work goes through +[references/rest-api-fallback.md](references/rest-api-fallback.md). + +**Only some host-adapter operations are direct calls.** The primary tools — `getJiraIssue`, +`searchJiraIssuesUsingJql`, `createJiraIssue`, `editJiraIssue`, `transitionJiraIssue`, +`addOrEditJiraIssueComment`, the Confluence content tools, `search`, and the graph tools — are in the +agent's tool list. Everything else, including remote links, worklogs, attachment transfers, board +sprints and saved filters, needs `discover` to resolve the operation name, then `executeRead` / +`executeWrite` / `executeDestructive` with `cloudId` as a top-level argument. Run `discover` before +concluding the host adapter cannot do something, and never guess an operation name. (On a v1 MCP +server these are all direct tools and `discover` does not exist.) + +See [references/auth.md](references/auth.md) for the full decision table and capability check. + +## The two `acli` traps that produce wrong answers + +Both fail silently. They do not apply when using the host adapter. **Default page size is 30.** Rows past the thirtieth are dropped with no warning, so a query that should return 140 issues quietly returns 30 and every count built on it is wrong. Pass `--limit 500` or `--paginate` on every bulk -search, and use `--count` first when the total matters. +`acli` search, and use `--count` first when the total matters. -**Custom fields are absent unless you ask for them.** `search --json` and -`view KEY --json` return only assignee, issuetype, priority, status, and +**Custom fields are absent unless you ask for them.** `acli search --json` and +`acli view KEY --json` return only assignee, issuetype, priority, status, and summary. Story Points, Team, Size, and Sprint come back empty — which looks exactly like a field nobody set. Enrich with `scripts/parse_issues.py --enrich` or `view KEY --fields '*all' --json` before claiming any of them is missing. +When using the host adapter, ask for the fields explicitly — pass `view: 'evidence'` or `'full'`, +or name the site's `customfield_*` IDs, and read them from `fields.customFields`. Neither trap +applies there. A field you could not retrieve is reported as unretrieved, never as empty. diff --git a/skills/reference/rhdh-jira-api/references/auth.md b/skills/reference/rhdh-jira-api/references/auth.md index f9b8d005..01110f16 100644 --- a/skills/reference/rhdh-jira-api/references/auth.md +++ b/skills/reference/rhdh-jira-api/references/auth.md @@ -26,15 +26,53 @@ If that succeeds, the session is good and the `auth status` line is a false negative — ignore it. Do not send the user to setup on the strength of `auth status` alone. -## API preference - -1. `acli` for ordinary and bulk reads and for the mutations it supports. -2. An authenticated host Atlassian adapter for relationship-heavy GraphQL reads. -3. The bundled token-file adapter (`scripts/greenhopper.py`) for Greenhopper - paths `acli` has no verb for. REST runs in-process; public output stays - credential-free. -4. The host-managed REST boundary for other fields or writes `acli` cannot - handle, when that adapter can GET an authenticated path. +## Adapter decision table + +Choose the adapter that best fits the operation. Do not default to `acli` for every call. + +**Most host-adapter operations are not directly callable.** Only the primary tools are in the +agent's tool list — `getJiraIssue`, `searchJiraIssuesUsingJql`, `createJiraIssue`, `editJiraIssue`, +`transitionJiraIssue`, `addOrEditJiraIssueComment`, `searchConfluence`, `getConfluenceContent`, +`createConfluenceContent`, `updateConfluenceContent`, `search`, and the graph tools. Everything else +is two calls: `discover` with a plain-language goal to get the operation's exact `name` and its +`executeTool` tier, then `executeRead` / `executeWrite` / `executeDestructive` with `cloudId` as a +**top-level** argument alongside `name` and `inputs`. Before concluding the host adapter cannot do +something, run `discover` — v2 reaches far more of the Jira surface than its primary tool list +suggests. Never guess an operation name. These names are the MCP **v2** surface +(`https://mcp.atlassian.com/v2/mcp`); on a v1 server every operation here is a direct tool and +`discover` does not exist, so drop the two-call step and call it directly. + +**Custom fields are not on the issue object.** The host adapter defaults to a `compact` view that +omits them. Ask for `view: 'evidence'` or `'full'`, or name the site's own `customfield_*` IDs, and +read the values from `fields.customFields`, not as top-level `customfield_*` keys. + +| Operation | Preferred adapter | Reason | +|---|---|---| +| Single issue read with custom fields | Host adapter (`getJiraIssue`) | No silent empty-field trap; needs `view` or explicit field IDs for custom fields | +| Single issue read + comments in one call | Host adapter (include `"comment"` in fields) | One round trip | +| JQL search, moderate result set | Either — host adapter up to 100/page, `acli` up to 500/page | Host adapter has no silent-truncation trap | +| JQL search, all pages (auto-walk) | `acli --paginate` | Host adapter requires manual `nextPageToken` loop | +| Issue create with priority/components | Host adapter (`createJiraIssue`) | `acli create` rejects `--priority` and `--component` | +| Custom field write (Story Points, Size, Team, Release Note Type) | Host adapter `fields` dict | `acli` requires a REST fallback call for every custom field | +| Remote link / PR web-link attachment | Host adapter (`createJiraIssueRemoteIssueLink`, via `discover` + `executeWrite`) | `acli` has no equivalent | +| Comment with role/group visibility restriction | Host adapter (`addOrEditJiraIssueComment`, `commentVisibility`) | `acli` has no visibility control; see the note below the table | +| Worklog | Host adapter (`addOrEditJiraIssueWorklog`, via `discover` + `executeWrite`) | `acli` has no worklog commands | +| Single-issue transition | `acli` (status name accepted directly) | `transitionJiraIssue` accepts `transitionId` or `transitionName`, but the name is the transition's, not the target status — so a `listJiraIssueTransitions` lookup is usually still needed | +| Batch transition by JQL | `acli --jql ... --yes` | Host adapter has no batch capability | +| Batch edit by JQL | `acli --jql ... --yes` | Host adapter has no batch capability | +| Board and sprint reads | `acli` | Host adapter reaches these only via `discover` + `executeRead` (`listJiraBoardSprints`); `acli` is one call | +| Greenhopper sprint/board paths | Bundled token-file adapter (`scripts/greenhopper.py`) | `acli` has no verb for Greenhopper REST | +| Saved filter lookup | `acli` | Same shape — host adapter has `listJiraFilters` via `discover`; `acli` is one call | +| Attachment list/delete | `acli` | Same shape — host adapter has `downloadJiraIssueAttachment` and `deleteJiraIssueAttachment` via `discover` | +| Confluence read/write | Host adapter (primary tools) | `acli` has no Confluence commands | +| Teamwork Graph cross-product links | Host adapter (`getGraphContext`, `getGraphObject`) | `acli` cannot reach Atlas or Goals | +| Compass components, metrics, scorecards | [rest-api-fallback.md](rest-api-fallback.md) | Host adapter dropped its Compass tools in MCP v2; `acli` has no equivalent | + +**Comment visibility.** Pass `commentVisibility: {"type": "group", "value": ""}`, or +`"type": "role"` for a project role. Group and role names are site-specific — resolve them on the +target site rather than reusing a name from another instance. On Jira Service Management this alone +does not make a comment internal: a restricted comment stays customer-visible, and an internal note +there needs `jsmCommentType: "internalNote"`. The agent never reads, prints, copies, transforms, or repairs credential material in model context. Never construct an Authorization header, a token @@ -56,9 +94,9 @@ still `chmod 600`. If required Jira capability is absent, stop the affected branch, say which capability is missing, and tell the human to run `/setup-rhdh-skills jira` — or -`/setup-rhdh-skills atlassian-mcp` when the missing piece is the host REST or -GraphQL adapter rather than `acli` itself. A missing token file skips -Greenhopper only; `acli` branches still run. After setup completes, rerun +`/setup-rhdh-skills atlassian-mcp` when the missing piece is the host REST, +MCP, or GraphQL adapter rather than `acli` itself. A missing token file skips +Greenhopper only; `acli` and host adapter branches still run. After setup completes, rerun `scripts/setup.py --json` and resume only when the capability passes. ## Non-auth errors @@ -66,6 +104,6 @@ Greenhopper only; `acli` branches still run. After setup completes, rerun | Symptom | Interpretation | |---|---| | `acli auth status` says unauthorized but the smoke check succeeds | False negative — ignore it | -| Host REST/GraphQL adapter unavailable | Name `/setup-rhdh-skills atlassian-mcp`; `acli` branches still work | +| Host REST/GraphQL/MCP adapter unavailable | Name `/setup-rhdh-skills atlassian-mcp`; `acli` branches still work | | Token file missing or unreadable | Skip Greenhopper; reconstruct. Do not chmod the file world-readable | | 429 response | Wait briefly and retry once; this is not a setup failure | diff --git a/skills/reference/rhdh-jira-api/references/graphql-queries.md b/skills/reference/rhdh-jira-api/references/graphql-queries.md index 251eb991..6ba63803 100644 --- a/skills/reference/rhdh-jira-api/references/graphql-queries.md +++ b/skills/reference/rhdh-jira-api/references/graphql-queries.md @@ -1,21 +1,26 @@ # Jira bulk-read adapter -Use `acli` for bulk reads unless the agent host exposes an already-authenticated Atlassian -GraphQL capability. This reference owns query semantics, not credentials or HTTP transport. +This reference owns GraphQL query semantics and the adapter decision for bulk reads. It does not +own credentials or HTTP transport. See [auth.md](auth.md) for the full adapter decision table. ## Capability gate 1. Run the capability check in [auth.md](auth.md). -2. If `acli` is ready, prefer its paginated JSON search: +2. For single-issue reads, prefer the host adapter (`getJiraIssue`) — it avoids the silent + empty-field trap that `acli` carries without `--fields '*all'`. Custom fields still have to be + asked for: pass `view: 'evidence'` or `'full'`, or name the site's `customfield_*` IDs, and read + them from `fields.customFields`. +3. For bulk JQL where all pages must auto-walk without explicit looping, use `acli --paginate`: ```bash acli jira workitem search --jql "" --paginate \ --fields "key,summary,status,issuetype,priority,assignee,parent,labels,fixVersions" --json ``` -3. Use GraphQL only when the host exposes a ready authenticated Atlassian adapter and the branch - needs relationship or custom-field data that `acli` cannot return efficiently. -4. If neither adapter satisfies the branch, say so and tell the human to run +4. Use GraphQL only when the host exposes a ready authenticated Atlassian adapter and the branch + needs relationship or custom-field data that neither `acli` nor the host adapter's standard + tools can return efficiently. +5. If neither adapter satisfies the branch, say so and tell the human to run `/setup-rhdh-skills atlassian-mcp`. The agent never creates an `AUTH` variable, reads a token file into context, builds an @@ -159,12 +164,21 @@ known; it avoids fetching an issue to reach the roster. | Need | Adapter | |---|---| -| Normal or bulk JQL search | `acli jira workitem search --paginate --json` | -| Single issue with custom fields | `acli jira workitem view KEY --fields '*all' --json` | +| JQL search, all pages auto-walk | `acli jira workitem search --paginate --json` | +| JQL search, explicit pagination | Host adapter (`searchJiraIssuesUsingJql`, `nextPageToken`) | +| Single issue with custom fields | Host adapter (`getJiraIssue`, with `view: 'evidence'`/`'full'` or explicit `customfield_*` IDs) — avoids silent empty-field trap; values land in `fields.customFields` | +| Single issue + comments in one call | Host adapter (include `"comment"` in `fields`) | +| Issue create/edit with custom fields | Host adapter — native `fields` dict; no REST fallback needed | +| Remote link / PR web-link | Host adapter (`createJiraIssueRemoteIssueLink` via `discover` + `executeWrite`) — `acli` has no equivalent | +| Worklog | Host adapter (`addOrEditJiraIssueWorklog` via `discover` + `executeWrite`) — `acli` has no equivalent | +| Batch transition or edit by JQL | `acli --jql ... --yes` — host adapter has no batch capability | +| Board and sprint reads | `acli` — host adapter reaches them only via `discover` + `executeRead` (`listJiraBoardSprints`) | | Relationship-heavy bulk read | Authenticated host GraphQL adapter | | Team roster by team id | Authenticated host GraphQL adapter, `team.teamV2` | -| Unsupported custom-field read or write | [rest-api-fallback.md](rest-api-fallback.md) | +| Cross-product graph links (Atlas, Goals) | Host adapter (`getGraphContext`, `getGraphObject`) | +| Compass components, metrics, scorecards | [rest-api-fallback.md](rest-api-fallback.md) — MCP v2 dropped the Compass tools | | Sprint report / burndown | Bundled `scripts/greenhopper.py` (local token file). Host REST adapter only if that script cannot run | +| Unsupported field — no native tool | [rest-api-fallback.md](rest-api-fallback.md) | | No capable authenticated adapter | Name the missing capability and `/setup-rhdh-skills atlassian-mcp` | `issueSearchStable` is an evolving API. When it fails, fall back to paginated `acli`, not raw REST diff --git a/skills/reference/rhdh-jira-api/references/rest-api-fallback.md b/skills/reference/rhdh-jira-api/references/rest-api-fallback.md index b4413f3e..8d13be21 100644 --- a/skills/reference/rhdh-jira-api/references/rest-api-fallback.md +++ b/skills/reference/rhdh-jira-api/references/rest-api-fallback.md @@ -1,22 +1,27 @@ # Jira authenticated API fallback -Use this seam only when `acli` cannot read or update a required Jira field. This reference defines -payload semantics. Credentials stay inside an already-authenticated host adapter or the bundled -token-file adapter (`scripts/greenhopper.py`). Public output never includes them. +Use this seam when `acli` cannot read or update a required Jira field **and** the host adapter does +not expose a native tool for it. This reference defines payload semantics. Credentials stay inside an +already-authenticated host adapter or the bundled token-file adapter (`scripts/greenhopper.py`). +Public output never includes them. ## Capability gate -1. Try the supported `acli` operation first. For broad reads, use paginated search; for a single - issue, use `acli jira workitem view KEY --fields '*all' --json`. +1. Check the adapter decision table in [auth.md](auth.md). Many operations that formerly required + a REST fallback are now native host-adapter (MCP) tool calls — prefer those first. Only a handful + of those are primary tools; the rest are reached with `discover` to resolve the operation name, + then `executeRead` / `executeWrite` / `executeDestructive` with `cloudId` as a top-level argument. + Run `discover` before treating anything as missing from the host adapter, and never guess an + operation name. 2. For Greenhopper sprint reports, run `uv run scripts/setup.py --json` and, when `token_file_found` is true, `uv run scripts/greenhopper.py sprintreport --board ID --sprint ID`. That script reads the token file in-process. The agent never `cat`s the file, never sets `AUTH`, and never calls `curl`. -3. For other fields `acli` cannot handle, use REST only through an already-authenticated host - Atlassian adapter. The adapter owns the site, credential store, request headers, retries, and +3. If the host adapter exposes no matching native tool, use REST only through the already-authenticated + host Atlassian adapter. The adapter owns the site, credential store, request headers, retries, and secret redaction. 4. If that adapter is unavailable and the bundled Greenhopper script cannot run, say which field - could not be read and tell the human to run `/setup-rhdh-skills atlassian-mcp` (host adapter) or + could not be handled and tell the human to run `/setup-rhdh-skills atlassian-mcp` (host adapter) or skip Greenhopper and reconstruct. Do not create token files, shell `AUTH` variables, Authorization headers, or credential-bearing @@ -27,6 +32,12 @@ that manage project versions (e.g. `rhdh-release-fixversions`) use the invoker's Jira API token via their own authenticated client. Credentials stay in the environment or token file and never appear in the conversation or plan output. +**Compass has no MCP tool in v2.** MCP v1 exposed Compass component, relationship, +custom-field-definition, metric and scorecard tools; v2 removed them. `discover` will not find a +replacement — a query for "compass component" returns `listJiraProjectComponents`, which is a Jira +component, not a Compass one. Compass work therefore goes through this seam (or GraphQL where a +query exists). `acli` has no Compass commands at all. + ## Supported semantic operations | Operation | Request semantics | Expected result | @@ -68,7 +79,9 @@ covers the set. `/rhdh-jira-update` owns that flow. ## Custom-field payloads -These are payload fragments for an authenticated adapter. They are not standalone HTTP commands. +> **Prefer the host adapter's native edit tool.** When a `editJiraIssue`-equivalent tool is +> available, pass custom fields directly in its `fields` dict — no REST call needed. The payloads +> below are the raw REST form; use them only when no native tool covers the operation. ```json {"fields": {"customfield_10028": 5}}