agentleFS
icarloscornejoicarloscornejo/doer/skills/bugfix/SKILL.md

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

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.
  • bradtraversySkill

    feature

    Manage current feature workflow - start, review, explain or complete

    Also in 7 projects that use this

  • GitHubSkill

    refactor

    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

  • SentrySkill

    spec

    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

  • SentrySkill

    commit

    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

  • BlockSkill

    plan

    Draft a TDD, PRD, or lightweight plan in the conversation without committing it.

    Also in 4 projects that use this

  • Microsoft AzureSkill

    docs

    Also in 3 projects that use this

  • +1 more from getsentry

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
  1. Very specific:

    Verify Jira access: "${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh" config.

    line 91
  2. Very specific:

    Write ticket.md in .doer/tickets/<KEY>/: summary, type/status/priority, description verbatim, every comment (author · date · body).

    line 96
  3. Very specific:

    No hits → fall back to the current behavior: offer the area inferred from signals/evidence and "search the codebase yourself".

    line 136
  4. Very specific:

    Stored entry exists and the dev accepted its paths as-is: ask nothing. Just entrypoints.sh save --topic <t> --paths <same paths> --from …

    line 142
  5. Distinctive:

    Commit the fix, separate from the tests commit:

    line 170
  6. Very specific:

    Read templates/mini-spike.md (this skill's directory) and write ~/Downloads/<KEY>/spike.md in Jira wiki markup, filled from signals, …

    line 183
  7. Very specific:

    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 …

    line 185
  8. Distinctive:

    When satisfied, ask whether to post it as a Jira comment.

    line 186
  9. Very specific:

    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 …

    line 200
  10. Very specific:

    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 …

    line 216
  11. Very specific:

    Keep bugfix.json lean at all times.

    line 229
  12. Distinctive:

    Every external or irreversible action (posting to Jira) requires an explicit user yes.

    line 231

“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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/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 EnterPlanMode so the investigation runs on the strongest model; execution drops back on ExitPlanMode. The skill cannot force the session model; at the top of Stage 4 it reminds the user. Everything that needs Bash (downloads, .chls conversion, HAR parsing) runs before Stage 4, in Stages 0-3, since plan mode does not inherit the session's own permission mode: a Bash call inside EnterPlanMode prompts for approval on every single invocation, turning the investigation into a click-through instead of a one-shot. Stage 4 only ever uses Read/Grep/Glob on the already-gathered evidence and the codebase.
  • Single source of truth: one lean bugfix.json per 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:setup or /wk:jira <url>. The token is full-env: $JIRA_PAT by default, or whatever jira_token_env names; never persisted. If jira.sh config reports missing base_url or token_present: false, run the auto-detect pass (candidate env var NAMEs only, never values; see /wk:setup's SKILL.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_bug

bugfix.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

  1. Parse the ticket KEY from the argument (full URL or bare key; regex [A-Z]+-\d+). Strip an optional --no-pr first (it is not part of the key) and hold whether it was given.
  2. If ./.doer/tickets/<KEY>/bugfix.json exists → resume: a --no-pr typed here is ignored with a one-line narration (the mode is fixed at creation and lives in no_pr; a file without the field is the PR flow). Read it, announce current_stage, jump to the next incomplete stage. Never redo work flagged done. 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 in cost_sessions).
  3. Otherwise run "${CLAUDE_PLUGIN_ROOT}/lib/helpers/workspace-guard.sh" acquire "<KEY>" bugfix (contract: lib/workspace-guard.md), then create the folders and the initial bugfix.json (status=in_progress, current_stage=0, all stages pending, artifacts_dir set, no_pr true only when --no-pr was given, else false). cost.sh enter runs before init and leaves cost-entry.json for it; --track-cost adds cost_sessions with 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
  4. Verify Jira access: "${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh" config. On missing base_url or absent token, run the auto-detect pass (jira.sh detect-token-env for 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)

  1. Fetch: jira.sh fetch <KEY> → lean JSON (title, status, description, comments, attachments, source_url).
  2. Write ticket.md in .doer/tickets/<KEY>/: summary, type/status/priority, description verbatim, every comment (author · date · body). The ONLY place raw text lives.
  3. Distill into signals{}: repro (numbered steps), expected, actual, env, technical (endpoint ids, context ids, flag/function names) stay judgment calls; related_tickets is mechanical, jira.sh extract-keys ticket.md --exclude <KEY> (unique [A-Z]+-[0-9]+ mentions, excluding the ticket's own key).
  4. Build attachments[]: jira.sh attachments <fetch.json> ticket.md returns the array ready to persist (kind by extension, every .chls mentioned in text cross-referenced against the fetched attachments, ASCII-safe deduped path, original filename preserved), no further transformation needed.
  5. 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").

  1. Download every attachment with a non-null jira_url via jira.sh download <jira_url> "<artifacts_dir>/<path>" (path is already ASCII-safe and deduped, computed by attachments[] in Stage 1 Step 4; filename stays the original, unsanitized name for display). A .chls mentioned in text with no matching attachment (jira_url: null) cannot be downloaded; log it in notes and continue.
  2. Convert each .chls → .har, auto-detecting the converter:
    "${CLAUDE_PLUGIN_ROOT}/lib/helpers/har.py" convert "<in.chls>" "<out.har>"
    NO_CONVERTER on stderr (neither makehar nor Charles.app found) → note it, keep the .chls for manual conversion, continue (the investigation can still work from ticket text + screenshots).
  3. Set done / converted per attachment, and har to the converted file's path (extension swapped to .har) whenever conversion succeeded; a failed or skipped conversion leaves har: null and converted: 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.

  1. HAR evidence digest (skip with a note if attachments[] has no converted .har and no screenshots; evidence stays []). For each converted .har, extract ONLY the requests that matter, filter by signals.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.

  2. Entry points, in three steps:

    2a. Recover before asking. Run "${CLAUDE_PLUGIN_ROOT}/lib/helpers/entrypoints.sh" match <terms> with signals.technical terms plus keywords from title. For each hit, validate every path with test -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 (what wk: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 the AskUserQuestion below, shown with their topic and captured_from so the dev knows where they came from.

    No hits → fall back to the current behavior: offer the area inferred from signals/evidence and "search the codebase yourself".

    2b. Ask. One AskUserQuestion offering: the recovered paths (if any, recommended), the inferred area, and "search the codebase yourself". Save the answer into entry_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>; set entry_points_topic to <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> to captured_from, confirming the entry without friction) and set entry_points_topic to <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"; set entry_points_topic to <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_topic entry's stored paths; if so it offers, once, adding it (see Stage 5/6).

  3. Persist. ONE metadata.sh write sets evidence[], entry_points[], entry_points_topic, and stages.3 = "complete" together.

Stage 4 - Investigation, Analysis & Verdict (plan mode)

  1. Remind: "Stage 4 runs in plan mode; for the deepest analysis, make sure you're on opusplan."
  2. EnterPlanMode, then read analyze.md (this skill's directory) and follow it: code correlation at entry_points using the evidence[] already gathered in Stage 3, root cause per lib/debugging.md, the app_bug vs not_app_bug verdict criteria, and the plan{} shape. Read-only tools only (Read/Grep/Glob); no Bash, the evidence is already on disk in bugfix.json.
  3. Present root cause + verdict + plan; on approval ExitPlanMode, then persist per analyze.md's Confirm step (one batched metadata.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:

  1. Write every test in plan.tests.bug and plan.tests.regression. Run them: tests.bug entries must fail (they exercise the not-yet-fixed defect); tests.regression entries must pass (they lock the sibling behaviors that already work today). A bug test that already passes means the root cause is wrong, go back to lib/debugging.md, do not proceed. A regression test that already fails is a distinct pre-existing bug, narrate it and let the dev decide before continuing.
  2. Commit the tests:
    git add -A && git commit --no-verify -m "<KEY>: <subject>"
  3. Implement plan.steps. Follow repo conventions and lib/debugging.md (no fix without root cause).
  4. Run the full relevant suite: everything green, bug and regression tests 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 the fails: line reports a cut; ok continues, failed means do not advance and fix first per the rule below, timeout, error or 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.
  5. 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

  1. Read templates/mini-spike.md (this skill's directory) and write ~/Downloads/<KEY>/spike.md in Jira wiki markup, filled from signals, evidence, the root cause, and plan.spike_owner.
  2. Iterate with the user on content and formatting; rewrite spike.md each round.
  3. 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 stored paths, ask once whether to add it (entrypoints.sh save --topic <entry_points_topic> --paths <existing+new> --from <KEY>). Skip silently when entry_points_topic is null, or the root-cause file is already covered.
  4. When satisfied, ask whether to post it as a Jira comment. Only on explicit yes:
    "${CLAUDE_PLUGIN_ROOT}/lib/helpers/jira.sh" comment <KEY> "$HOME/Downloads/<KEY>/spike.md"
    Close in ONE metadata.sh write ... --file bugfix.json that sets status=complete and 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, deleting cost-record.json after 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 store cost: null and 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)

  1. 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), passing entry_points[] as context. On completion, hold a short summary ({"technique": ..., "summary": ...}) for step 10's write into replay. On no, replay stays null.
  2. Invoke wk:protologs (Skill tool, inject mode), passing entry_points[] from bugfix.json as 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 START and // REPLAY END: that code is forced scaffolding for reproduction, not the fix, and protologs' own slice now includes the [TEMP] REPLAY commit's diff (skills/protologs/SKILL.md computes it from git 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.
  3. The user runs the build on device; confirm the logs show the expected flow and the fix behaves.
  4. 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. (Its PROTOLOG - pattern, with no trailing boundary, does not false-positive on the still-live PROTOLOG_RESPONSE - lines replay leaves behind at this point).
  5. If step 1 ran replay, invoke wk:replay cleanup now, 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 no REPLAY START/REPLAY END/PROTOLOG_RESPONSE - trace remains.
  6. 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 once whether to add it (entrypoints.sh save --topic <entry_points_topic> --paths <existing+new> --from <KEY>). Skip silently when entry_points_topic is null, or the root-cause file is already covered.
  7. 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):
    printf '%s\n' "<candidate-1>" "<candidate-2>" "<candidate-3>" | "${CLAUDE_PLUGIN_ROOT}/lib/helpers/vocab-guard.sh"
    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 inside AskUserQuestion, only the selection does. Ask via AskUserQuestion with 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-run vocab-guard.sh on any edited text before accepting it. Hold the chosen message for step 10's single write.
  8. 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.
    "${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-checks.sh" squash-gate <base>
    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 .doer-gate-ignore at 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:
    printf '%s' "<chosen message>" | "${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-ops.sh" squash <base> <KEY> bugfix
    prints SKIP: 1 commit (nothing to do) or BACKUP <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 a BACKUP <ref> line means a git step failed after the backup, so narrate the ref and the rollback and stop. Never append a Co-Authored-By trailer to any candidate; git-ops.sh squash strips one and prints STRIPPED Co-Authored-By trailer, and exits 1 if a repo-side git hook re-injects it.
  9. PR description. 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 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:
    "${CLAUDE_PLUGIN_ROOT}/lib/helpers/git-checks.sh" pr-templates
    One found → use it; several → ask which; none → ask the dev to paste one, or reply default (Summary / Changes / How to test / Verification / Notes) or skip. Dispatch a PR-description writer Agent with model: "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), omit model and never trim the input. (Read budget 0; inline title, signals (repro/expected/actual/env), the root cause and steps from plan, 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 (no PROTOLOG, no REPLAY, no stage names, no verdict jargon like app_bug). Validate with vocab-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.md turn boundary 4); never AskUserQuestion for this. Persisting pr_description or running step 10 before the dev's reply is prohibited. On requested changes, rewrite, re-validate with vocab-guard.sh, re-present, and ask again, as many rounds as needed. Only an explicit ok (or skip) unlocks step 10; on skip, hold the literal "skipped" for step 10's write.
  10. 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.json call setting status=complete, completed_at, commit_message (step 7's choice), pr_description (step 9's approved result, or "skipped"), replay (step 1's summary, or null), .cost = $cost[0] and stages.6="complete". Before the write run "${CLAUDE_PLUGIN_ROOT}/lib/helpers/cost.sh" record "<KEY>" --file bugfix.json > .doer/tickets/<KEY>/cost-record.json and pass --slurpfile cost .doer/tickets/<KEY>/cost-record.json, deleting cost-record.json after 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_pr true, 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.json lean 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.