diff --git a/packages/agents/content/skills/update-jira-ticket/SKILL.md b/packages/agents/content/skills/update-jira-ticket/SKILL.md index db8d0713..65c1c194 100644 --- a/packages/agents/content/skills/update-jira-ticket/SKILL.md +++ b/packages/agents/content/skills/update-jira-ticket/SKILL.md @@ -1,14 +1,37 @@ --- name: update-jira-ticket -description: 'Use whenever updating a Jira issue description or comment via the update_jira_issue MCP tool. Runs a pre-flight checker against the HTML payload to catch known triggers of INVALID_INPUT (composition rules, named entities, Confluence macros, multi-line
, disallowed elements) before any MCP round-trip.'
+description: 'Use whenever updating a Jira issue description or comment, with either the contentFormat-based tool (editJiraIssue, taking fields.description plus contentFormat markdown/adf) or the HTML-based tool (update_jira_issue, taking description_html / comment_html). The HTML tool needs the bundled pre-flight checker to catch INVALID_INPUT triggers (composition rules, named entities, Confluence macros, multi-line 
, disallowed elements) before any MCP round-trip; the contentFormat tool takes Markdown directly.'
 user-invocable: true
 ---
 
 # Update Jira ticket
 
-Use whenever calling `update_jira_issue` (or `create_jira_issue`) with `description_html` or `comment_html`. The MCP tool advertises a permissive HTML surface, but the payload is converted to Atlassian Document Format (ADF) before persistence and frequently rejects valid-looking HTML with an opaque `INVALID_INPUT` error. This skill prescribes the one path that avoids the known triggers, backed by a deterministic pre-flight checker.
+Two different MCP tool shapes update Jira issues, and they need opposite handling. One takes Markdown (or ADF) directly and needs no sanitization; the other takes HTML that Jira converts to Atlassian Document Format (ADF), a conversion that frequently rejects valid-looking HTML with an opaque `INVALID_INPUT` error. Identify which tool you have, then follow that branch.
 
-## The one correct path
+## Identify which tool you have
+
+Inspect your available MCP tools and match the shape:
+
+- **`contentFormat`-based tool** — e.g. `editJiraIssue` (Atlassian Rovo): takes `fields.description` together with `contentFormat: "markdown" | "adf"`. No HTML surface. → Follow the [Markdown path](#markdown-path).
+- **`description_html`-based tool** — `update_jira_issue` / `create_jira_issue` with `description_html` / `comment_html`. → Follow the [HTML path](#html-path).
+
+If both are available, prefer the `contentFormat` tool: the Markdown path is simpler and cannot trigger the HTML→ADF failure classes.
+
+## Markdown path
+
+Use this branch when a `contentFormat`-based tool (e.g. `editJiraIssue`) is available.
+
+1. **Author Markdown.** Prefer a local Markdown artefact when one exists; otherwise compose in Markdown. Pass it via `contentFormat: "markdown"`.
+2. **Prefer Markdown over ADF.** Reserve `contentFormat: "adf"` for content whose fidelity Markdown cannot express (panels, status lozenges, expand blocks, layout columns). ADF is full-fidelity JSON but verbose and harder to author, so reach for it only when Markdown genuinely falls short.
+3. **Do not sanitize.** The HTML allowlist, the composition rules, and the pre-flight checker under [HTML path](#html-path) **do not apply** here, and you must **not** run `update-jira-ticket.mjs`. Those rules exist solely to survive Jira's HTML→ADF conversion, and that converter is never invoked when you submit Markdown or ADF — so there is nothing for them to guard against. Rendering content to allowlist HTML and running the checker on this path is wasted work.
+
+That is the entire path. Everything under [HTML path](#html-path) is irrelevant when a `contentFormat` tool is available.
+
+## HTML path
+
+Use this branch only when the available tool is the HTML-surface `update_jira_issue` / `create_jira_issue` (with `description_html` or `comment_html`). The MCP tool advertises a permissive HTML surface, but the payload is converted to Atlassian Document Format (ADF) before persistence and frequently rejects valid-looking HTML with an opaque `INVALID_INPUT` error. This branch prescribes the one path that avoids the known triggers, backed by a deterministic pre-flight checker.
+
+### The correct path for the HTML tool
 
 1. **Source content as Markdown.** Prefer a local Markdown artefact when one exists. Otherwise, compose in Markdown first — never author HTML directly.
 2. **Convert Markdown to HTML using only the allowlist below.** Anything outside the allowlist must be omitted or rewritten.
@@ -17,11 +40,11 @@ Use whenever calling `update_jira_issue` (or `create_jira_issue`) with `descript
 5. **Never pass a file path** to `description_html` / `comment_html`. File-path mode is forbidden — it has been observed to fail with `INVALID_INPUT`.
 6. **Never include `version_message`** as an argument. It is not a parameter of `update_jira_issue` or `create_jira_issue` — including it triggers a validation failure and a wasted retry.
 
-## Pre-flight checker
+### Pre-flight checker
 
 A bundled helper at `{platform_home_dir}/skills/update-jira-ticket/update-jira-ticket.mjs` validates the rendered HTML against every known failure class. The agent invokes it before every `update_jira_issue` / `create_jira_issue` call.
 
-### Invocation
+#### Invocation
 
 Pipe the HTML on stdin; the helper writes a JSON result to stdout and exits 0 in both the pass and fail cases (only invocation errors exit non-zero).
 
@@ -39,7 +62,7 @@ cat <<'EOF' | node {platform_home_dir}/skills/update-jira-ticket/update-jira-tic
 EOF
 ```
 
-### Output
+#### Output
 
 `ok: true` means the payload passes every rule:
 
@@ -65,11 +88,11 @@ EOF
 
 The rule classes are: `composition-code-inline-mark`, `named-entity`, `confluence-construct`, `pre-multiline`, `disallowed-element`. The same payload may emit multiple findings; fix them all before the next MCP attempt.
 
-### Acting on findings
+#### Acting on findings
 
 For each finding, apply the suggested fix to the source. Do not invoke `update_jira_issue` / `create_jira_issue` until the checker returns `ok: true`. Findings are not optional — every rule corresponds to a documented `INVALID_INPUT` trigger.
 
-## Allowed elements
+### Allowed elements
 
 Exhaustive list. Nothing else.
 
@@ -77,11 +100,11 @@ Exhaustive list. Nothing else.
 
 **Always strip `` and `` constructs unconditionally.** These are Confluence storage-format extensions: `` for Confluence elements like task lists and structured macros (e.g., ``, ``); `` for resource identifiers (e.g., ``, ``, ``). They have no Jira analogue, and including them produces `INVALID_INPUT`. If you have been working with Confluence content in the same session, audit the payload before sending — the checker will catch any that slip through.
 
-## Composition rules (reference)
+### Composition rules (reference)
 
 The pre-flight checker enforces these; this section explains why they exist.
 
-### `` combined with other inline marks
+#### `` combined with other inline marks
 
 `` may not nest with ``, ``, ``, ``, ``, ``, or `` in either direction. ADF represents inline styling as marks on text nodes, and the `code` mark is mutually exclusive with the other inline marks. Applying styling to monospace code has no defensible rendering anyway — code is meant to display literal characters.
 
@@ -98,13 +121,13 @@ The pre-flight checker enforces these; this section explains why they exist.
 isDevMode parameter
 ```
 
-### Multi-line code samples
+#### Multi-line code samples
 
 `
` is omitted from the allowlist entirely, and the checker also flags multi-line `
` separately. ADF's `codeBlock` node accepts plain text, but the converter mishandles the combination of newlines and quote characters inside the `pre` block. Inline `` in `

` survives the same characters, so the `

` wrapper is the differentiator.
 
 **Workaround:** Render multi-line code as either multiple `

...

` paragraphs (one per logical line) or a single `

` with `
` separators between lines and inline `` wrapping the code on each line. Single-line code is unchanged — continue to use inline `` inside `

` or `

  • ` as usual. -## Character handling +### Character handling Use **literal Unicode** in HTML. Do not use named HTML entities outside the three universally-safe ones. The checker flags any named entity other than `&`, `<`, `>` in text content. @@ -119,11 +142,11 @@ Use **literal Unicode** in HTML. Do not use named HTML entities outside the thre `"` and `'` are valid only inside attribute values where they're needed to avoid clashing with the attribute's quote style. The checker only scans text content for named entities, so legitimate attribute-value uses are not flagged. -## Recovery protocol (backstop) +### Recovery protocol (backstop) Use only if `INVALID_INPUT` still fires after the pre-flight checker returned `ok: true`. A clean checker result followed by an MCP rejection means the payload triggered an unknown failure class that the checker does not yet catch. -### 1. Surface the failure to the user +#### 1. Surface the failure to the user Do not create a probe ticket silently. Present the situation to the user and let them choose how to proceed. Use the [recommendation-gradient format](../_data/recommendation-gradient.md): @@ -139,13 +162,13 @@ Do not create a probe ticket silently. Present the situation to the user and let > ➕ no further side effects; > ➖ the failure class remains unidentified. -### 2. If the user picks option 1 (probe and bisect) +#### 2. If the user picks option 1 (probe and bisect) a. **Probe.** Create a ticket with `

    ok

    ` as the entire payload. The create call must include the tagging contract below ([Probe-ticket tagging contract](#probe-ticket-tagging-contract)). If the probe also fails, the problem is call shape, permissions, or the issue itself — not the payload. Stop and report. b. **Bisect.** If the probe succeeds, the failure is in the payload's content. Bisect the payload (split in half, test each half, recurse) to isolate the smallest fragment that still triggers `INVALID_INPUT`. c. **Cap retries.** Do not exceed 4 retry attempts beyond the original failure. If the bisection has not converged by then, surface the smallest failing fragment to the user and stop. -### 3. Record the failure +#### 3. Record the failure Regardless of which option the user picked, append a single JSON object (one line, no trailing comma) to `~/ai-artifacts/skill-failures/update-jira-ticket.jsonl`. Create the directory and file if absent. @@ -167,7 +190,7 @@ Required fields: - `failing_fragment`: The smallest payload fragment that reproduced `INVALID_INPUT`. When the user chose option 2 or 3, record the full rejected payload. - `notes`: Free-form. Name the suspected trigger class if recognisable, otherwise leave empty. -### Probe-ticket tagging contract +#### Probe-ticket tagging contract When (and only when) a probe ticket is created in step 2a, it **must** carry all three markers: @@ -177,7 +200,7 @@ When (and only when) a probe ticket is created in step 2a, it **must** carry all A probe ticket that lacks any of these markers will not be picked up by the cleanup query below and risks polluting the user's backlog indefinitely. -## Probe-ticket cleanup +### Probe-ticket cleanup Probe tickets created via the recovery protocol are designed to be swept by a single JQL query: @@ -187,13 +210,13 @@ project = AND labels = mcp-probe AND created < -1d Run this query periodically and bulk-transition any matches to a closed/deleted state. Probe tickets created before this skill version went live will not carry the `mcp-probe` label and must be cleaned up by hand. -## Escalation criterion +### Escalation criterion If recorded failures concentrate in a **new trigger class** that the checker does not currently cover, file a follow-up to add a rule for it. The rule list in `rules.ts` is the canonical inventory of what the checker catches; extending it is the right unit of escalation. If recorded failures distribute across truly **unknown classes** (no clear pattern), the recovery protocol remains the right tool. See [#467](https://github.com/williamthorsen/codeassembly/issues/467) for the prior decision context and [#468](https://github.com/williamthorsen/codeassembly/issues/468) for the generic-logging follow-up. -## Antipatterns +### Antipatterns - Skipping the pre-flight check before invoking `update_jira_issue` / `create_jira_issue`. - Creating a probe ticket without the `mcp-probe` label, the deterministic title, and the description prefix.