
Peer Qa Review
- 5 installs
- 1 repo stars
- Updated August 3, 2026
- netresearch/peer-qa-review-skill
Helps with ai & agent building tasks.
About
peer-qa-review is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- peer-qa-review
- AI & Agent Building
- AI-coding skill
Peer Qa Review by the numbers
- 5 all-time installs (skills.sh)
- Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/netresearch/peer-qa-review-skill --skill peer-qa-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 3, 2026 |
| Repository | netresearch/peer-qa-review-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
Peer QA Review (Round 1)
A teammate has marked work as ready for QA. Your job: verify it before it goes to customer acceptance / QA2 / internal close. Round-1 QA is not rubber-stamping — re-run verification yourself, check formal correctness, check inventory and docs, post a structured QA comment, transition the ticket.
When this skill applies
Auto-trigger:
- Ticket transitioned to QA status (or equivalent: "Review", "Code Review", "Ready for Review")
- Comment "ready for QA" / "ready for review" posted by the implementer
- User asks for "IT QA review", "internal QA", "peer review", "quality gate", "review and resolve"
Skip when:
- Ticket is still In Progress — ask implementer to transition first
- Ticket is QA2 / customer-acceptance — different scope
- Ticket is already closed — post-mortem only; say so explicitly
- You are also the implementer — you can't self-review (see
references/edge-cases.md)
Companion skills
- Required: a ticket-system skill. For Jira: `jira-communication`. Stage 0 uses its
qa-gather.pyfor single-call discovery (seescripts/qa-gather.sh). - Optional: if your team has internal IT / maintenance / ITSM skills, consult them after this skill loads — they define project-specific overrides (project list, inventory CRUD, announcement channels, customer-vs-internal routing).
Lifecycle (6 stages)
| Stage | Name | Output |
|---|---|---|
| -1 | Claim | Ticket assigned to me; not previously mine |
| 0 | Discover | Single-call gather of issue + comments + worklog + links + sibling tickets |
| 1 | Formal correctness | Description / linkage / console-output / worklog checks |
| 2 | Functional + Inventory + Guardrails | Reviewer re-runs verification; inventory updated; guardrails checked (adjacent components, shared-layer downstream, unchanged default path) |
| 3 | Docs + Rollback + Communication | Runbook stale? Snapshot taken? Announced if customer-affecting? |
| 4 | Verdict | Pass-resolve · Pass-QA2 · Bounce · Won't-do |
| 5 | Comment + Transition | Single structured QA comment; transition |
Full per-stage checks: references/lifecycle.md and references/checklist.md.
Severity vocabulary
Reuse the standard Atlassian icon set. Do not invent new categories.
| Icon | Meaning | Action |
|---|---|---|
(/) | verified / passed | none |
(x) | MUST: blocking — cannot resolve | bounce to In Progress |
(!) | SHOULD: real issue, non-blocking here | document; create follow-up if structural |
(i) | HINT: improvement / next-time nice-to-have | document; no action required |
(?) | open question | block on answer |
Rationale and examples: references/severity.md.
Output: one structured comment
Single Jira comment, header h3. IT Internal QA, h4 sections for each pillar, severity icons throughout, verdict line at the end. Optional addendum comments for separable concerns (e.g. Confluence runbook review).
Full template + filled examples: references/comment-template.md.
Stage 0 — discover (one call)
# If jira-communication is installed:
${CLAUDE_SKILL_DIR}/scripts/qa-gather.sh <ISSUE-KEY>
# JSON for programmatic consumption:
${CLAUDE_SKILL_DIR}/scripts/qa-gather.sh <ISSUE-KEY> --jsonReturns issue + comments + worklog + issue/web links + URLs extracted from description and comments (MR/PR/pipeline/commit/tag/release/issue) + sibling tickets — in one call. Falls back to multi-call if qa-gather.py is unavailable.
Stage -1 — claim the ticket
digraph claim {
"ticket assignee?" [shape=diamond];
"claim it" [shape=box];
"stop: in flight by someone else" [shape=box];
"stop: cannot self-QA, hand off" [shape=box];
"proceed" [shape=doublecircle];
"ticket assignee?" -> "claim it" [label="unassigned (team queue)"];
"ticket assignee?" -> "stop: in flight by someone else" [label="someone else"];
"ticket assignee?" -> "stop: cannot self-QA, hand off" [label="me"];
"claim it" -> "proceed";
}The Round-1 QA queue is typically a team queue with no assignee. Self-assign and continue. If someone else holds it, stop. If it's already yours from earlier (you implemented it), you cannot self-QA — see references/edge-cases.md.
Verdict routing
| Outcome | When | Action |
|---|---|---|
| Pass — resolve | All (x) clear, IT-internal scope (no customer affected) | transition QA → Closed/Done |
| Pass — QA2 | All (x) clear, customer-affecting change | transition QA → QA2, assign to product owner / customer |
| Bounce — In Progress | Any (x) | transition QA → In Progress, comment with the blocker, re-assign to implementer |
| Won't-do | Blocking external prerequisite missing | document, file follow-up ticket, resolve as Won't-do with reopen condition |
QA2-vs-internal-resolve decision rule: see references/edge-cases.md. When uncertain, default to QA2.
References
references/lifecycle.md— full per-stage detailreferences/checklist.md— every check, organised by pillar (F1–F8 formal, R1–R6 functional, G1–G3 guardrails, I1–I4 inventory, D1–D4 docs, B1–B3 rollback, C1–C3 communication, P1–P6 process compliance)references/severity.md— icon vocabulary and worked examplesreferences/comment-template.md— Jira-wiki template + 3 worked examples (pass, bounce, won't-do)references/edge-cases.md— bounce, won't-do, self-review, unverifiable evidence, QA2 routingreferences/anti-patterns.md— what to flag in implementer's commentsreferences/frameworks.md— light alignment notes (ITIL PIR, Scrum DoD)
Scripts
| Script | Purpose |
|---|---|
scripts/qa-gather.sh | Stage 0 single-call discovery (delegates to jira-communication's qa-gather.py, falls back to multi-call) |
Anti-patterns to flag
(See references/anti-patterns.md for the full list with examples.)
1. One giant final comment instead of one-per-step 2. Markdown leakage in Jira (**bold**, # heading, em-dashes --) 3. Inventory bulk-updated at end instead of immediately after each component 4. No console-output audit trail for key actions 5. Tag without pipeline (release pushed but CI was red or skipped) 6. No re-execution by reviewer — copy-pasting implementer's output is not QA
Anti-patterns
Things to flag in the implementer's comments. Each item below is (!) unless noted.
Comment hygiene
1. One giant final comment instead of one-per-step. Kills the audit trail when work is interrupted; reviewers can't tell what was done when. 2. `{{monospace}}` for commands instead of {code} blocks. Can't be expanded/collapsed, no syntax highlighting, copy-paste eats whitespace. 3. Markdown leakage in Jira: **bold**, # heading, em-dashes --, [display text](url). Renders as literal text. 4. `[display text|url]` display-text links. Convention in many NR teams is full URLs — the URL shows where it goes, the text becomes a lie when the link is renamed. 5. No prompt context in `{code}` blocks: just bare output. The reader can't tell which host or container the command ran in. 6. Mixing implementer and reviewer voice in one thread without clear h3. headings to separate concerns.
Evidence quality
7. Tag without pipeline — release tag pushed but CI was red, skipped, or not run. The tag claims "this version exists and works"; without green CI, the second half is unsupported. (x) if no other evidence; (!) if other verification is present. 8. Inventory bulk-updated at end instead of immediately after each component. If the work is interrupted, the inventory state is wrong — and the audit trail of when each component reached the new version is lost. 9. No console-output audit trail for a key irreversible action. Tmux, script(1), terminal recording, pasted blocks — all acceptable. None is not. (Required only for irreversible / multi-step actions, not single-line edits.) 10. Stale verification output — implementer captured output once at start of work, never re-ran after the final commit. Reviewer should re-run anyway, but the implementer's stale output is misleading and should be flagged. 11. Verification commands that don't actually verify the fix — running systemctl status when the bug was about a config-reload race, for example. The command runs cleanly but doesn't exercise the failing scenario.
Reviewer anti-patterns
12. Copy-pasting implementer's output instead of re-running. This is not QA — this is re-reading. R-pillar checks require fresh output. 13. Inflating `(x)` to look thorough — only use blocking severity when the ticket genuinely cannot resolve. Over-bouncing wastes everyone's time. (severity.md) 14. Reviewing in pieces across many comments instead of one structured QA comment. Hurts auditability — pillars get scattered, the verdict is unclear. 15. Quoting the entire implementer comment back at them in the QA comment — wastes space. Reference by date/author/section if you must. 16. Missing the verdict line — every QA comment must end with one of: Ready to transition to QA passed / Ready for customer acceptance (QA2) / Bouncing to In Progress / Resolving as Won't-do. No verdict = unclear next action.
Process
17. Self-review — see edge-cases.md E. The reviewer can't be the implementer. If forced, document the constraint and flag for asynchronous sanity check. 18. QA2 routing skipped for customer-affecting work — if the change touches customer-visible state, it must go to customer acceptance, not internal-resolve. Defaulting to QA2 when uncertain is the right call. 19. No follow-up ticket for structural `(!)` items — if a should-fix is genuinely structural (architecture, naming, recurring pattern), document it AND file a follow-up. Otherwise it's lost. 20. Resolving without checking sibling tickets — when this ticket is one of N parallel tickets, the others may have caught issues this one missed. A 30-second sibling spot-check is cheap insurance.
Round-1 QA Checklist
All checks, organised by pillar. Apply the severity in the rightmost column when a check fails. See severity.md for the full vocabulary.
Pillar F — Formal correctness
| # | Check | Severity if failing |
|---|---|---|
| F1 | Description has clear, testable acceptance criteria (or numbered Task list) | (x) if absent — cannot QA |
| F1.5 | Description currency — does the description still accurately describe what was actually delivered? Scope shifts, emerged requirements, surfaced constraints, and important tradeoffs that came up during work should be folded back into the description (not buried in comments). The description is the canonical record for future readers. | (!) if stale and misleading; (i) if minor drift |
| F2 | Implementer-side comments document each step with {code} blocks containing both the command and its output | (!) per missing block |
| F3 | Console output / screenshot for the actual fix is present | (x) if no proof at all; (!) if partial |
| F4a | Structured issue links — related issues, predecessor, inventory item are linked via the ticket system's issue-link feature (not just mentioned in prose). Bidirectional traceability matters: someone navigating from the inventory item should find this ticket. Verify by querying issue links, not by reading the description. Parent Epic / Parent / Sprint are out of scope here — see the "parent-Epic exception" note below. | (!) per missing link |
| F4b | External work artefacts — MR/PR, pipeline, registry/release — present in comments or as web/remote links, and reachable. Prefer GitHub/GitLab native shorthand (owner/repo#123, group/project!456, owner/repo@7c12680) over full URLs for trusted shared-namespace projects; full URLs for pipelines, branches, releases, and cross-org links. See comment-template.md "Link conventions". | (!) per missing or 404 |
| F5 | (merged into F4b) | — |
| F6 | Worklog present (≥ 1 entry, plausible duration) | (!) — should-have. Worklogs are part of the audit/billing/capacity trail; the team has agreed to log work, so missing entries shouldn't be normalised as merely a hint. |
| F7 | Comment formatting: ticket-system-native markup (Jira: wiki, not Markdown), no leaking **bold**, # heading, em-dashes -- | (i) |
| F8 | (deprecated — replaced by Stage -1 claim) | — |
Note F1: "acceptance criteria absent" is the one finding you should bounce on without going further. Without criteria, there is no testable bar.
Note F4a: don't conflate "mentioned in description prose" with "linked via issue-link feature". A common self-deception is reading the description, seeing IOT-146, and assuming it must be linked. Always verify against the actual issue-link list (Jira: Issue Links section / API issuelinks array). If a related ticket is mentioned in prose but not linked, that's a (!) — and an easy fix (add the link).
Note F4a (reviewer-side): apply the same rule to your own QA comment. Every ticket key, MR/PR URL, commit hash, runbook page, or vault entry you mention as a reviewer should also exist as a structural link on the ticket. If the QA comment introduces a new reference — typical case: "filed follow-up as NEW-TICKET", "see MR !N", "fixed by commit abc123" — create the structural link (issue link for tickets, web link for external URLs) before posting the comment, not after. The inline mention is for narrative; the link is the relationship that survives someone navigating in from the other side. Anti-pattern caught the hard way: QA comments referencing a follow-up ticket only inline, leaving the navigation one-way.
Note F4a (parent-Epic exception): the parent Epic lives in Jira's dedicated Epic Link custom field, not in the issue-link list. Don't add a redundant relates to <epic> issue-link — it duplicates a relationship that Jira already expresses structurally (epic badge in the child, "Issues in Epic" panel in the parent, JQL via "Epic Link" = EPIC-KEY). Same exclusion applies to the Parent field on sub-tasks (JQL parent = PARENT-KEY), the Sprint field (JQL sprint = "Sprint X"), and any other dedicated Jira-native relationship field. The link-audit rule covers references the implementer/reviewer introduces; Jira-built-in structural fields are out of scope.
Note F7: applies equally to the reviewer's own QA comment. Common violations to scan for before posting:
- Display-text links
[text|url]when convention is full URLs (or vice versa — match the team's house style). - `{{monospace}}` for commands in prose when convention is
{code}blocks. Especially galling when the same comment claims the opposite under Pillar P. - Transition-name vs verdict mismatch — e.g. writing "Ready to transition to QA passed" when the routing rule for IT-internal scope is Resolve (and "QA passed" goes to QA2). The verdict's meaning and the transition name must agree.
Pillar R — Functional resolution
The reviewer re-runs these. Copy-pasting the implementer's output does not satisfy R-checks.
| # | Check | How |
|---|---|---|
| R1 | Re-run the failing scenario from the original report. Does it now succeed? | Per ticket. Capture fresh output. |
| R2 | Re-run the implementer's verification commands, capture fresh {code} blocks for the QA comment | Don't trust stale output |
| R3 | Idempotence for IaC / config-mgmt: a re-run applies cleanly with changed=0 (or, if not, the diff is explicable) | Ansible: ansible-playbook --diff; Terraform: tofu plan |
| R4 | No collateral damage: failed services, error-level logs since the change, container health, dependent services | systemctl --failed, journalctl -p err --since '1h ago', etc. |
| R5 | Tag/release exists where claimed (annotated vs lightweight is (i); missing is (x)) | git for-each-ref refs/tags/<v> |
| R6 | CI pipeline for the merged commit/tag was green | glab api projects/<id>/pipelines?ref=<tag> or equivalent |
Severity: each R-check failing on its own is (x) for R1–R4 (the fix doesn't actually work or breaks something else); (!) for R5–R6 (artefacts present but quality flag).
Pillar G — Guardrails (what the change wasn't trying to touch)
The R pillar verifies the change fixed what the ticket said it would. The G pillar verifies it didn't break what the ticket didn't mention. Round-1 QA owns this — by QA2 / customer acceptance the cost of finding it is already too high.
| # | Check | Severity |
|---|---|---|
| G1 | Adjacent components — name two adjacent components the change could have affected but is not trying to. Spot-check each is unchanged. If you cannot name two, the blast radius is not understood. | (!) SHOULD; (?) if blast radius unclear |
| G2 | Shared layer downstream — if the change is in a shared layer (auth, logging, error handling, DB schema, build config), exercise one downstream consumer end-to-end, not just the changed code. | (x) MUST if shared layer; n/a otherwise |
| G3 | Unchanged default path — if the change touches a config file, env var, or feature flag default, verify the unchanged default path still behaves as before. Don't just test the new branch. | (x) MUST |
Severity uses the standard icon vocabulary (/) (x) (!) (i) (?). A G-finding that demonstrates a real regression is (x) MUST and bounces the ticket. A G-finding that surfaces "I cannot tell if X is affected" is (?) and blocks on the answer.
Common false negatives this catches
- Bugfix touched a shared helper; the helper has three callers and only one was tested.
- New feature flag's "off" path silently flipped because the default changed in code, not in config.
- Schema migration's rollback path was never tested (B-pillar checks the migration; G2 checks a real downstream query against rolled-back schema).
- "Refactored for clarity" changed behavior in an edge case the original test suite didn't cover.
For changes with no plausible adjacent surface (e.g. a typo fix in a comment, a one-line README update), G-pillar may be recorded as n/a rather than (/).
Pillar I — Inventory & related artefacts
| # | Check | Severity |
|---|---|---|
| I1 | Inventory / CMDB entry updated where applicable (e.g. IOS "Current Version" custom field for maintenance tickets) | (x) for maintenance tickets if missing; (!) otherwise |
| I2 | Linked tickets state: parent epic moves forward, child tickets are themselves in valid states, inventory issues reflect the change | (!) per inconsistency |
| I3 | Sibling tickets: if this is one of N parallel tickets (e.g. multiple host upgrades), spot-check that this ticket's pattern matches the others | (i) — flag deviation |
| I4 | Side-quests (improvements made en passant) are documented as separate sub-headings, not lost | (i) |
Pillar D — Documentation & runbook
| # | Check | Severity |
|---|---|---|
| D1 | README / role meta / module manifest matches reality (e.g. supported-platforms list reflects what we now run on) | (!) |
| D2 | Internal runbook (Confluence / wiki / repo doc) reviewed for staleness. Minor → propose update; major → flag in QA comment | (i) to (!) |
| D3 | CHANGELOG / release notes present for tagged releases | (i) (often inconsistent across teams) |
| D4 | AGENTS.md / CLAUDE.md / repo-level docs updated if the change affects future automation | (i) |
Pillar B — Rollback / backout
| # | Check | Severity |
|---|---|---|
| B1 | Snapshot / backup taken before risky change (VM snapshot, database backup, config archive). Evidence in implementer's comments. | (x) for irreversible production changes without a backup; (!) otherwise |
| B2 | Backout path documented (which command rolls back, where the prior artefact is) | (!) |
| B3 | Backup retention: archive accessible / not auto-purged before reasonable verification window | (i) |
For purely additive changes (new feature, no data migration, no service disruption), B-pillar may not apply — record as n/a rather than (/).
Pillar C — Communication
Conditional. Apply only when the change has external visibility.
| # | Check | Severity |
|---|---|---|
| C1 | Customer-affecting change → announcement posted to relevant channel (per-customer Slack/Matrix/email/dashboard) | (!) if missed |
| C2 | Org-wide service downtime / behaviour change → org channel notified (team Matrix room, email list, internal blog) | (!) if missed |
| C3 | Security-relevant action (password rotation, ACL change, exposed surface reduced) → security log / ticket / sec-channel notified | (!) if missed |
For purely internal, behind-the-scenes changes (e.g. bumping a private CI image), C-pillar is n/a.
Pillar P — Process compliance
For tickets in scope of a maintenance / change-management lifecycle skill (your team's internal one), apply these.
| # | Check | Severity |
|---|---|---|
| P1 | Console-output audit trail attached or pasted for key actions (deploys, irreversible ops). Tmux, script(1), terminal recording, pasted blocks all count. Required only for irreversible / multi-step actions. | (!) if missing for an action that needed one |
| P2 | Comments use `{code}` blocks (not {{monospace}}) per Jira-wiki conventions | (!) per violation |
| P3 | One comment per logical step — not one giant comment at the end | (i) |
| P4 | Inventory updates posted immediately after each component, not bulk at end | (i) |
| P5 | Verbose CI / docker / etc output flags used inside logs (e.g. docker compose --progress=plain) — no animated noise | (i) |
| P6 | Phase / transition rule of the team's maintenance lifecycle respected (IT-internal vs customer routing) | (x) if wrong path taken |
Comment Template
One structured comment at the end of QA. Use Jira wiki markup. Optional addendum comments for separable concerns. For long reviews with action items, also post a TL;DR action comment — see the bottom of this file.
Link conventions
For your team's trusted shared-namespace GitHub / GitLab projects, prefer the platform's native shorthand over full URLs. Same logic as why we already use bare NRS-4365 instead of full Jira URLs: shorter, more scannable, project-context inline.
| Type | GitHub | GitLab (e.g. internal git server) |
|---|---|---|
| Repo | owner/repo | group/project |
| Issue | owner/repo#123 | group/project#123 |
| PR / MR | owner/repo#456 | group/project!456 |
| Commit | owner/repo@7c12680 | group/project@7c12680 |
| Branch / pipeline / release / tag | full URL | full URL |
In Jira specifically: the shorthand alone is not clickable — Jira only auto-links its own issue keys. To get clickable links and shorthand readability in Jira, wrap the shorthand as display text in a Jira link macro:
[provision/ansible-role-vault!9|https://git.netresearch.de/provision/ansible-role-vault/-/merge_requests/9]
[netresearch/peer-qa-review-skill#10|https://github.com/netresearch/peer-qa-review-skill/issues/10]This renders as a clickable link reading provision/ansible-role-vault!9 — the same anchor text GitHub/GitLab use natively. *This is not the display-text-link anti-pattern (which targets opaque text like `[click here|url]`) — the shorthand IS the canonical reference, so using it as display text is the opposite* of opaque.
On GitHub PRs / GitLab MRs: the bare shorthand is already auto-linked by the platform, so write provision/ansible-role-vault!9 (without the […|…] wrapper) when authoring there.
Rule of thumb:
- Authoring in Jira → wrap:
[shorthand|url] - Authoring on GitHub/GitLab → bare:
shorthand - Authoring anywhere ambiguous → bare shorthand + full URL on the next line, or just use full URL.
Keep full URLs for:
- Pipelines, branches, releases, tags (no clean shorthand)
- Cross-org / public / third-party projects where the namespace isn't shared
- Anything you're not 100% sure the reader knows
Rendering note: GitLab's ! in group/project!456 is not a Jira image-macro trigger — Jira's !filename! macro requires a closing !. Mid-token ! is safe; no escape needed.
Format pillar findings as bulleted lists
Use * (or ** for sub-items) at the start of each finding line — not bare lines with severity-icon prefixes.
Why: line-wrap. Findings often run long (multiple linked tickets, file paths, command output references). On bare severity-icon lines, the wrap continuation lands at the left margin, under the icon, which is hard to scan. With formal list items, Jira's <li> indent makes wrap continuation align under the text, not the bullet — so each finding stays visually grouped.
h4. Formal correctness
* (/) F1: Description has clear acceptance criteria
* (/) F4a: Structured Jira issue links:
** NRS-4317 — parent, Closed
** IOT-146 — VM inventory, In use
** SRVV-104 — related, Closed
* (!) F6: No worklog entries — should-have per audit/billing/capacityRenders as a properly nested <ul> with bullet, severity icon, and indented sub-items. Long lines wrap under the text.
Avoid the bare-line style for pillar findings:
h4. Formal correctness
(/) F1: Description has clear acceptance criteria...
(!) F6: No worklog entries — long line that will wrap...This renders with <br/> between items; long lines wrap to the left margin, under the icon, breaking visual grouping.
Exceptions — keep prose paragraphs (not lists) for:
- The Verdict header sentence ("All must-have checks pass.")
- The Follow-up section narrative
- Re-execution / unverifiable-evidence explanations that follow a finding (the explanation is a paragraph under the bullet, not its own bullet)
- The TL;DR comment uses numbered
#items, not*— actions deserve sequence.
Template
h3. IT Internal QA — {passed | failed | won't do}
h4. Formal correctness
(/) Description has clear acceptance criteria (N numbered tasks)
(/) Implementer comments document each step with command+output
(/) Linked: {parent}, {MR/PR}, {inventory ticket}
(!) {finding}
(i) {hint}
h4. Functional verification
{code:bash}
{reviewer-run command + fresh output — not copy-pasted from implementer}
{code}
(/) {scenario re-run, succeeds}
(/) Pipeline {N} for {tag}: success
(/) Tag {v} exists on merge {sha}
h4. Inventory / linked artefacts
(/) {inventory ticket}: updated to {new value}
(!) {discrepancy}
h4. Guardrails
(/) G1: adjacent components {A}, {B} spot-checked, unchanged
(-) G2: n/a — not a shared-layer change
(/) G3: default-path of {flag/config} still behaves as before
h4. Documentation
(!) {README / meta / runbook discrepancy}
(i) {improvement hint}
h4. Rollback / backout
(/) Snapshot taken before change: {evidence}
(/) Backout path documented: {how}
h4. Communication
(-) n/a — internal-only change | (/) Announced in {channel} | (!) Customer-affecting; no announcement found
h4. Process compliance
(/) Comments in {code} blocks throughout
(/) Inventory updated immediately
(i) {minor compliance hint}
h4. Verdict
(/) All must-have checks pass.
(!) N should-fix items: {brief list}
(i) M hints for next time: {brief list}
Ready to {transition-name-from-your-system}.The verdict line must match your ticket system's actual transition name and the routing rule. For example, in a system where "QA passed" transitions to a customer-acceptance status, do not write "Ready to transition to QA passed" if the verdict is "internal-resolve" — that's a contradiction. Use the literal transition name that matches the routing decision (e.g. "Resolve" for internal-resolve, "QA passed" only when the next stop really is customer acceptance).
Keep it tight. Skip pillars that don't apply (e.g. omit "Communication" if (-) n/a). Don't pad with (/) for every check — list (/) items only when they're load-bearing or non-obvious.
Sanity scan before posting
Re-read your own comment before clicking Add. Common self-introduced bugs:
1. Severity inconsistency — declaring "all must-haves pass" while a (x) is present elsewhere. Scan for (x) first; if any, the verdict must be Bounce or Won't-do. 2. Reviewer-side limitations marked `(x)` — "I couldn't SSH" is (!) or (i), not (x). See severity.md. 3. F7 violations in your own comment — display-text links where convention is full URLs, {{monospace}} for commands when convention is {code} blocks, Markdown leakage (**bold**, # heading). 4. Transition-name vs verdict mismatch — the verdict's meaning and the literal transition name must agree (see above). 5. Pillar P claims vs actual content — if your P-pillar says "comments use {code} blocks" while your comment uses {{monospace}} for commands, that's the very contradiction the runbook is meant to prevent. 6. Link audit on your own references — every issue key, MR/PR, commit, or external URL you introduce must also exist as a structural link (issue link or web link), not just inline. See F4a (reviewer-side). Anti-pattern: "filed as NEW-TICKET" without the link.
Example 1 — Pass (NRS-4365 shape)
h3. IT Internal QA — passed
h4. Formal correctness
(/) Description has clear acceptance criteria (4 numbered tasks)
(/) Implementer comments document each step with command+output
(/) Linked: NRS-4317 (parent), MR !9 (role), MR !315 (consumer)
(!) No CHANGELOG entry for v1.7.7 — minor, team-wide pattern
(i) No worklog logged
h4. Functional verification
{code:bash}
ssh exocortex.nr 'systemctl is-active vault && vault status | head -3'
active
Sealed false
Version 2.0.0
{code}
(/) Vault active and unsealed on exocortex.nr (re-checked just now)
(/) Pipeline 204129 for v1.7.7: success
(/) Tag v1.7.7 exists on merge 95f7770
h4. Inventory / linked artefacts
(/) IOT-146: nothing to update (ticket is about ansible role, not host inventory)
(!) meta/main.yml platforms still lists only bookworm — should add trixie
h4. Guardrails
(/) G1: vault role's two adjacent callers (consul, exocortex bootstrap) spot-checked, unchanged
(-) G2: n/a — not a shared-layer change
(-) G3: n/a — no config defaults touched
h4. Documentation
(!) README "Currently supported platforms" still says Debian 12 only
(i) Adding a debian13 scenario to molecule would have caught this in CI
h4. Rollback / backout
(/) Single-line template change; rollback = revert tag bump in requirements.yml + re-run ansible. Documented implicitly via git history.
h4. Communication
(-) n/a — internal-only change to internal infrastructure
h4. Process compliance
(/) Comments in {code} blocks throughout
(i) No tmux session attached — single-host single-template change, below the threshold
h4. Verdict
(/) All must-have checks pass.
(!) 2 should-fix items: README + meta/main.yml platform metadata.
(i) 2 hints for next time: molecule debian13 scenario, CHANGELOG entry.
Ready to transition to QA passed.Example 2 — Bounce
h3. IT Internal QA — failed
h4. Formal correctness
(x) F3: No console output for the verification step. Implementer comment claims "tested locally, works" but no command/output captured.
(/) Description has clear acceptance criteria
h4. Functional verification
(x) Re-running the verification command on staging fails:
{code:bash}
$ <command>
<error output>
{code}
The fix is not idempotent — second run errors on existing resource.
h4. Verdict
(x) 2 blocking issues. Bouncing to In Progress.
Please:
1. Capture command+output for each verification step (F3).
2. Make the playbook idempotent — second run should be no-op (R3).Example 3 — Won't-do (NRT-4567 shape)
h3. IT Internal QA — won't do (blocking external prerequisite)
h4. Why
(x) Container image for 1.2.2 does not exist on ghcr.io.
{code}
docker manifest inspect ghcr.io/netresearch/<component>:1.2.2
no such manifest
{code}
GitHub releases v1.2.1 and v1.2.2 are tagged but no Docker images were published. Only 1.2.0 and latest are on ghcr.io.
h4. Reopen condition
The CI for https://github.com/netresearch/<component> doesn't publish Docker images on release. Reopen this ticket once the GitHub Actions release workflow is fixed and v1.2.2 (or later) images are available.
h4. Follow-up filed
{NEW-TICKET}: fix GitHub Actions release workflow for <component> to publish Docker images.
Resolving as Won't-do.Two-comment pattern for long reviews
The structured QA comment is the audit trail — complete, traceable, written for future readers. But it's also long. The implementer who just finished the work often needs one thing: "do I need to do anything?"
For reviews with action items (bounce, won't-do with reopen condition, pass with structural follow-ups), post a second comment immediately after the main one — a short TL;DR addressed to the implementer.
When to do this:
| Verdict | TL;DR comment? |
|---|---|
| Pass — clean, no follow-ups | Skip — main comment is enough |
| Pass — with structural follow-ups or backfill asks | Post TL;DR |
Pass — with (!) items the implementer should know about | Post TL;DR |
| Bounce | Always post TL;DR — the implementer needs to know what to fix |
| Won't-do | Always post TL;DR — reopen condition + follow-up ticket |
TL;DR template
h3. TL;DR for [~implementer.username] — what needs doing now
Verdict: *{passed | failed | won't do}*. {one-line status of the ticket itself}
# *{action 1}* — {short rationale, link to follow-up ticket if any}
# *{action 2}* — {short rationale}
{Optional: "Nothing here blocks anything. Detail in the full QA review above."
OR: "Bouncing — fix #1 and re-transition. Detail above."}TL;DR principles
- Mention the implementer with
[~username]so they get a notification. - Lead with the verdict in one sentence — they want to know if they need to act before reading anything else.
- Numbered list of concrete actions only — no severity icons, no pillar references, no quoted findings. Each item should be doable in ≤ 1 sentence.
- Cross-reference the audit-trail comment at the end ("Detail above") rather than repeating the analysis.
- Skip if there's no action. A clean pass with no follow-ups doesn't need a TL;DR — that just adds noise.
Why two comments and not one
It's tempting to put the TL;DR at the top of the structured comment instead. Don't — for two reasons:
1. The main comment is the audit trail. Future readers (next sprint planning, postmortem, similar incident) read it for the analysis, not the actions. A TL;DR on top buries the audit-trail header (h3. IT Internal QA — passed). 2. Jira notifications quote the first lines of a comment in email/Matrix previews. A separate TL;DR comment makes the action items the preview, which is what the implementer needs.
Edge Cases
Decisions that don't fit cleanly in the checklist.
A. The implementer's evidence is unverifiable
Production-only verification, destructive test, only observable during a maintenance window that has now passed. The reviewer cannot re-run R1 / R2.
Decision: Treat the implementer's evidence as primary. Document explicitly in the QA comment that re-execution was not possible and why. Do not silently accept — the explicit note matters for audit.
h4. Functional verification
(i) Re-execution not possible: the destructive test (data-migration dry-run) has already consumed its input batch.
(/) Implementer's captured output reviewed: the migration completed cleanly, row counts match.B. Failed prerequisites discovered in QA (NRT-4567 pattern)
The component being updated doesn't exist upstream / can't be deployed / depends on a fix elsewhere.
Verdict: Won't-do (not Bounce). The implementer didn't do anything wrong — the world isn't ready for the change.
QA comment must include:
1. Evidence the prerequisite is missing (404 from registry, missing tag, broken pipeline). 2. Pointer to where the prerequisite must be fixed (which repo / which CI job / which ticket). 3. Reopen condition — concrete signal that says "now this ticket can be retried." 4. Optional but recommended: file the follow-up ticket and link it.
C. Sibling-ticket pattern deviation
When parallel tickets share a runbook (multiple host upgrades, traefik updates across a fleet, room-by-room migrations), spot-check that this ticket's evidence pattern matches its siblings.
Decision: If this ticket's evidence is thinner than its siblings without explanation, (!) and ask why. If it's more thorough, (/) — that's just better work. Pattern deviations are the thing to flag, not symmetry.
D. QA2 vs internal-resolve routing
After QA passes, where does the ticket go next?
| Scope | Route to |
|---|---|
| Internal infrastructure (server playbooks, internal tooling, CI components, internal monitoring, dev environments) | Internal-resolve (Closed/Done) |
| Customer-affecting (a customer's hosted instance, customer's domain, hosted services for clients, user accounts, customer-visible config) | QA2 |
| Mixed (touches customer-relevant config but the change is invisible to the customer) | QA2 — err on the side of more eyes |
When uncertain, default to QA2 and let the product owner / customer-success rep approve.
E. Reviewer is also implementer
You cannot review your own work. The first-person bias is too strong; even with discipline, you'll miss things.
Decision: Hand off to another teammate. If genuinely no other reviewer is available right now:
1. Document the constraint in the QA comment explicitly: "Self-review by implementer due to no available reviewer at <time>. Requesting asynchronous sanity check from <colleague> when available." 2. Flag for colleague: post a follow-up comment / ping in the team channel asking for a real second pair of eyes. 3. Do not transition to "QA passed" until at least one other set of eyes has acknowledged the work — even if it's just a (/) from a colleague after the fact.
F. The ticket should not have been put in QA
Common signals: implementation was clearly aborted ("WIP, will continue Monday"), required pre-work isn't done (a dependency ticket is still open), the implementer ticks "ready for QA" while admitting in the same comment that something is missing.
Decision: Bounce immediately, no full QA pass needed. Reason in the QA comment:
h3. Bouncing to In Progress
(x) Pre-conditions for QA aren't met yet:
- {specific signal, e.g. "dependency ticket NRS-1234 is still in progress"}
- {or: "comment from <date> says 'still need to test on staging'"}
Re-transition once {specific condition} holds.G. Comment thread is too tangled to summarise
Long discussion threads, multiple back-and-forth corrections, side-quests interleaved with main work. Hard to tell what the final state is.
Decision: Ask the implementer to post a clean summary comment first ("ready for QA — final state: X, Y, Z"). Then QA against that summary, not against the whole thread.
H. The verdict is "Pass but I'd like an addendum"
When the work passes but you want to flag something separable — a Confluence-runbook update, a side-quest the implementer did along the way, a sibling-ticket inconsistency, a process-improvement suggestion.
Decision: Keep the main verdict comment self-contained ("Ready to transition to QA passed"). Post a separate addendum comment with h3. heading like "QA addendum: Confluence docs" or "QA addendum: side-quests". This keeps the audit trail clean and lets the team find the addendum later by topic.
I. The audit trail of console output is missing for a key action
Implementer ran something irreversible (data migration, prod deploy, account deletion) but didn't capture the output — only summarised in prose.
Decision: Don't bounce automatically — sometimes the action genuinely happened cleanly and a re-run would prove it. Re-run if non-destructive; for genuinely destructive actions, ask the implementer to provide whatever record they do have (terminal scrollback, dashboard screenshot, system logs from the time window).
If nothing exists at all → (!) for this ticket plus a process-compliance note in the QA comment, but pass if the action's effects are independently verifiable now.
If nothing exists and the effects can't be independently verified → (x) and bounce. We don't accept "trust me" for irreversible production changes.
Framework Alignment
This runbook is pragmatic, not framework-compliant. It's shaped by what works in practice (empirical NR observations across NRS / NRT / SRV* projects) rather than by adherence to any one external standard. That said, several established frameworks describe overlapping concerns, and naming the alignment helps with credibility and onboarding.
ITIL — Change Enablement / Post-Implementation Review
ITIL's Post-Implementation Review (PIR) is the closest-named external practice: a review of a completed change against its objectives, conducted after the change has been implemented but before it's marked closed.
| ITIL PIR concern | This skill |
|---|---|
| Was the change implemented as planned? | Pillar F (formal correctness against acceptance criteria) |
| Did the change achieve its objectives? | Pillar R (functional resolution, reviewer re-run) |
| Were there unexpected side-effects? | R4 (no collateral damage) |
| Backout / rollback verified? | Pillar B |
| Configuration / inventory records updated? | Pillar I |
| Stakeholders informed? | Pillar C (communication) |
*What this skill does not import from ITIL*: full Change Advisory Board (CAB) approval workflows, formal Change Manager role, change-window governance. Those are too heavyweight for Round-1 IT QA. If your team uses CAB, that's a separate process upstream of implementation, not Round-1 review.
Scrum / Agile — Definition of Done
Scrum's Definition of Done (DoD) is a team-agreed checklist for "this work is complete." This skill's Pillar F maps to DoD-style "code complete + reviewed + tested + documented" checks.
Difference: DoD is the implementer's self-check; Round-1 QA is the peer's verification of the same. Both should agree, but they're applied by different people at different times.
Google SRE — Postmortems
Round-1 QA is not a postmortem (no incident occurred), but the postmortem culture's emphasis on blameless tone, evidence-based assertions, and action items rather than recriminations translates well to QA comments.
| Postmortem pattern | Applied here |
|---|---|
| Blameless framing | "this fix doesn't exercise scenario X" not "you forgot scenario X" |
| Evidence > assertion | re-run command + paste output, don't just say "I checked" |
| Action items, not blame | each (x) and (!) says what to do, not just what's wrong |
NIST SP 800-128 — Secure Configuration Management
NIST 800-128 §3.5 ("Configuration Monitoring") describes change verification — confirming that an implemented change has the expected effects and no unexpected ones. Pillars R and B in this skill cover the same ground for security-relevant changes specifically. The skill doesn't claim compliance with 800-128, but its R/B pillars are consistent with the §3.5 checklist.
What this skill is not aligned with
- CMMI / formal change-control gates — too heavyweight for Round-1.
- ISTQB test-design framework (boundary value, equivalence partitioning, etc.) — that's test design by the implementer, not peer review by the reviewer.
- Six Sigma / DMAIC — different scope (process improvement, not per-ticket review).
When to reach beyond this skill
If your organisation has a formal change management process (CAB, change windows, security board sign-off), this skill complements it rather than replaces it. Round-1 IT QA is the team-internal, peer-level gate. Higher-tier governance, when present, applies on top.
Lifecycle
The 6-stage Round-1 IT QA lifecycle. Stages are sequential — do not skip or reorder them.
Stage -1: Claim the ticket
The QA queue is normally a team queue with no assignee. Reviewer self-assigns and continues.
| Current assignee | Action |
|---|---|
| Unassigned | Claim (assign to me) and continue |
| Someone else | Stop — in flight by another reviewer |
| Me, from a prior implementation phase | Stop — cannot self-QA. Hand off to another teammate. See edge-cases.md. |
| Me, from a prior abandoned QA attempt | Continue |
Stage 0: Discover
One call to gather all reviewer-relevant context. With jira-communication installed:
${CLAUDE_SKILL_DIR}/scripts/qa-gather.sh <ISSUE-KEY>Returns: issue + description + comments + worklog + issue links + remote/web links + URLs extracted from description and comments (merge_request, pull_request, pipeline, commit, tag, release, issue_link) + sibling tickets in the same project (60-day window, summary-token overlap).
If your team has internal skills for the ticket system / inventory / runbook, they may chain in additional context — consult them.
Stage 1: Formal correctness
Verify the ticket itself is reviewable. See checklist.md items F1–F8.
Output: a list of (/) (x) (!) (i) items. If F1 (acceptance criteria absent) is (x), you cannot proceed — bounce immediately.
Stage 2: Functional resolution + Inventory + Guardrails
Verify the reported problem is fixed. The reviewer re-executes the implementer's verification commands themselves — do not copy-paste the implementer's output. Include fresh {code} blocks in the QA comment.
Then verify the change didn't break what the ticket didn't mention: adjacent components, shared-layer downstream consumers, and the unchanged default path of any flag/config touched.
See checklist.md items R1–R6 (functional), I1–I4 (inventory), and G1–G3 (guardrails).
For maintenance-style tickets, also run any component-specific QA scripts (e.g. a gitlab-qa.py style check) if your team's maintenance skill provides one.
Stage 3: Documentation + Rollback + Communication
Three smaller pillars combined into one stage:
- Documentation (D1–D4): README / role-meta matches reality, runbook reviewed for staleness, CHANGELOG entry where applicable.
- Rollback / backout (B1–B3): snapshot or backup taken before risky change, backout path documented and reachable.
- Communication (C1–C3): for customer-affecting / org-wide / security-relevant changes — announcement posted (Matrix / Slack / email / blog).
C1–C3 are conditional: many internal tickets need no announcement. The check is whether one is needed and missing, not whether one is universally present.
Stage 4: Verdict
Pick one outcome:
| Outcome | When |
|---|---|
| Pass — resolve | All (x) clear, IT-internal scope |
| Pass — QA2 | All (x) clear, customer-affecting |
| Bounce — In Progress | Any (x) |
| Won't-do | Blocking external prerequisite missing |
Decision rules: edge-cases.md.
Stage 5: Comment + Transition
One structured comment per template (comment-template.md), then transition the ticket:
| Outcome | Transition |
|---|---|
| Pass — resolve | QA → Closed/Done |
| Pass — QA2 | QA → QA2 (or equivalent customer-acceptance status) |
| Bounce | QA → In Progress, re-assign to implementer |
| Won't-do | QA → Closed with resolution "Won't Do" + reopen condition |
Optional addendum comments are fine for separable concerns (e.g. a separate Confluence-runbook review). Keep the main verdict comment self-contained.
Severity Vocabulary
The icon set
Reuse the standard Atlassian / Jira-wiki icon set. Do not invent new categories — reviewers and implementers should not have to learn fresh vocabulary.
| Icon | Wiki source | Meaning | Action |
|---|---|---|---|
(/) | (/) | verified / passed | none |
(x) | (x) | MUST: blocking — ticket cannot resolve until fixed | bounce to In Progress |
(!) | (!) | SHOULD: real issue, non-blocking for this ticket | document; create follow-up if structural |
(i) | (i) | HINT: improvement suggestion / next-time nice-to-have | document; no action required |
(?) | (?) | open question for implementer | block on answer |
(-) | literal text n/a | not applicable here | none — explicit "we considered this and it doesn't apply" |
In rendered Jira these become coloured icons. In other systems (GitHub, GitLab, Markdown), use the literal strings — they read clearly even unrendered.
Watch out for `(-)` specifically: in Jira wiki the (-) token renders as a forbidden / no-entry icon (🚫), not as "n/a". The forbidden icon visually says "blocked / denied", which is wrong for "not applicable here". Use literal text like *n/a* or write "n/a — reason" instead of the (-) token in Jira comments. The other tokens ((/) (x) (!) (i) (?)) render with semantically-correct icons.
Also avoid using (/), (x), (!), (i), (?) literally in prose when you mean to refer to a finding by its severity. Jira will render the icon mid-sentence, which is visually confusing. Write "the SHOULD-fix finding" or "the warning above" instead of "the (!)".
How to choose
Ask three questions in order:
1. Does the ticket fail its acceptance criteria, or actively break something? → (x) 2. Is it a real issue that should be addressed, but doesn't block this ticket's purpose? → (!) 3. Is it just a "next time, consider…" suggestion? → (i)
If you can't decide between (!) and (i): ask whether the issue would still be worth fixing if the ticket were already closed. Yes → (!) (file a follow-up). No → (i).
Examples from real tickets
(x) — blocking
(x) container image for 1.2.2 does not exist on ghcr.io. Cannot deploy without a published image.
— NRT-4567, resolved as Won't-do
(x) F1: Description has no acceptance criteria. Cannot QA — bouncing to In Progress for clarification.
(!) — should-fix
(!) molecule.yml references requirements.yml but file doesn't exist. Generates warning. Recommend either creating an empty requirements.yml or removing the dependency block.
— NRS-4199 QA review (Björn Marten)
(!) :latest image tags in CI — components are pinned (@v0.2.0) but Docker images are :latest. A breaking rebuild upstream would silently propagate.
— NRS-4356 QA review (Sebastian Mendel)
(i) — hint
(i) timezone test missing `when: setup_time` guard. Other feature tests use this pattern — should be consistent.
(i) same cgroup v1 legacy warning as IOT-71 — not blocking, worker runs fine after 30s v2 fallback.
— NRS-4240 QA review (Björn Marten)
(?) — open question
(?) Was the OPNsense config snapshot taken before the major upgrade? I see the patch-update comment but not a snapshot mention.
(-) — n/a
(-) Communication (C1–C3): change is internal-only (CI image refresh), no announcement needed.
Anti-patterns in severity choice
- Inflating `(x)` to look thorough — only use
(x)when the ticket genuinely cannot resolve. Over-bouncing wastes everyone's time. - Using `(x)` for reviewer-side limitations — "I couldn't SSH to the host" or "I don't have access to the staging environment" is not a blocker on the ticket. The work might be fine; the reviewer's verification is just incomplete. Per
edge-cases.mdA, document explicitly with(!)(limitation worth noting) or(i)(limitation, but implementer's evidence is otherwise solid). Reserve(x)for things wrong with the implementation, not the review. - Downgrading `(x)` to `(!)` to be polite — if a fix doesn't actually fix the bug, it's
(x). Politeness is in tone, not in severity. - Using `(!)` for things that aren't issues — "I would have done this differently" is
(i)(or no comment at all), not(!). - Using `(?)` as a hidden `(x)` — if you'd bounce regardless of the answer, just bounce.
(?)is for genuinely unknown. - Internal inconsistency — declaring "all must-haves pass" while still having a
(x)somewhere in the comment. If you have a(x), the verdict is bounce or won't-do, period. Re-read your own comment before posting.
Mapping to verdict
- Any
(x)→ Bounce to In Progress (or Won't-do if the blocker is external). - All
(x)clear, some(!)/(i)→ Pass (resolve or QA2). - All clear → Pass (resolve or QA2).
(!) and (i) items are documented in the QA comment but do not block the verdict. If a (!) is structural, file a follow-up ticket.
#!/usr/bin/env bash
# Stage 0 single-call discovery for peer-qa-review.
#
# Delegates to the jira-communication skill's qa-gather.py if available
# (locates it via CLAUDE_PLUGIN_ROOT, $HOME/.claude/plugins, or PATH).
#
# Falls back to a multi-call sequence using core jira-communication scripts
# if qa-gather.py is not yet installed (older skill version).
#
# Usage:
# qa-gather.sh <ISSUE-KEY> [--json]
#
# Exits with the underlying script's status. Prints a friendly hint to
# stderr if neither qa-gather.py nor the fallback scripts can be found.
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "usage: qa-gather.sh <ISSUE-KEY> [--json|--no-siblings|--max-siblings N|...]" >&2
exit 64
fi
ISSUE_KEY="$1"
shift
EXTRA_ARGS=("$@")
# Preferred: qa-gather.py from jira-communication skill.
find_qa_gather() {
local search_paths=(
"${CLAUDE_PLUGIN_ROOT:-}"
"${HOME}/.claude/plugins/cache/netresearch-claude-code-marketplace/jira-integration"
"${HOME}/.claude/plugins/cache"
)
local p
for p in "${search_paths[@]}"; do
[[ -z "$p" ]] && continue
local found
found=$(find "$p" -maxdepth 6 -path '*/skills/jira-communication/scripts/utility/qa-gather.py' 2>/dev/null | head -n1)
if [[ -n "$found" ]]; then
echo "$found"
return 0
fi
done
return 1
}
# Fallback: best-effort using core scripts. Same skill base, older version.
find_jira_scripts_dir() {
local search_paths=(
"${CLAUDE_PLUGIN_ROOT:-}"
"${HOME}/.claude/plugins/cache/netresearch-claude-code-marketplace/jira-integration"
"${HOME}/.claude/plugins/cache"
)
local p
for p in "${search_paths[@]}"; do
[[ -z "$p" ]] && continue
local found
found=$(find "$p" -maxdepth 6 -path '*/skills/jira-communication/scripts/core/jira-issue.py' 2>/dev/null | head -n1)
if [[ -n "$found" ]]; then
dirname "$(dirname "$found")"
return 0
fi
done
return 1
}
if QA_GATHER_PATH=$(find_qa_gather); then
exec uv run "$QA_GATHER_PATH" "$ISSUE_KEY" "${EXTRA_ARGS[@]}"
fi
# Fallback path
if SCRIPTS_DIR=$(find_jira_scripts_dir); then
echo "[qa-gather] qa-gather.py not found; falling back to multi-call discovery" >&2
echo "=== ISSUE ===" && uv run "${SCRIPTS_DIR}/core/jira-issue.py" get "$ISSUE_KEY"
echo "=== COMMENTS ===" && uv run "${SCRIPTS_DIR}/workflow/jira-comment.py" list "$ISSUE_KEY"
echo "=== WORKLOG ===" && uv run "${SCRIPTS_DIR}/core/jira-worklog.py" list "$ISSUE_KEY" 2>/dev/null || true
exit 0
fi
cat >&2 <<'EOF'
[qa-gather] Could not find the jira-communication skill.
Install it from:
https://github.com/netresearch/jira-skill
Or if it's installed in a non-standard location, set:
export CLAUDE_PLUGIN_ROOT=/path/to/your/claude/plugins/cache
EOF
exit 1