bugfix
Skill0 starsChanged 5 days agoMITView on GitHub
End-to-end bug triage from a Jira ticket. Invoke as "/wk:bugfix <jira-url-or-key>" (e.g. /wk:bugfix PDE-2779 or a full browse URL). Pulls the ticket (title, description, comments), downloads every Charles session (.chls) attached or mentioned in text, converts them to .har, distills the signals, digests the evidence, asks for entry points, then investigates in plan mode (read-only, code correlation and verdict only) to reach a verdict. A real app bug gets planned, fixed, and verified on device with /wk:replay (if the scenario needs a forced response or flag to reproduce) and /wk:protologs; a bug that is not the app's fault (API / CMS / backend / data / env) produces a mini-spike ready to post to Jira. Pass --no-pr at start for a branch that merges straight to its base branch (no PR description, merge-and-push closing line). Use /wk:doer for planned feature/refactor tickets instead.
- Deletes or force-pushes
34 other projects use a version of this.
Adopted by
Most recently taken up by
- isanchez-aguilar/relayrace-aitheir own version, first seen Nov 8, 2026
- ClaireAll/skillstheir own version, first seen Nov 5, 2026
- bhoon716/skill-forgetheir own version, first seen Nov 2, 2026
- Snowflake-Labs/coco-skillstheir own version, first seen Nov 1, 2026
- irahardianto/awesome-agvtheir own version, first seen Nov 1, 2026
- mustafarslan/skillstheir own version, first seen Nov 1, 2026
Used with
Projects with it also use feature, refactor and 9 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
bradtraversySkillfeature
Manage current feature workflow - start, review, explain or complete
Also in 7 projects that use this
GitHubSkillrefactor
Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.
Also in 6 projects that use this
SentrySkillspec
Produce a verified spec — problem, desired outcome, and acceptance criteria — before building. Use when given a GitHub/Linear issue link, when starting from a fuzzy in-conversation idea, or asked to "figure out what to do" / "scope this" / "what should I build" — whenever the *what* isn't yet pinned down. Grills the ambiguities, checks the premise against the code, and produces acceptance criteria you sign off on — then hands to design-first.
Also in 4 projects that use this
SentrySkillcommit
Create commit messages following Sentry conventions. Use when committing code changes, writing commit messages, or formatting git history. Follows conventional commits with Sentry-specific issue references.
Also in 8 projects that use this
BlockSkillplan
Draft a TDD, PRD, or lightweight plan in the conversation without committing it.
Also in 4 projects that use this
Microsoft AzureSkilldocs
Also in 3 projects that use this
- +1 more from getsentry
Other versions34 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 34
- alibaba/MNN★ 16ksame name
- SparkDevNetwork/Rock★ 701same name
- oasm-platform/open-asm★ 272same name
- atelier-fashion/adlc-toolkit★ 173same name
- team-attention/hoyeon★ 173same name
- irahardianto/awesome-agv★ 157same name
- Snowflake-Labs/coco-skills★ 49same name
- mrveiss/AutoBot-AI★ 39same name
- dan-the-dev/xp-developer-skills★ 31same name
- ecodelearn/template-claude-code-open★ 25same name
24 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
We split this skill into the rules it gives an agent
12 rules. 0 are common, 3 are distinctive, and 9 are very specific to this project.
0
Common
Most of the 31 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 91
Verify Jira access: "${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh" config.
- Very specific: line 96
Write ticket.md in .doer/tickets/<KEY>/: summary, type/status/priority, description verbatim, every comment (author · date · body).
- Very specific: line 136
No hits → fall back to the current behavior: offer the area inferred from signals/evidence and "search the codebase yourself".
- Very specific: line 142
Stored entry exists and the dev accepted its paths as-is: ask nothing. Just entrypoints.sh save --topic <t> --paths <same paths> --from …
- Distinctive: line 170
Commit the fix, separate from the tests commit:
- Very specific: line 183
Read templates/mini-spike.md (this skill's directory) and write ~/Downloads/<KEY>/spike.md in Jira wiki markup, filled from signals, …
- Very specific: line 185
Entry-points refinement (2c): If entry_points_topic is set and the root cause in plan.root_cause names a file not already in that topic's …
- Distinctive: line 186
When satisfied, ask whether to post it as a Jira comment.
- Very specific: line 200
Entry-points refinement (2c): If entry_points_topic is set and plan.root_cause names a file not already in that topic's stored paths, ask …
- Very specific: line 216
No-PR: When no_pr in bugfix.json is true, do not detect templates, dispatch the writer or ask for approval: narrate one line, "PR …
- Very specific: line 229
Keep bugfix.json lean at all times.
- Distinctive: line 231
Every external or irreversible action (posting to Jira) requires an explicit user yes.
“Common” is measured: kept by at least 6 in 10 of the 31 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
/wk:bugfix - bug triage pipeline
A deterministic pipeline: Jira ticket → ordered context → investigation (plan mode) → verdict → fix-or-spike.
- Best run in opusplan. Stages 0-3 and 5-6 are mechanical. Stage 4 uses
EnterPlanModeso the investigation runs on the strongest model; execution drops back onExitPlanMode. The skill cannot force the session model; at the top of Stage 4 it reminds the user. Everything that needsBash(downloads,.chlsconversion, HAR parsing) runs before Stage 4, in Stages 0-3, since plan mode does not inherit the session's own permission mode: aBashcall insideEnterPlanModeprompts for approval on every single invocation, turning the investigation into a click-through instead of a one-shot. Stage 4 only ever usesRead/Grep/Globon the already-gathered evidence and the codebase. - Single source of truth: one lean
bugfix.jsonper ticket. Heavy raw data (full description, comments, HAR bodies) lives in files on disk and is read on demand only, never inlined into the JSON. - Jira access goes through
"${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh", configured per-project at./.doer/config.json(git-excluded), via/wk:setupor/wk:jira <url>. The token is full-env:$JIRA_PATby default, or whateverjira_token_envnames; never persisted. Ifjira.sh configreports missingbase_urlortoken_present: false, run the auto-detect pass (candidate env var NAMEs only, never values; see/wk:setup'sSKILL.md) before giving up and pointing the user at/wk:setup.
Storage layout (hybrid)
./.doer/tickets/<KEY>/ # control state (Workspace Guard keeps it out of git)
├── bugfix.json # LEAN control file: state + signals + evidence + plan
├── ticket.md # raw description + comments (read on demand only)
└── lock.json
~/Downloads/<KEY>/ # heavy artifacts, easy to inspect manually
├── charles/ # <name>.chls + <name>.har
├── screenshots/ # <name>.png
└── spike.md # only when verdict = not_app_bugbugfix.json schema
{
"ticket_id": "PDE-2779",
"title": "<summary>",
"url": "<source_url from jira.sh>",
"status": "in_progress",
"current_stage": 0,
"verdict": null,
"artifacts_dir": "/Users/<user>/Downloads/PDE-2779",
"signals": {
"repro": ["1- ...", "2- ..."],
"expected": "...", "actual": "...", "env": "qa4",
"related_tickets": ["PDE-2680"],
"technical": ["BO-6x3Srun...", "isP13NEnabled"]
},
"attachments": [
{"filename": "x.chls", "kind": "charles", "source": "attachment", "jira_url": "...",
"path": "charles/x.chls", "har": "charles/x.har", "done": false, "converted": false}
],
"evidence": [],
"entry_points": [],
"entry_points_topic": null,
"plan": null,
"replay": null,
"no_pr": false,
"commit_message": null, "pr_description": null,
"stages": {"0": "pending", "1": "pending", "2": "pending", "3": "pending", "4": "pending", "5": "pending", "6": "pending"},
"notes": []
}entry_points[] can arrive pre-populated from ./.doer/entry-points.json (per-repo store, see lib/state.md and Stage 3 Step 2 below), not just from the dev typing them fresh. entry_points_topic holds the matched/chosen topic key from that store when applicable (null when entry_points is "search" or came from an ad hoc answer with no stored topic); Stage 5/6 close uses it to offer refining the stored entry.
replay is informational only: null when Stage 6 never invoked /wk:replay, otherwise a short record of what was forced ({"technique": "network | flags | both", "summary": "<one line>"}). /wk:replay itself never reads or writes this field; it anchors on the repo's own git history (PRE_REPLAY_SHA), not on bugfix.json, precisely so it keeps working standalone with no ticket at all.
Rule: after each stage, update bugfix.json in ONE "${CLAUDE_PLUGIN_ROOT}/lib/helpers/metadata.sh" write "<KEY>" '<filter>' --file bugfix.json call (current_stage, stages[n]="complete", the fields that stage owns, all in the same jq filter). Never Write/Edit bugfix.json directly, and never split one stage's close into two writes: see lib/state.md, "Writing metadata.json" (applies equally to bugfix.json), for why. Keep it lean; raw text goes to ticket.md, network dumps stay as .har.
Stage 0 - Init & Resume
- Parse the ticket KEY from the argument (full URL or bare key; regex
[A-Z]+-\d+). Strip an optional--no-prfirst (it is not part of the key) and hold whether it was given. - If
./.doer/tickets/<KEY>/bugfix.jsonexists → resume: a--no-prtyped here is ignored with a one-line narration (the mode is fixed at creation and lives inno_pr; a file without the field is the PR flow). Read it, announcecurrent_stage, jump to the next incomplete stage. Never redo work flaggeddone. First register this session for cost:"${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" enter "<KEY>" --file bugfix.json(writes only when the session is not yet incost_sessions). - Otherwise run
"${CLAUDE_PLUGIN_ROOT}/lib/helpers/workspace-guard.sh" acquire "<KEY>" bugfix(contract:lib/workspace-guard.md), then create the folders and the initialbugfix.json(status=in_progress,current_stage=0, all stages pending,artifacts_dirset,no_prtrue only when--no-prwas given, else false).cost.sh enterruns beforeinitand leavescost-entry.jsonfor it;--track-costaddscost_sessionswith the current session:mkdir -p "$HOME/Downloads/<KEY>/charles" "$HOME/Downloads/<KEY>/screenshots" ".doer/tickets/<KEY>" "${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" enter "<KEY>" echo '<full JSON document>' | "${CLAUDE_PLUGIN_ROOT}/lib/helpers/metadata.sh" init "<KEY>" --file bugfix.json --track-cost - Verify Jira access:
"${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh" config. On missingbase_urlor absent token, run the auto-detect pass (jira.sh detect-token-envfor candidate env var NAMEs, plus project memory, never values); if nothing resolves, stop and point the user at/wk:setup.
Stage 1 - Ingest Jira (informational, no pause)
- Fetch:
jira.sh fetch <KEY>→ lean JSON (title, status, description, comments, attachments, source_url). - Write
ticket.mdin.doer/tickets/<KEY>/: summary, type/status/priority, description verbatim, every comment (author · date · body). The ONLY place raw text lives. - Distill into
signals{}:repro(numbered steps),expected,actual,env,technical(endpoint ids, context ids, flag/function names) stay judgment calls;related_ticketsis mechanical,jira.sh extract-keys ticket.md --exclude <KEY>(unique[A-Z]+-[0-9]+mentions, excluding the ticket's own key). - Build
attachments[]:jira.sh attachments <fetch.json> ticket.mdreturns the array ready to persist (kind by extension, every.chlsmentioned in text cross-referenced against the fetched attachments, ASCII-safe dedupedpath, originalfilenamepreserved), no further transformation needed. - Recap one line (title, attachment count, Charles sessions detected) and continue. Mark stage 1 complete.
Stage 2 - Download & Convert
Skip with a note if attachments[] has no .chls and no screenshots (stages.2 = "complete", note "no attachments").
- Download every attachment with a non-null
jira_urlviajira.sh download <jira_url> "<artifacts_dir>/<path>"(pathis already ASCII-safe and deduped, computed byattachments[]in Stage 1 Step 4;filenamestays the original, unsanitized name for display). A.chlsmentioned in text with no matching attachment (jira_url: null) cannot be downloaded; log it innotesand continue. - Convert each
.chls→.har, auto-detecting the converter:"${CLAUDE_PLUGIN_ROOT}/lib/helpers/har.py" convert "<in.chls>" "<out.har>"NO_CONVERTERon stderr (neithermakeharnor Charles.app found) → note it, keep the.chlsfor manual conversion, continue (the investigation can still work from ticket text + screenshots). - Set
done/convertedper attachment, andharto the converted file'spath(extension swapped to.har) whenever conversion succeeded; a failed or skipped conversion leaveshar: nullandconverted: false. Mark stage 2 complete.
Stage 3 - Evidence Digest & Entry Points
Runs entirely outside plan mode, since it is the stage that does the actual Bash-heavy parsing.
HAR evidence digest (skip with a note if
attachments[]has no converted.harand no screenshots;evidencestays[]). For each converted.har, extract ONLY the requests that matter, filter bysignals.technical(endpoint ids, BO/context ids, flag names). Never read a whole HAR into context; they are huge."${CLAUDE_PLUGIN_ROOT}/lib/helpers/har.py" digest "<charles/name.har>" "<term1>" "<term2>"Refine iteratively: grep response bodies for the specific identifiers (e.g. a context id present in one session and absent in another; a flag value; a status code). Distill each finding into a one-line entry in
evidence[]:{"session": "repro", "finding": "context=6x3Srun... never appears as a p13n request (0 calls)"} {"session": "fixed", "finding": "context=6x3Srun... fires x6 (logout+login, en-US/en-CA/fr-CA)"}Prefer a comparative shape when the ticket has a repro vs fixed/working session, the delta between sessions is usually the whole story. Read screenshots only if they add signal the HARs and text don't (UI state, error copy, toggle states); fold anything relevant into
evidence[]the same way.Entry points, in three steps:
2a. Recover before asking. Run
"${CLAUDE_PLUGIN_ROOT}/lib/helpers/entrypoints.sh" match <terms>withsignals.technicalterms plus keywords fromtitle. For each hit, validate every path withtest -f: a path that no longer exists gets narrated plainly ("<path> from the '<topic>' entry point no longer exists, was it moved/renamed?") and the dev is offered to fix or drop it right there, in the same turn. This replaces MAJOR-version pruning (whatwk:doer's lessons pool uses): an entry point does not go stale because the skill got a version bump, it goes stale because the file moved. Surviving paths become the recommended option in theAskUserQuestionbelow, shown with theirtopicandcaptured_fromso the dev knows where they came from.No hits → fall back to the current behavior: offer the area inferred from
signals/evidenceand "search the codebase yourself".2b. Ask. One
AskUserQuestionoffering: the recovered paths (if any, recommended), the inferred area, and "search the codebase yourself". Save the answer intoentry_points[](paths, or the literal"search").Then, only when the answer is real paths (never on
"search"), a second, conditional question about persisting it, one case at a time:- No stored entry for this topic and the dev gave real paths: ask whether to save it as a standing rule for future tickets on
<proposed topic>(options: save / just this ticket; the tool's "Other" is the edit-the-topic path). On save →entrypoints.sh save --topic <t> --paths <p> --keywords <k> --from <KEY>; setentry_points_topicto<t>. - Stored entry exists and the dev accepted its paths as-is: ask nothing. Just
entrypoints.sh save --topic <t> --paths <same paths> --from <KEY>(a no-op merge that appends<KEY>tocaptured_from, confirming the entry without friction) and setentry_points_topicto<t>. - Stored entry exists and the dev gave additional or different paths: ask what to do, showing the current stored count (options: merge the new ones in / replace / leave the stored entry as-is). Apply via
entrypoints.sh save --topic <t> --paths <p> --from <KEY> [--mode replace], or skip the write on "leave as-is"; setentry_points_topicto<t>whenever a write happened.
2c. Refinement hook for later. Stage 5/6 close checks whether Stage 4's root cause lives in a file that was NOT in the
entry_points_topicentry's storedpaths; if so it offers, once, adding it (see Stage 5/6).- No stored entry for this topic and the dev gave real paths: ask whether to save it as a standing rule for future tickets on
- Persist. ONE
metadata.sh writesetsevidence[],entry_points[],entry_points_topic, andstages.3 = "complete"together.
Stage 4 - Investigation, Analysis & Verdict (plan mode)
- Remind: "Stage 4 runs in plan mode; for the deepest analysis, make sure you're on opusplan."
EnterPlanMode, then readanalyze.md(this skill's directory) and follow it: code correlation atentry_pointsusing theevidence[]already gathered in Stage 3, root cause perlib/debugging.md, theapp_bugvsnot_app_bugverdict criteria, and theplan{}shape. Read-only tools only (Read/Grep/Glob); noBash, the evidence is already on disk inbugfix.json.- Present root cause + verdict + plan; on approval
ExitPlanMode, then persist peranalyze.md's Confirm step (one batchedmetadata.sh write:verdict+plan+ stage 4 complete).
Stage 5 - Execute
Branch on verdict:
app_bug
Tests first, TDD order, driven by plan.tests from Stage 4:
- Write every test in
plan.tests.bugandplan.tests.regression. Run them:tests.bugentries must fail (they exercise the not-yet-fixed defect);tests.regressionentries must pass (they lock the sibling behaviors that already work today). Abugtest that already passes means the root cause is wrong, go back tolib/debugging.md, do not proceed. Aregressiontest that already fails is a distinct pre-existing bug, narrate it and let the dev decide before continuing. - Commit the tests:
git add -A && git commit --no-verify -m "<KEY>: <subject>" - Implement
plan.steps. Follow repo conventions andlib/debugging.md(no fix without root cause). - Run the full relevant suite: everything green,
bugandregressiontests together. Run it through${CLAUDE_PLUGIN_ROOT}/lib/helpers/bounded.sh run --timeout 540 -- sh -c '<relevant-suite command you pick, not persisted>'(write each'inside it as'\'') with the Bash tool timeout set to 600000 ms; it prints at most 55 lines plus a FAIL section of at most 16,000 characters, total at most 27,000 characters, with the full list in.doer/bounded-fails.txt(Read it with offset and limit) when thefails:line reports a cut;okcontinues,failedmeans do not advance and fix first per the rule below,timeout,erroror helper exit 2 are reported and the stage stops. Green regressions after the fix are the evidence nothing sibling broke; a regression going red means the fix broke that sibling, fix it before advancing, never weaken the test to make it pass. - Commit the fix, separate from the tests commit:
git add -A && git commit --no-verify -m "<KEY>: <subject>"
Record what changed in notes.
Both commits land before Stage 6 runs (working messages; the PR-ready message is chosen in Stage 6). They must stay separate from anything Stage 6 adds. Both wk:replay's [TEMP] REPLAY commit and wk:protologs' [TEMP] PROTOLOG commit assume the fix underneath is already committed, so each one's diff contains only its own scaffolding and cleanup's revert is a clean no-op. An uncommitted fix gets tangled with the injected code in the same working-tree diff and has to be split apart by hand afterward.
Mark stage 5 complete → Stage 6.
not_app_bug
- Read
templates/mini-spike.md(this skill's directory) and write~/Downloads/<KEY>/spike.mdin Jira wiki markup, filled fromsignals,evidence, the root cause, andplan.spike_owner. - Iterate with the user on content and formatting; rewrite
spike.mdeach round. - Entry-points refinement (2c). If
entry_points_topicis set and the root cause inplan.root_causenames a file not already in that topic's storedpaths, ask once whether to add it (entrypoints.sh save --topic <entry_points_topic> --paths <existing+new> --from <KEY>). Skip silently whenentry_points_topicisnull, or the root-cause file is already covered. - When satisfied, ask whether to post it as a Jira comment. Only on explicit yes:
Close in ONE"${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh" comment <KEY> "$HOME/Downloads/<KEY>/spike.md"metadata.sh write ... --file bugfix.jsonthat setsstatus=completeand the cost: run"${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" record "<KEY>" --file bugfix.json > .doer/tickets/<KEY>/cost-record.json, add.cost = $cost[0]to the filter and pass--slurpfile cost .doer/tickets/<KEY>/cost-record.json, deletingcost-record.jsonafter the successful write. If the record output is empty or not a JSON object, use"${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" unknown "<reason>"instead; never storecost: nulland never let cost block the close. Then release the lock and session marker:"${CLAUDE_PLUGIN_ROOT}/lib/helpers/workspace-guard.sh" release "<KEY>". A spike does not go to device; stop here.
Stage 6 - Verify on device & Deliver (app_bug only)
- Ask whether reproducing this on device needs anything forced. Plain-chat question: "Does verifying this fix on your device need anything forced (a captured network response, an internal flag or kill switch), or can you reproduce the scenario as-is?" On yes, invoke
wk:replay(Skill tool, inject mode), passingentry_points[]as context. On completion, hold a short summary ({"technique": ..., "summary": ...}) for step 10's write intoreplay. On no,replaystaysnull. - Invoke
wk:protologs(Skill tool, inject mode), passingentry_points[]frombugfix.jsonas the user-specified entry points (protologs' Step 2.5 will not re-ask). Since the entry points are user-specified, protologs starts there regardless of its own hop budget, and traces down through the diff to the boundary. If the logging scope is unclear, ask the user how far up/down to instrument first. If step 1 ran replay, tell protologs' agent to skip any hunk between// REPLAY STARTand// REPLAY END: that code is forced scaffolding for reproduction, not the fix, and protologs' own slice now includes the[TEMP] REPLAYcommit's diff (skills/protologs/SKILL.mdcomputes it fromgit diff <BASE>...HEAD --name-only), so without this exclusion it instruments code that is about to be reverted and creates overlapping edits between the two skills' eventual reverts. - The user runs the build on device; confirm the logs show the expected flow and the fix behaves.
- Invoke
wk:protologs cleanup; verify no trace remains:"${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-checks.sh" trace PROTOLOG. Exit 2 means the base is unusable or git failed: stop and report it, never read it as an empty or clean result. (ItsPROTOLOG -pattern, with no trailing boundary, does not false-positive on the still-livePROTOLOG_RESPONSE -lines replay leaves behind at this point). - If step 1 ran replay, invoke
wk:replay cleanupnow, after protologs cleanup: replay's commit sits underneath protologs' in the stack, so reverting protologs first avoids the two skills' reverts touching overlapping lines. Verify noREPLAY START/REPLAY END/PROTOLOG_RESPONSE -trace remains. - Entry-points refinement (2c). If
entry_points_topicis set andplan.root_causenames a file not already in that topic's storedpaths, ask once whether to add it (entrypoints.sh save --topic <entry_points_topic> --paths <existing+new> --from <KEY>). Skip silently whenentry_points_topicisnull, or the root-cause file is already covered. - Recommended commit message. Draft THREE candidates, each
<KEY>: <Subject ≤72 chars>with the subject starting uppercase, specific to the actual change, in plain business language. Each candidate takes a genuinely different angle (the user-visible symptom fixed, the component changed, the root cause addressed), not rewordings of the same sentence. Validate all three before presenting (Core Principle 10):
A match means an internal label leaked; rewrite that candidate and re-validate, never present a matching draft. Present the three candidates in the chat as plain text, numbered 1-3, each in its own fenced code block. Drafts NEVER go insideprintf '%s\n' "<candidate-1>" "<candidate-2>" "<candidate-3>" | "${CLAUDE_PLUGIN_ROOT}/lib/helpers/vocab-guard.sh"AskUserQuestion, only the selection does. Ask viaAskUserQuestionwith short labels (Option 1/Option 2/Option 3), marking the strongest(Recommended); the tool's auto-appended "Other" is the edit path, and a plain-chat reply (1,2,3,edit: <text>) is equally valid. Re-runvocab-guard.shon any edited text before accepting it. Hold the chosen message for step 10's single write. - Squash gate, then offer to squash now (
AskUserQuestion:Yes/No, I'll squash manually). With replay in play there can be TWO[TEMP]/revert pairs on top of the Stage 5 fix commit (replay's and protolog's), not one; both cleanups (steps 4-5) must have actually run and left no trace before squashing collapses their subjects away forever. Gate on content, not on commit subjects (a legitimate[TEMP]/revert pair can remain in history even after a clean cleanup, so subject-grepping proves nothing). Run the squash gate as its own Bash command, never chained with the squash or any other step (&&,;,||or a pipe): its exit code is the decision.
Exit 1 or 2: STOP. Do not squash and do not offer the squash. Report the printed lines to the dev (and to the orchestrator when one is driving), even when the match looks like a false positive; only the dev decides what happens next. Exit 1 means a marker was added by the branch (the printed lines name the file); exit 2 means the base is unusable or git failed. A repo that documents or tests the markers (this kit does) lists the paths where that text is legitimate in"${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-checks.sh" squash-gate <base>.doer-gate-ignoreat its base branch; the gate skips only those paths and still fails on a marker anywhere else. Only on a clean gate (exit 0), proceed to the squash offer, in a separate command. On yes:
printsprintf '%s' "<chosen message>" | "${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-ops.sh" squash <base> <KEY> bugfixSKIP: 1 commit(nothing to do) orBACKUP <ref>followed by the squash itself (backup ref,reset --soft, commit, verify exactly 1 commit remains, all atomic); narrate the backup ref (rollback:git reset --hard <ref>). Exit 1 means it refused for a stated reason (dirty tree, base not an ancestor) and changed nothing; exit 2 after aBACKUP <ref>line means a git step failed after the backup, so narrate the ref and the rollback and stop. Never append aCo-Authored-Bytrailer to any candidate;git-ops.sh squashstrips one and printsSTRIPPED Co-Authored-By trailer, and exits 1 if a repo-side git hook re-injects it. - PR description.
No-PR. When
no_prinbugfix.jsonis true, do not detect templates, dispatch the writer or ask for approval: narrate one line, "PR description skipped: this ticket has no PR", hold the literal"skipped"for step 10's write and go to step 10. Otherwise auto-detect a template:
One found → use it; several → ask which; none → ask the dev to paste one, or reply"${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-checks.sh" pr-templatesdefault(Summary / Changes / How to test / Verification / Notes) orskip. Dispatch a PR-description writer Agent withmodel: "haiku"on every dispatch of this writer, rewrite rounds included (it only turns inlined text into text: it writes no code and judges none; every other dispatch inherits the session model). If the whole inlined prompt would pass 100 KB (about 33k tokens of ordinary prose, which with the roughly 38k fixed tokens of any subagent stays under the 100k-token long-prompt threshold), omitmodeland never trim the input. (Read budget 0; inlinetitle,signals(repro/expected/actual/env), the root cause and steps fromplan,notes, and a one-line on-device verification outcome from step 3). Rules for the output: fill every template section (> N/A for this ticket.where not applicable), preserve headings and directives verbatim, terse prose + bullets, no em-dashes, no internal labels (noPROTOLOG, noREPLAY, no stage names, no verdict jargon likeapp_bug). Validate withvocab-guard.sh(same as step 7) before presenting; scrub or regenerate on a match. Present wrapped in a four-backtick fence (four backticks on their own line before and after) so the description's own markdown, including any triple-backtick blocks inside it, renders literally in chat and copies verbatim. Then ask a plain-chat question ("keep it as is, or want changes?") and end the turn there (lib/narration.mdturn boundary 4); neverAskUserQuestionfor this. Persistingpr_descriptionor running step 10 before the dev's reply is prohibited. On requested changes, rewrite, re-validate withvocab-guard.sh, re-present, and ask again, as many rounds as needed. Only an explicit ok (orskip) unlocks step 10; onskip, hold the literal"skipped"for step 10's write. Close. Precondition: step 9 has the dev's explicit approval (or
"skipped"); if not, this step does not run. ONE"${CLAUDE_PLUGIN_ROOT}/lib/helpers/metadata.sh" write ... --file bugfix.jsoncall settingstatus=complete,completed_at,commit_message(step 7's choice),pr_description(step 9's approved result, or"skipped"),replay(step 1's summary, ornull),.cost = $cost[0]andstages.6="complete". Before the write run"${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" record "<KEY>" --file bugfix.json > .doer/tickets/<KEY>/cost-record.jsonand pass--slurpfile cost .doer/tickets/<KEY>/cost-record.json, deletingcost-record.jsonafter the successful write; if the output is empty or not a JSON object use"${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" unknown "<reason>", so cost never blocks the close. Release the lock and session marker:"${CLAUDE_PLUGIN_ROOT}/lib/helpers/workspace-guard.sh" release "<KEY>".With
no_prtrue, finish by saying:"Ticket
<KEY>complete.<N>commit(s) on<branch>. Run your pre-commit checks, use the commit message above, then merge<branch>into<base>and push when you approve (or keep everything as is)."
Notes
- Keep
bugfix.jsonlean at all times; this is a token-cost guardrail. - On resume, trust the
done/converted/stage flags; never redo completed work. - Every external or irreversible action (posting to Jira) requires an explicit user yes.
- Narration, turn boundaries, and locale follow
lib/narration.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

