Skip to content
Back to skills

Jira Writeback

ASecurity

Post the run's verdict comment (rendered from the plugins/jira-pack/templates/ template matching verdict + ticket class) and, when the verdict allows it, transition the ticket to jira.qaStatus by exact match. Idempotent via a hidden marker carrying verdict and class; the comment always precedes the transition attempt. NOT for classifying the ticket (that's jira-triage) and NOT for the board card (that's swarmery-board-card).

  • 7 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 5, 2026
ai-agentsgogitapi

Works with

  • api
  • mcp

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add atretyak1985/swarmery --skill jira-writeback --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Jira Writeback?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Jira Writeback
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/atretyak1985-jira-writeback/badge)](https://www.skillsdirectory.com/skills/atretyak1985-jira-writeback)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: jira-writeback
description: "Post the run's verdict comment (rendered from the plugins/jira-pack/templates/ template matching verdict + ticket class) and, when the verdict allows it, transition the ticket to jira.qaStatus by exact match. Idempotent via a hidden marker carrying verdict and class; the comment always precedes the transition attempt. NOT for classifying the ticket (that's jira-triage) and NOT for the board card (that's swarmery-board-card)."
version: "0.2.0"
owner: "swarmery-core"
docs:
  status: reviewed
  source_sha: abec8fdeeb3c
  updated: 2026-08-06
---

# Purpose

The only skill in `jira-pack` that calls `addCommentToJiraIssue` or
`transitionJiraIssue`. It takes `jira-triage`'s verdict and evidence bundle,
renders the matching template from `plugins/jira-pack/templates/`, posts it,
and — only when the caller says the verdict allows it — attempts the QA
transition. It never decides the verdict itself and never touches the board
card.

# Inputs (from the caller, `@jira-task-runner`)

- `verdict` — one of `already-fixed`, `cannot-reproduce`, `needs-info`,
  `needs-fix` (Phase 7) — never called for `too-large`, which Phase 7's
  `jira-escalation` handles on its own.
- `class` — `defect` or `change`, from `jira-triage`. It selects the template
  for `needs-fix` (see [Templates](#templates-plugins-jira-pack-templates)) and
  is recorded in the idempotency marker. A `needs-fix` call that arrives
  without a class is a caller bug: stop and report it rather than defaulting to
  `defect`, which would post a root-cause comment on a feature ticket.
- The evidence bundle from `jira-triage` (command, exit code, output
  fragment, commit/test reference, or the specific questions for
  `needs-info`) — used to fill the chosen template's placeholders.
- `attemptTransition: yes | no` — `no` only for `needs-info`; `yes` for every
  other verdict this skill is called with.
- `cloudId`, `issueIdOrKey`, and the pinned Atlassian MCP tool prefix, all
  resolved by `jira-access-preflight`.
- A run tag (the board card's `externalId`, or a timestamp-less tag if the
  board is unavailable) for the idempotency marker.

# Step 1 — resolve the transition, before composing the comment

Call `getTransitionsForJiraIssue` (a **read**, always allowed, even in
`--dry-run`, even when `attemptTransition: no` — see the note at the end of
this section). Search for a transition whose target status is an **exact**
match against `jira.qaStatus` from the config: case-insensitive, leading/
trailing whitespace trimmed, and nothing fuzzier than that. The config exists
precisely so this skill never has to guess which status is "the QA one" —
a partial or fuzzy match would silently pick the wrong transition on a
workflow with several similarly-named statuses.

- **Found** → note the transition `id`; the comment will not need an extra
  line, and Step 4 will actually call `transitionJiraIssue`.
- **Not found** → the comment (composed in Step 2) gains a line naming every
  available transition, and Step 4 never fires — the ticket's status is left
  exactly as it was.

This read happens **first**, ahead of composing the comment body, because its
result decides whether the comment needs that extra "status not changed"
line. Running it first does not violate the comment-before-transition
ordering below — that ordering is about the two **write** calls
(`addCommentToJiraIssue`, `transitionJiraIssue`); a read is free to happen
whenever it is needed to make the write correct.

When `attemptTransition: no` (the `needs-info` path): still call
`getTransitionsForJiraIssue` only if the caller wants the dry-run/for-the-record
answer; in a real (non-dry-run) `needs-info` run this read is not needed at
all, since no transition of any kind will ever be attempted regardless of
what it would return — skip it to avoid an unnecessary call.

# Step 2 — compose the comment

Render the template matching the verdict (see
[Templates](#templates-plugins-jira-pack-templates)) with the evidence bundle,
plus — only if Step 1 found no match — the "status not changed" line:

```
Status not changed: no transition to "<jira.qaStatus>" exists in the current
workflow.
Available transitions: <comma-separated list of transition names>.
```

Every rendered comment ends with a hidden idempotency marker as its **literal
last line**, and the marker carries this run's verdict so a later run can
decide whether to skip **without** re-deriving the verdict from the comment's
rendered prose:

```
<!-- swarmery:jira-task-runner run=<external_id or run tag> verdict=<already-fixed|cannot-reproduce|needs-fix|needs-info|too-large> class=<defect|change> -->
```

`class` is recorded alongside the verdict because the two together are what a
later run needs to decide whether anything changed: the same ticket can
legitimately move from `needs-info` (criteria weren't testable) to `needs-fix`
(they are now), and a marker carrying only the verdict cannot distinguish a
re-run of the same conclusion from a genuinely new one on a re-classified
ticket.

**Language**: the same language the ticket is written in (read off
`summary`/`description`); English if that is ambiguous.

# Step 3 — idempotency check

Before posting, scan the ticket's existing comments (already fetched during
`jira-triage`'s Step 1 read — no extra call needed) for markers matching this
agent's tag, `<!-- swarmery:jira-task-runner run=<tag> verdict=<slug> -->`. If
more than one such marker exists on the ticket, take the **most recent** one —
never an older one. The skip decision is read straight off that marker's
`verdict` field — never reconstructed by reading the surrounding comment
prose:

- **No marker with this run's tag** (first run, or the board card is new) →
  write.
- **Marker found, `verdict` differs from this run's verdict** — e.g. the most
  recent marker carries `verdict=needs-info` and this run just produced
  `cannot-reproduce` (more information became available, or the environment
  got fixed) → write; only a truly identical repeat write is what idempotency
  guards against.
- **Marker found, `verdict` matches this run's verdict** → skip the write,
  recording `comment: skipped (identical verdict already posted)`.

Use the exact verdict slugs `jira-triage` assigns: `already-fixed`,
`cannot-reproduce`, `needs-fix`, `needs-info`, `too-large` — never a
paraphrase, so the marker's `verdict` field always matches one of the five.

# Step 4 — post, in order: comment, then transition

1. **Write** the comment via `addCommentToJiraIssue`
   (`contentFormat: "markdown"`, `cloudId`, `issueIdOrKey`, `commentBody`) —
   unless Step 3 said to skip.
2. **Only then**, if `attemptTransition: yes` and Step 1 found a match, call
   `transitionJiraIssue` with `transition: {id}`.

**Why this order, specifically**: if the transition call fails after the
comment succeeded, the ticket already carries the explanation of what the run
found — a human reading it is not left guessing. The reverse order (transition
first) would risk leaving the ticket moved to a new status with no comment
explaining why, which is a worse failure mode than a transition that simply
didn't happen yet.

# Dry-run

No call to `addCommentToJiraIssue` or `transitionJiraIssue` — those are the
only two calls this mode suppresses. `getTransitionsForJiraIssue` (Step 1) and
the idempotency scan (Step 3, over already-fetched comments) still run, so the
dry-run's printed answer is honest rather than a guess. Print, instead of the
two write calls:

```
DRY-RUN jira comment <KEY>
<full rendered comment text, including the hidden marker line>
DRY-RUN jira transition <KEY> → "<jira.qaStatus>" (transition id=<id> | NOT AVAILABLE)
```

If Step 3 would have skipped the write (identical verdict already posted),
print that instead of the `DRY-RUN jira comment` line:
`would skip: identical verdict already posted`.

# Templates (`plugins/jira-pack/templates/`)

| Verdict | Class | Template |
|---|---|---|
| `already-fixed` | either | `comment-already-fixed.md` |
| `cannot-reproduce` | `defect` only (a `change` ticket can never carry this verdict — `jira-triage`) | `comment-cannot-reproduce.md` |
| `needs-info` | either | `comment-needs-info.md` |
| `needs-fix` | `defect` | `comment-fix-summary.md` |
| `needs-fix` | `change` | `comment-change-summary.md` |
| `too-large` (posted by `jira-escalation`, not this skill) | either | `comment-too-large.md` |

The `needs-fix` split is the whole reason `class` is an input: the defect
template opens with a root cause, which a change ticket does not have, and the
change template pairs each acceptance criterion with the test that now asserts
it, which a defect fix has no list for. Rendering the wrong one produces a
comment whose first line is a fabrication.

**A `cannot-reproduce` call for a `class: change` ticket is a caller bug** —
stop and report it instead of rendering the template. `jira-triage` cannot
produce that combination; if it arrives here, something upstream reclassified
or lost the class, and posting it would move unimplemented work to QA.

Every template ships with placeholders only — no real ticket keys, hostnames,
or team names (`docs/NEUTRALITY.md`).

# Placeholders / neutrality

Any example this skill's own report prints uses only `<jira-base-url>`-style
hosts, `<KEY>`, and `<PROJECT-KEY>` — `scripts/scan-flavor.sh` must stay
`✓ clean` for `plugins/**`.

# Self-check before returning

- [ ] `getTransitionsForJiraIssue` ran before the comment body was finalized,
      whenever a transition might be attempted
- [ ] The rendered comment ends with the hidden marker as its literal last
      line, carrying both `verdict=` and `class=`
- [ ] The template was selected by verdict **and** class — `needs-fix` on a
      `change` ticket rendered `comment-change-summary.md`, never the
      root-cause-shaped `comment-fix-summary.md`
- [ ] No `cannot-reproduce` comment was rendered for a `class: change` ticket
- [ ] The idempotency check read the verdict off the most recent marker's
      `verdict` field — never skipped on marker presence alone, and never
      guessed the prior verdict from the comment's rendered prose
- [ ] `addCommentToJiraIssue` was called (or explicitly skipped, with a
      recorded reason) before any `transitionJiraIssue` call in the same run
- [ ] `needs-info` calls never resulted in a `transitionJiraIssue` call
- [ ] Dry-run made zero calls to `addCommentToJiraIssue`/`transitionJiraIssue`

# Common mistakes to avoid

- Calling `transitionJiraIssue` before `addCommentToJiraIssue` "to save a
  round trip" — never; the ordering exists specifically for the failure case.
- Fuzzy-matching `jira.qaStatus` ("QA" matching "QA Review") — exact,
  case-insensitive, trimmed, nothing looser.
- Skipping a comment merely because *any* marker is present, without checking
  its `verdict` field matches this run's verdict — and never inferring that
  prior verdict from the comment's rendered text instead of the marker.
- Posting the "status not changed" line even when a matching transition was
  found (it belongs only in the not-found branch).

# Related

- `plugins/jira-pack/skills/jira-triage/SKILL.md` — produces the verdict and
  evidence bundle this skill renders and posts.
- `plugins/jira-pack/skills/jira-config/SKILL.md` — source of `jira.qaStatus`.
- `plugins/jira-pack/skills/jira-access-preflight/SKILL.md` — source of the
  pinned Atlassian MCP tool prefix and `cloudId`.
- `plugins/jira-pack/skills/swarmery-board-card/SKILL.md` — moves the board
  card after this skill completes; this skill never calls the board API
  itself.
- `plugins/jira-pack/templates/` — the five comment templates this skill
  renders from.

# How to use

## What it does

This skill is the only thing in the pack that writes to a ticket. You hand it a verdict and the evidence behind it, and it renders the matching comment template, posts it, and — when the verdict allows — moves the ticket to your configured QA status. It never decides the verdict itself, and it never touches the board card.

## When to use it

- A triage step has produced a verdict and evidence bundle, and the ticket now needs that result written back as a comment.
- The run finished successfully and the ticket should move to the configured QA status.
- You want a rerun on the same ticket to stay safe: an identical verdict posts nothing twice.
- You are checking a run end to end and want the dry-run output of exactly what would be posted.

## When not to use it

- To decide whether a ticket is already fixed, needs a fix, or needs more information — use `jira-triage`.
- To move the tracking card on the board after the comment lands — use `swarmery-board-card`.
- To post the over-budget plan comment on a `too-large` ticket — use `jira-escalation`.
- To find the QA status name or the connection details — use `jira-config` and `jira-access-preflight`.

## How to invoke

```
Skill(skill: "jira-pack:jira-writeback")
```

Call it once per run, after triage has finished. It is normally invoked by the task runner rather than typed by hand.

## Inputs

- `verdict` — one of `already-fixed`, `cannot-reproduce`, `needs-info`, `needs-fix` — required.
- `class` — `defect` or `change` — required; it picks which `needs-fix` template renders.
- Evidence bundle — the command, exit code, output fragment, commit or test reference, or the questions to ask — required.
- `attemptTransition` — `yes` or `no`; `no` only for `needs-info` — required.
- `cloudId`, ticket key, and the pinned tool prefix — required, all resolved by the preflight skill.
- Run tag — the identifier written into the hidden idempotency marker — required.

## What you get back

A comment on the ticket, written in the ticket's own language, ending with a hidden marker that records the verdict and class. If no transition matching your QA status exists, the comment names every transition that does exist and the status is left alone. The comment is always written before the transition is attempted, so a failed move never leaves an unexplained status change. In dry-run mode nothing is written — you get the full rendered comment and the transition decision printed instead.

## Worked example

```
Skill(skill: "jira-pack:jira-writeback")
verdict: cannot-reproduce, class: defect, attemptTransition: yes, key: <KEY>

→ reads available transitions, finds one matching the configured QA status
→ renders comment-cannot-reproduce.md with the repro command and exit code
→ no prior marker for this run tag, so it posts the comment
→ then transitions <KEY> to the QA status
```

You end up with one comment carrying the evidence and a hidden marker, and the ticket sitting in QA. Rerun it with the same verdict and it reports `comment: skipped (identical verdict already posted)`.

## Related

- `jira-triage` — produces the verdict and evidence this skill renders.
- `jira-escalation` — handles the `too-large` verdict, which this skill never receives.
- `swarmery-board-card` — moves the board card once this skill has finished.
- `jira-config` — supplies the QA status name matched here.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.