- Published on
Ticket to Branch: A Claude Code Skill Pipeline from Jira Ticket to Squash-Merged PR
A skill pipeline for Claude Code that takes a Jira ticket in a codebase you don't know yet and gets it to a pushed branch with small, reviewable commits. It is built from what Matt Pocock, Addy Osmani, GitHub Spec Kit, obra/superpowers, Dex Horthy and Anthropic actually ship and teach.
Yes, start using it, with three changes. Four independent checks (productivity studies, critics of spec-driven workflows, 10 more YouTube transcripts, and a hands-on technical audit) support the core of this approach. They also agree it fails if you run the full process on every ticket. Read the verdict ↓
Seven skills, four stops where you decide, one folder per ticket. The agent researches the code and finds facts. You make the decisions. Every stage writes a markdown file, and the next stage starts from that file in a clean context.
- Nothing to install: all eight skills, two hooks and the config are your own plain-text files, ready to copy into
~/.claude/. Jira works through a small curl script or a pasted ticket. Get the kit ↓ - Commits: plan them before coding. Each slice in
tasks.mdbecomes one local commit (5–6 for a bigger ticket). You push them as they are, the reviewer steps through them, and the platform squash-merges the PR, somainstays linear with one commit per ticket. This is common, well-supported practice. How it works ↓ - The decision skill asks one question at a time and always shows its recommendation first. It never asks you anything it could look up in the code.
Does this actually make you more productive?
Four research agents tried to prove this plan wrong. One read the controlled studies on AI coding productivity. One collected the strongest critiques of spec-driven workflows. One transcribed 10 more popular YouTube videos, including skeptics. One audited every command and skill setting in this page, and ran the git commands in a test repo.
The core loop is right
Understand the code → settle the decisions → small slices with tests → fresh-context review → small PR. This is what Pocock's 1.8M-view walkthrough, Boris Cherny (plan mode first on ~80% of tasks), Kent Beck and Dave Farley all describe. DORA 2025 calls AI "an amplifier" of exactly these habits: small batches, tests, version control.
Being asked questions works
The step with the best evidence is the agent asking you about unclear requirements before coding. In one study, letting agents ask questions on underspecified tasks improved results by up to 74% (Ambig-SWE, ICLR 2026). In another, missing implicit requirements caused 25–46% of failed agent runs. That's /decide.
Heavy process on every ticket
This is the most common criticism. Böckeler (martinfowler.com) watched a tool turn a small bug into "4 user stories with 16 acceptance criteria". Anthropic's own guide: "If you could describe the diff in one sentence, skip the plan." Long specs double review time and drift from the code. Fixed: the right-sizing tracks below.
Realistic gains
Ignore "10x". Measured studies of experienced developers in existing codebases find gains far smaller than what developers feel. In METR's trial, developers were 19% slower while believing they were 20% faster. Expect something like this:
| Study | What it found | What it means for you |
|---|---|---|
| METR RCT (2025, 16 devs, 246 tasks) + 2026 follow-up (57 devs) | 19% slower in 2025, believing 20% faster. The 2026 follow-up with agents showed about 18% faster, which METR calls "very weak evidence". | Your feeling of speed is unreliable. Measure. |
| Stanford, ~100k devs (Denisov-Blanch) | Gross +30–40%, net +15–20% after rework. Complex work in a large existing codebase: 0–10%. Codebase quality (tests, types, modularity) explains much of the gain. | Hard enterprise tickets gain least. Clean, tested areas gain most. |
| Harvard / Jellyfish (718 firms) · Faros (22k devs) | More code and PRs, but Jira issue resolution unchanged in one study. Review time up 49–441%, bugs per developer up 54%. | The bottleneck moves to review. Small PRs and your own verification are what turn output into delivered tickets. |
| Dave Farley trial (150 devs) · Cui et al. (4,867 devs) · Microsoft CLI agents | About 26–30% faster (55% for habitual users), +24% merged PRs. No maintainability penalty in Farley's study. | Real gains exist and grow with practice. |
| Anthropic RCT (Jan 2026, 52 devs) | AI users scored 17% lower on understanding the code. Those who asked "why" follow-ups kept their understanding. | You're new to this codebase. Use the brief and /decide to learn it, not just to skip reading. |
The three changes
Right-size every ticket
Pick a track at intake. Only M and L tickets get the full pipeline. If grilling or the spec takes longer than the fix, you're over-processing.
You verify, not just the AI
Read the tests first, then the diff. Run the feature yourself before G4 (Pocock: "without that, you just end up with slop"). Be able to explain every slice in one sentence. AI review is an extra filter, not your review.
Measure from day one
Write down the cycle time of your last 5–10 tickets as a baseline. Then log each ticket: track, hours to PR, review rounds, bugs found later. After 2–4 weeks you'll have real numbers to adjust the process, and to show your manager.
Right-sizing tracks
Based on what critics and practitioners recommend. Intake sizes the ticket. You can always move a ticket up a track.
| Track | Looks like | 1 intake | 2 decide | 3 spec | 4 slice | 5 build | 6 review | 7 ship |
|---|---|---|---|---|---|---|---|---|
| XS | One-line fix, diff fits in one sentence | 10-line brief | skip | skip | skip | 1 commit, run existing tests | optional | yes |
| S | Bug or small change, 1–3 files, under a day | short brief | skip | skip | skip | failing test first, 1–2 commits | narrow | yes |
| M | Multi-file feature, unclear approach or unfamiliar code | full brief | 5–10 questions | short (≤1 page) | 2–5 slices | TDD per slice | yes | yes |
| L | Cross-module, schema or API change, several days | full brief | full | ≤150 lines | split into 2–3 PRs | TDD, fresh session per slice | yes | per PR |
What the audit fixed in this version
37 issues: 1 critical, 13 high. All critical and high issues are fixed in the page and the skill drafts.
- Critical:
/shipreused a merge-base saved before the rebase. When re-splitting commits, that would have silently copied other people's changes into your commits. It now recomputes the base. - Git:
- The "nothing lost" check didn't work; it now uses
git range-diff. - A pushed branch is never rewritten; main is merged in instead.
- Rebase stops are handled.
- Local cleanup after a squash uses
git branch -D.
- The "nothing lost" check didn't work; it now uses
- Skills:
/reviewrenamed to/ticket-review, because the original name clashes with a built-in.- The review now runs in the foreground, with the mutation check done one reviewer at a time.
- The Jira fetch has permission set and a working fallback.
- The branch is created at intake.
- Repo hygiene:
- The ticket folder is no longer committed. It would have put raw Jira data on main.
- The Stop hook is scoped so it doesn't run in every stage.
- GitLab, Bitbucket and merge-queue settings are corrected.
Risks that remain
- Review load on teammates. This is the most consistent finding across studies. Keep PRs under ~400 lines and make your PR descriptions good.
- Understanding debt. If you only skim the agent's work, you won't learn the codebase, and you'll be slower on the next ticket.
- Agents gaming tests. Beck: it will "just change the test" or return hard-coded values. Read the test diff every time.
- Learning dip. The first 2–4 weeks may be no faster. Start with S and M tickets.
- Company rules. Check whether AI tools, personal Jira API tokens and posting from agents are allowed on your codebase.
A two-week start
- Days 1–2:
- Record your baseline from your last tickets.
- Copy the kit into
~/.claude/: the skills, both hooks, and thesettings.jsonandCLAUDE.mdsnippets. - Optional: create
~/.jira-envwith an API token, and test it withfetch-ticket.sh. Otherwise paste tickets. - Put your baseline as the first row of
~/ticket-log.md.
- Week 1: run 3–5 S and M tickets. Use
/ticket-intakeevery time and/decideonly on M tickets. Finish each with/ticket-retro. - Week 2:
- Look at the log.
- Wherever the agent repeated a mistake, add one line to CLAUDE.md or a hook. Not more prose.
- Edit the skill that caused the most friction. Drop any stage that didn't pay off for that ticket size.
The pipeline
Blue boxes are skills. Orange diamonds are where the agent stops and waits for you. Grey labels are the files each skill writes into docs/tickets/PROJ-123/.
One folder per ticket
Every stage passes its result to the next through files, which is what both Addy Osmani and Matt Pocock recommend. If a session dies or you /clear, the next one resumes from the folder and git status.
Default: keep the ticket folder out of git. Add docs/tickets/ to .git/info/exclude. If you commit it, it lands on main with the squash. That includes ticket.md, a raw Jira dump that may contain customer data, and many companies don't allow planning files in the repo. The reviewer gets the spec summary and the decisions in the PR body instead. Only ADRs and CONTEXT.md are committed, inside the slice they belong to. If your team does want specs in the repo, agree on that first, and never commit ticket.md. If you use claude -w worktrees, keep the notes folder outside the worktree: an untracked folder is deleted along with the worktree.
# local working notes, untracked (.git/info/exclude) docs/tickets/PROJ-123-bulk-export/ ├── ticket.md # raw ticket (curl script or pasted), read-only ├── fastcheck # 1 · one-line fast check command ├── brief.md # 1 · understanding artifact ├── decisions.md # 2 · D-1..n, who decided, why ├── questions-for-po.md # 2 · things only the PO knows ├── spec.md # 3 · the PRD, AC-1..n ├── tasks.md # 4 · slices = planned commits ├── review.md # 6 · findings + resolution ├── pr.md # 7 · PR title + body = squash commit └── jira-comment.txt # 7 · shown to you before posting # committed, inside the slice they belong to docs/adr/0042-export-runs-as-job.md # only hard-to-reverse calls CONTEXT.md # glossary, grows every ticket
# on the branch: one commit per slice (reviewers see these) $ git log --oneline origin/main.. c3d8e21 PROJ-123 feat(export): export button + status in UI a91c2e0 PROJ-123 feat(export): email link when job done 7d03b11 PROJ-123 feat(export): CSV writer for orders 4be8f9a PROJ-123 feat(export): enqueue export job (+ ADR 0042) 19ac5d2 PROJ-123 refactor(orders): extract OrderQuery # on main after squash-merge: one commit per ticket $ git log --oneline -3 main e5f1a07 PROJ-123 feat(export): bulk export of orders (#456) b2c9d14 PROJ-118 fix(auth): refresh token race (#451) 8a07e3c PROJ-120 feat(search): saved filters (#449)
Many commits on the branch, one on main
Is it possible? Yes. GitHub, GitLab and Bitbucket all support it natively, and you can enforce it with one repo setting. Is it good practice? Yes, as long as one ticket is one reviewable change (roughly ≤400 changed lines). LLVM works exactly this way: "Squash and Merge" only, with stacked PRs for anything bigger. GitLab's contributor guide says "do not squash until ready to merge."
The key point is where the squash happens. You don't squash on your machine. You push the slice commits as they are, review uses them, and the platform squashes when it merges.
| When | What happens to commits | Who / command | Rule |
|---|---|---|---|
| Build (stage 5) | One commit per slice, each with its tests passing. These are also the agent's rollback points. | /build-slice → git commit | Stage files by path. Never git add -A. |
| Self-review (stage 6) | Fixes go in as fixup! commits on the slice they belong to. | git commit --fixup=<sha> | Still local, so rewriting history is safe. |
| Before first push (stage 7) | Fold the local fixups into their slices and rebase onto the latest main, checking every commit. | GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash -x "<fast check>" origin/main | Only history nobody has seen yet. Make a backup branch first. Do not squash it to one commit yet. |
| Push + PR | Push 5–6 slice commits. The PR title is written as the future squash commit subject. | git push -u origin HEAD, gh pr create | Title like PROJ-123 feat(export): … |
| Human review | The reviewer steps through the Commits tab slice by slice, then reads the full diff once. Fixes are added as new commits. If main moved, merge it into the branch. | git commit --fixup, git merge origin/main, plain git push | No force-push during review, so "changes since last review" keeps working. |
| Merge | The platform squashes everything into one commit on main. Message = PR title + PR description. | "Squash and merge", or "Merge when ready" with a merge queue | The branch is deleted if "Automatically delete head branches" is on. Its commits stay visible on the PR page. Locally you delete it with git branch -D. |
Why not squash locally before pushing?
- The reviewer loses the slice map. Reviewing 5 commits of about 80 lines each is much easier than one 400-line diff.
- The agent loses its rollback points while review is still going on.
- You'd have to force-push after every review round, which breaks "changes since last review".
- The platform does the squash anyway, and does it better: it writes the message from the PR title and description you already reviewed.
Squash locally only if your team requires one commit before push, as in a Gerrit-style setup. In that case, make a backup branch first and use git reset --soft $(git merge-base origin/main HEAD) && git commit.
When one commit per ticket is the wrong fit
- Ticket over ~400–500 lines, or slices that are risky or useful on their own (a migration, a big refactor, a feature flag): split it into 2–3 PRs, each squashed, titled
PROJ-123 (1/3) …. Mark this at gate G3. Google: "100 lines is usually a reasonable size… 1000 lines is usually too large." - You need bisect and blame at slice level: use rebase-and-merge instead. History stays linear but keeps all 5 green commits.
- Long-lived branches: use merge commits. Squash records no merge base, so repeated merges conflict.
| Merge method | Linear main? | Commits on main per ticket | Revert a ticket | Bisect granularity | Fits this pipeline |
|---|---|---|---|---|---|
| Squash and merge | yes | 1 | git revert <sha> | per ticket | default |
| Rebase and merge | yes | 5–6 (each message must be good) | revert a range | per slice | if slice history matters |
| Merge commit | no (--first-parent view is) | 5–6 + merge | git revert -m 1 | per slice | long-lived branches |
# the one commit on main (PR title + description) PROJ-123 feat(export): bulk export of orders (#456) Admins can export up to 100k orders as CSV. Runs as a background job; a signed link is emailed when done (D-3, ADR 0042). Slices: - refactor(orders): extract OrderQuery - feat(export): enqueue export job from API - feat(export): CSV writer for orders - feat(export): email link when job done Refs: PROJ-123 Co-authored-by: Claude <noreply@anthropic.com>
Repo settings to enforce it
- GitHub:
- Allow only "Squash merging", with default message "Pull request title and description".
- Branch protection or a ruleset with Require linear history and required checks.
- "Automatically delete head branches" (a separate setting).
- Optional merge queue set to squash. It's only available for org-owned public repos or Enterprise Cloud, and CI must also run on the
merge_groupevent.
- GitLab: merge method "Merge commit" with squash set to "Require" works without rebasing. "Fast-forward merge" also gives a linear main, but forces a rebase and force-push of the branch before merge.
- Bitbucket: merge strategy "Squash". "Squash, fast-forward only" needs the branch rebased first, with the same force-push caveat.
- Conventional Commits: the PR title becomes the commit subject, so lint PR titles with
amannn/action-semantic-pull-request. By default it rejects titles starting withPROJ-123. Either set a customheaderPattern, or put the key at the end:feat(export): bulk export (PROJ-123).
What happens in Jira
- Put the key in all three places: branch name, PR title (so it ends up in the squash commit), and each slice commit. Jira only matches uppercase keys.
- The Development panel keeps the original branch commits even after the branch is deleted, and adds the squash commit. Expect about 6 + 1 entries. The merged PR is the link that lasts.
- Don't put smart-commit commands (
#time,#comment,#transition) in slice commits. They run on push, and Atlassian documents that they show up duplicated aftergit push --forceandgit merge --squash. This flow rewrites unpushed history, and if your repo uses GitHub's "Default message" squash setting, every commit message is copied into the squash commit and they run again. Let/shipcomment through the MCP, and drive status changes with Jira Automation's "Pull request merged" trigger. - Reverting is clean:
git revert e5f1a07undoes the whole ticket. Because "Revert "PROJ-123 …"" keeps the key, the revert links back to the ticket too.
The seven skills
Each card says what the skill does, which public skill it borrows from, and where it stops. Full drafts of every SKILL.md are further down.
Turns the ticket into the "understanding artifact": what the task really is, where it lives in the code, and what is still open.
- How
-
- Pulls the ticket, comments and linked issues before the model starts, through a small bundled curl script that calls the Jira REST API with your token. With no token, it reads a ticket you pasted into
ticket.md. Nothing to install. - Sizes the ticket first (XS / S / M / L, see right-sizing). For XS and S tickets it writes a 10-line brief and goes straight to building.
- Creates the branch right away:
git switch -c feat/PROJ-123-slug origin/main. Every later skill refuses to run onmain. - Writes research questions from the ticket, then sends 2–3 parallel Explore subagents to answer them without seeing the ticket. Dex Horthy's 2026 revision: the subagents report facts about the code, not opinions about the solution.
- Uses
git logon the touched files ("tell me how this code changed"), and readsCONTEXT.mdand existing ADRs first.
- Pulls the ticket, comments and linked issues before the model starts, through a small bundled curl script that calls the Jira REST API with your token. With no token, it reads a ticket you pasted into
- Writes
brief.md: task in plain words, acceptance criteria as Jira states them, code map withfile:line, current behaviour, the existing tests that cover this area (pointing the agent at them reduces regressions much more than general TDD instructions do), risks, size, and open questions sorted into Facts (the agent already answered these) and Decisions (you have to make these).- Borrows
- Addy's context-engineering project map · official feature-dev plugin's parallel code-explorers · Pocock's triage checks for duplicates and earlier rejections · Dex's research-without-the-ticket
The skill that helps you answer the design questions. It goes through the Decisions list from the brief and settles each one with you.
- Rules
-
- Facts are the agent's job. If the code or docs can answer a question, a subagent looks it up instead of asking you (Pocock: "Finding facts is your job, never the user's").
- One question at a time through
AskUserQuestion. The recommended option comes first, with a one-line "why it matters" (Spec Kit). Questions are ranked by impact × uncertainty. - A question is only asked once everything it depends on is settled (Pocock's "frontier").
- Questions only the product owner can answer go to
questions-for-po.md. You can post them as one Jira comment, after confirming. - It finishes by laying out the full scope in one place and asking "anything missing?".
- Writes
decisions.md(D-1…n: question, choice, alternatives, reason, who decided). It also writes an ADR (architecture decision record), but only when a choice is hard to reverse, would surprise someone without context, and was a real trade-off (Pocock's three tests). New terms go intoCONTEXT.md.
Writes the PRD in the same session as /decide, so nothing from the conversation is lost. It does not interview you again.
- Format
- Problem · Goals / non-goals · user stories · acceptance criteria AC-1…n in EARS form ("WHEN … THE SYSTEM SHALL …"), each with the test that will prove it · implementation decisions (modules, interfaces, schema; links to D-n) · testing decisions (agreed seams, existing tests to copy) · out of scope · open risks.
- Why EARS
- Each criterion maps directly onto a test name, so stage 5 can't quietly drop one.
- Where
- It stays in the local notes folder. Once you approve it, its summary and the AC list go into the PR description, so the reviewer sees them.
Splits the spec into vertical slices, where each slice is exactly one future commit. Commit-splitting happens here, before any code is written.
- Rules
-
- Prep refactors go first, as their own commit, and don't change behaviour.
- Each slice is a thin path through every layer (schema → API → UI → test). It can be shown working and fits in one fresh context.
- Very wide refactors follow expand → migrate in batches → contract.
- Every slice lists the ACs it covers, its files, its verify command, what it depends on, and its planned commit message.
- Addy's guide for sizing: about 100 lines is ideal, 300 is fine, 1,000 means split it.
- Optional
- Mark stack boundaries if the change will go out as stacked PRs, for example
prefactor | feature. A human sets these boundaries in every published demo.
Runs once per slice, ideally in a fresh session (/clear between slices). It does red → green → verify → commit, then stops.
- Loop
- Read the slice and its ACs → run the existing tests for this area first (if they already fail, stop and tell you) → write one failing test at an agreed seam and watch it fail → write the smallest code that passes → run the repo's full check (
make checkor equivalent) → stage only this slice's files (nevergit add -A) → commitPROJ-123 type(scope): …→ tick the slice intasks.md. - You own the tests
- Read the new test names and assertions at G4. Tests written by the same agent that wrote the code can share its mistakes, and Kent Beck warns that agents will disable tests to get green.
- Never
- Edit, skip or delete an existing test without asking. Edit a new test to make it pass. Write all the tests up front (Pocock calls this "horizontal slicing"). Fix things outside the slice: list them in a
NOTICED BUT NOT TOUCHINGblock instead (Addy). - Guard
- A Stop hook scoped to this skill that runs the fast tests, so the agent can't declare a slice done while they fail. See guardrails for how to keep it from firing in every stage.
Reviewers in a fresh context (new subagents started by your session, which never see the conversation) that only see git diff origin/main...HEAD after a fetch, the spec and the repo's standards, never the conversation that wrote the code. It's named /ticket-review because /review is already Claude Code's built-in alias for /code-review.
- Axes
- Two read-only subagents run in parallel and report separately, without merging or re-ranking each other's findings. The spec checker looks for missing ACs, scope creep and wrong behaviour. The standards checker covers repo conventions, code smells and security.
- Tests first
- It reviews the tests first. Then one reviewer at a time flips one condition in the code, checks that a test catches it, reverts the change and confirms the tree is clean (Addy). Running this in parallel would let the two reviewers break each other's test runs.
- Filter
- A separate check validates each finding before you see it. Only correctness problems and gaps against the spec are reported, not style preferences. Anthropic warns that an AI reviewer asked for gaps will always find some, and that chasing every finding leads to over-engineering. Your main session applies the fixes as
git commit --fixup=<slice-sha>, so each one stays attached to its slice. - Not a replacement
- for your own read of the diff. One study found PRs reviewed only by AI bots merged at 45%, versus 68% for PRs a human reviewed.
- Built-in
- Claude Code's
/code-review highworks as a second pass.
Cleans up the slice commits, pushes them as they are, and opens the PR whose title and description become the single commit on main.
- Steps
-
- Branch already pushed? If so, it rewrites nothing and goes straight to the PR step. To pick up changes on main, it uses
git merge origin/main. - Not pushed yet: it makes a backup branch, then folds the fixups into their slices and rebases onto
origin/main, running the fast check on every commit. That's one command:git rebase -i --autosquash -x "<fast check>" origin/main. If it stops partway, it shows you and offersgit rebase --abort. It never squashes everything into one commit. - Proves nothing was lost or added with
git range-diffagainst the backup branch. - Shows you the slice log, the PR title (this becomes the squash subject) and the PR body (this becomes the squash body). Pushes only after you say yes.
- Opens the PR. Body: Summary, slice list, AC → evidence table, Merge danger,
Refs: PROJ-123and aCo-authored-byline. If it opened the PR as a draft, it marks it ready withgh pr readyonce CI passes. - Posts a Jira comment with the PR link through the bundled curl script, after you approve the text. With no token, it gives you the text to paste. It doesn't use smart commits.
- Branch already pushed? If so, it rewrites nothing and goes straight to the PR step. To pick up changes on main, it uses
- During review
- Review fixes are pushed as new
fixup!commits, never force-pushed. If main moves, it runsgit merge origin/main. "Require linear history" only applies to main, and the squash at merge removes the merge commit anyway. - Merge
- You click "Squash and merge", or add the PR to the merge queue if your repo has one. One commit lands on main. Afterwards:
git switch main && git pull --prune && git branch -D feat/…. You need-Dbecause git can't tell a squashed branch was merged. - If the diff is messy
- Re-split without interactive tools. Recompute the base first with
BASE=$(git merge-base origin/main HEAD), because a base saved before the rebase is stale and would pull other people's changes into your commits. Thengit reset --soft "$BASE", and stage by file per slice, or by hunk withgit apply --cached. jj withjj-hunkis built for agents. - Too big
- If G3 split the ticket, open 2–3 PRs titled
PROJ-123 (1/3). Each is squashed separately. Usegh stackorgit-spiceand merge from the bottom up.
What a /decide question looks like
A decision only gets asked once the agent has checked the code. The question states what the agent found, why the choice matters, and what it recommends. Saying yes takes one keypress.
Should bulk export run synchronously in the request, or as a background job?
Found: OrderQuery takes ~4s for 10k rows (api/orders/query.ts:88), and the gateway times out at 30s. A JobRunner already exists (workers/runner.ts:12). Why it matters: it decides the API shape and whether the UI needs a polling state.
Reuses JobRunner, so no timeouts at 100k rows. Costs one extra slice.
Simplest. Breaks above ~70k rows.
Moves it to questions-for-po.md and blocks the spec.
## decisions.md ### D-3 · Export runs as a background job - Asked: 2026-10-18 · Decided by: you - Choice: JobRunner job, emailed link - Rejected: sync stream (fails > ~70k rows) - Evidence: query.ts:88, runner.ts:12 - Affects: AC-2, AC-4 · ADR: 0042 - Reversible? No, API contract changes ### D-4 · Max rows per export - Decided by: agent default, flagged - Choice: 100k (matches existing CSV limit) - Status: needs PO confirmation → Q-1
Superpowers adds a useful rule: anything the agent decided on its own is recorded as the agent's call, never as something you agreed to.
What is an ADR?
An ADR (Architecture Decision Record) is a short markdown file that records one important technical decision: what was decided, why, what else was considered, and what it costs. Michael Nygard proposed the format in 2011. ADRs live in the repo, usually in docs/adr/, numbered in order (0001-…, 0002-…), and are never edited after they're accepted. If the decision changes later, a new ADR replaces the old one and the old one is marked "Superseded by 00NN".
ADRs answer the question a new developer (or a new agent session) asks a year later: "Why on earth is it done this way?" The answer stays next to the code instead of disappearing in a Slack thread or a closed Jira ticket. That matters a lot for agents, since they start every session with no memory and read the repo for context.
decisions.md vs ADR
decisions.mdbelongs to one ticket. It records every choice made while working on it (D-1…n), small ones included: row limit, naming, which endpoint to extend.- An ADR is for the project as a whole. Only the few decisions that will still matter after the ticket is closed get promoted to one.
- Pocock's three tests: write an ADR only if the decision is hard to reverse, would be surprising without context, and was a real trade-off. If any one fails,
decisions.mdis enough. - Typical ADR topics: a new dependency or service, sync vs async processing, a data model or API contract change, auth or security approach, picking one pattern over another for the whole codebase.
- Not ADR material: variable names, a 100-row page size, which test file to put something in.
Before stage 1, the intake skill reads the existing docs/adr/ so the agent follows decisions already made instead of fighting them. If your repo has no ADRs yet, start with the first one this pipeline produces. Templates: MADR or Nygard's original. Tooling (optional): adr-tools.
# docs/adr/0042-export-runs-as-background-job.md # 42. Bulk export runs as a background job Status: Accepted · 2026-10-18 · PROJ-123 (D-3) ## Context Exports can reach 100k rows. OrderQuery takes ~4s per 10k rows and the API gateway times out at 30s. A JobRunner for background work already exists. ## Decision Exports run as a JobRunner job. The API returns 202 with a job id; the user gets an email with a signed download link valid for 24h. ## Alternatives considered - Synchronous streaming response: simplest, but fails above ~70k rows at the gateway timeout. - Client-side pagination + merge: moves work to the browser, unreliable for large files. ## Consequences + No timeouts; reuses existing worker infra. - API contract is async: clients must poll or wait for email. Hard to reverse once public. - Needs storage + cleanup for generated files.
The spec (PRD) format
This combines Pocock's sections, Kiro's EARS criteria and Anthropic's rule to "end with an end-to-end verification step". It adds a code map, which the public templates leave out because they were written for new projects.
# PROJ-123 · Bulk export of orders status: draft → approved (G2) ## Problem why this ticket exists, 3–5 lines, in the user's words ## Goals / Non-goals non-goals are first-class; they stop scope creep at review ## User stories numbered: "As a <actor>, I want…, so that…" ## Acceptance criteria - AC-1 WHEN an admin requests an export of ≤100k orders THE SYSTEM SHALL enqueue a job and return 202 with a job id. → test: export.api.test.ts "enqueues job and returns 202" - AC-2 WHEN the job finishes THE SYSTEM SHALL email a signed link valid 24h. → test: export.job.test.ts "emails signed link on completion" ## Implementation decisions modules + interfaces, linked to D-n; no line numbers here ## Code map from brief.md: entry points, files to touch, patterns to copy ## Testing decisions agreed seams (ideally one), existing tests to copy, what is NOT tested ## Out of scope ## Verification the end-to-end check a human or agent runs before G4 ## Open risks
What the public skill sets cover
Taken from reading the actual SKILL.md files on 18 Oct 2026. ● covered well, ◐ partly, ○ not at all.
| Stage | Matt Pocock mattpocock/skills | Addy Osmani agent-skills | GitHub Spec Kit | obra superpowers | Anthropic feature-dev / code-review |
|---|---|---|---|---|---|
| 1 Ticket → brief | ◐ triage, research | ◐ context-engineering | ○ | ◐ recon in brainstorming | ● code-explorer agents |
| 2 Decide questions | ● grilling, grill-with-docs | ● interview-me | ● /clarify (1 at a time) | ● brainstorming | ◐ clarifying phase |
| 3 PRD / spec | ● to-spec | ● spec-driven-development | ● spec / plan | ◐ design doc | ◐ architecture phase |
| 4 Slices / commits plan | ● to-tickets | ● planning-and-task-breakdown | ● tasks + /analyze | ● writing-plans | ○ |
| 5 TDD build | ● tdd, implement | ● TDD, incremental, /build | ◐ /implement | ● TDD, subagent-driven | ◐ |
| 6 Review | ● code-review (2 axes) | ● code-review, /ship personas | ○ | ● requesting-code-review | ● code-review plugin |
| 7 Split, push, PR, Jira | ◐ pr template; push blocked | ◐ git rules only | ○ | ◐ finishing-a-branch | ○ |
| Jira | ◐ "Other" tracker | ◐ named, no tooling | ○ | ○ | ○ |
| Weight | light, composable | broad, strict gates | heavy, own CLI | opinionated, strict | built in |
Write your own, borrow the ideas
The table shows where each idea came from. The kit rewrites all of them as your own files, fitted to Jira, an unfamiliar codebase and squash-merge, with nothing to install.
Three skills first
Week 1 needs only /ticket-intake, /build-slice and /ship, plus /ticket-retro to log each ticket. Add /decide, /ticket-spec, /slice and /ticket-review once your first M ticket comes along.
Mixing in frameworks later
If you also try Superpowers, Pocock's or Spec Kit's skills, their rules collide with these. Keep one planning flow and one review gate.
The kit: every file, ready to copy
Eight skills, two small scripts they bundle, two hooks and two config snippets. All of it is plain text and bash, written for this pipeline. Ideas are borrowed from Pocock, Addy Osmani, Spec Kit and Anthropic, but no code is. Nothing is installed: no plugin, no MCP server, no CLI. The only tools used are git, curl and bash, which your machine already has, plus gh if it's already there.
Put the files in your personal ~/.claude/ folder. Personal skills work in every repo, and nothing in your team's repo changes. Each file below says why it's built the way it is, so you can change it with confidence. Treat them as yours: edit them after every few tickets.
# your home folder: nothing inside any work repo changes ~/.claude/ ├── CLAUDE.md # + the ticket-workflow block ├── settings.json # + the git-guard PreToolUse hook ├── hooks/ │ ├── git-guard.sh │ └── fast-check-on-stop.sh └── skills/ ├── ticket-intake/ SKILL.md fetch-ticket.sh ├── decide/ SKILL.md ├── ticket-spec/ SKILL.md ├── slice/ SKILL.md ├── build-slice/ SKILL.md ├── ticket-review/ SKILL.md ├── ship/ SKILL.md jira-comment.sh └── ticket-retro/ SKILL.md ~/.jira-env # optional, chmod 600 ~/ticket-log.md # written by /ticket-retro
What was tested
- The
!`…`ticket fetch,$0and${CLAUDE_SKILL_DIR}, run in a real Claude Code 2.1.302 session. - A skill-scoped Stop hook firing.
git-guard.shagainst more than 30 commands.fetch-ticket.shagainst a fake Jira server.- JSON escaping in
jira-comment.sh. - The setup script in an empty home folder, twice.
Not tested: calls to a real Jira instance, because there's no account here. Run bash ~/.claude/skills/ticket-intake/fetch-ticket.sh YOUR-KEY once by hand to check yours.
Skills
1 · /ticket-intakecustom
- Fetches the ticket before the model starts: the
!`…`line runs a small bundled curl script. There's no Jira plugin or MCP server to install, and your token never enters the conversation. Without Jira access it reads aticket.mdyou pasted. - Sizes the ticket first. XS and S tickets get a 10-line brief and skip straight to
/build-slice. This answers the most common criticism of spec-heavy workflows. - Research subagents get questions, never the ticket (Dex Horthy's 2026 revision), so they report what the code does, not what they guess you want.
- Writes the
fastcheckcommand once./build-slice, the Stop hook and/shipall reuse it, so "tests pass" means the same thing everywhere. - Sorts open questions into Facts, Decisions and PO questions, which is exactly the input
/decideneeds.
---
name: ticket-intake
description: Turn a Jira ticket into an understanding brief for an unfamiliar codebase. Use when starting work on a Jira key like PROJ-123.
disable-model-invocation: true
argument-hint: "<JIRA-KEY>"
allowed-tools: Bash(bash *fetch-ticket.sh*)
---
# Ticket intake
Ticket key: $0. It must look like PROJ-123. If it is missing or malformed, stop and ask.
## Ticket (fetched before you started)
!`bash "${CLAUDE_SKILL_DIR}/fetch-ticket.sh" "$0"`
If the block above starts with NO_TICKET or BAD_KEY, ask the user to paste the
ticket (summary, description, acceptance criteria, important comments). Never guess it.
## Steps
1. Size it. XS: one-line fix. S: 1-3 files, under a day, no open decisions.
M: several files or at least one real decision. L: crosses modules, changes
a schema or public API, or will be over ~400 changed lines.
2. Branch: git fetch origin, then git switch -c feat/$0-<short-slug> origin/main
(use the team's prefix if CLAUDE.md names one). If a branch for $0 already
exists, switch to it instead. Never work on main or master.
3. Notes folder: docs/tickets/$0-<short-slug>/. Add the line docs/tickets/ to
.git/info/exclude if it is not there. Save the ticket text as ticket.md.
Never edit the Jira issue in this skill.
4. Fast check: find the quickest command that typechecks, lints and runs the
tests for one area (package.json scripts, Makefile, CI config, README).
Write it as one line to fastcheck in the notes folder and run it once.
Note whether the baseline is green.
5. XS or S: write brief.md with Task, Files, Existing tests (names and how to
run them) and Done when, in about 10 lines. STOP and suggest /build-slice.
6. M or L: read CLAUDE.md, CONTEXT.md and docs/adr/ if they exist.
7. Write 5-10 research questions about the code ("where is X computed?",
"what calls Y?", "which tests cover Z?"). Launch 2-3 Explore subagents in
parallel and give them ONLY the questions, not the ticket. They return facts
with file:line and no proposals.
8. Run git log --oneline -15 on the 3-5 main files. Summarise how the area
changed recently and which commits matter.
9. If the fetch above printed JIRA_OK, run
bash "${CLAUDE_SKILL_DIR}/fetch-ticket.sh" --search "<2-3 keywords>"
and look for duplicates or earlier rejected attempts.
10. Write brief.md:
- Task in plain words (2-4 lines). Acceptance criteria exactly as Jira states them.
- Code map: entry points, files likely touched, patterns to copy (file:line).
- Current behaviour, and the EXISTING TESTS that cover it (names + run command).
- Fast check command and whether the baseline is green.
- Risks and unknowns. Size (XS/S/M/L) and why.
- Open questions, in three groups:
FACTS: answered by the research, each with file:line.
DECISIONS: need the user; each with 2-3 options and your recommendation.
PO QUESTIONS: only the ticket author can answer.
11. STOP. Show a summary of at most 15 lines and ask: "Is this the task?" (G1)
1 · fetch-ticket.shbundled script
- Plain bash and curl, which every Mac and Linux machine already has. It always exits 0, so a Jira outage never stops the skill from loading. It prints a marker (
JIRA_OK,PASTED_TICKET,NO_TICKET,BAD_KEY) that the skill reacts to. - Uses REST API v2 for the issue because it returns the description as short wiki text instead of v3's large JSON tree. For search it uses
/rest/api/3/search/jqlon Cloud, because Atlassian removed the old v2 search there. - The token is passed to curl on stdin (
-H @-), so it doesn't show in the process list. - Tested: no config, a malformed key, a pasted ticket, a fake Jira server returning an issue, a 404, and search.
#!/usr/bin/env bash
# Prints a Jira ticket for /ticket-intake. Always exits 0 so the skill still loads.
#
# Usage: fetch-ticket.sh PROJ-123
# fetch-ticket.sh --search "two or three keywords"
#
# Optional setup, once: put these lines in ~/.jira-env and run chmod 600 ~/.jira-env
# JIRA_BASE_URL=https://yourcompany.atlassian.net
# JIRA_EMAIL=you@company.com # Jira Cloud
# JIRA_API_TOKEN=... # Cloud: id.atlassian.com > Security > API tokens
# JIRA_PAT=... # Jira Server / Data Center, instead of the two above
# JIRA_AC_FIELD=customfield_10042 # only if acceptance criteria live in a custom field
# Without it, paste the ticket into docs/tickets/<KEY>-<slug>/ticket.md and this script prints that.
[ -f "$HOME/.jira-env" ] && . "$HOME/.jira-env"
# The token goes to curl on stdin (-H @-), so it never shows up in the process list.
auth_header() {
if [ -n "${JIRA_PAT:-}" ]; then
printf 'Authorization: Bearer %s\n' "$JIRA_PAT"
else
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "${JIRA_EMAIL:-}" "${JIRA_API_TOKEN:-}" | base64 | tr -d '\n')"
fi
}
if [ "${1:-}" = "--search" ]; then
[ -n "${JIRA_BASE_URL:-}" ] || { echo "NO_JIRA: no ~/.jira-env, so search Jira by hand for duplicates."; exit 0; }
# Jira Cloud removed /rest/api/2/search; Server/Data Center still uses it.
if [ -n "${JIRA_PAT:-}" ]; then path=/rest/api/2/search; else path=/rest/api/3/search/jql; fi
auth_header | curl -sfG --max-time 20 -H @- -H 'Accept: application/json' "$JIRA_BASE_URL$path" \
--data-urlencode "jql=text ~ \"${2:-}\" ORDER BY updated DESC" \
--data-urlencode "fields=summary,status,resolution,updated" \
--data-urlencode "maxResults=15" || echo "JIRA_SEARCH_FAILED"
exit 0
fi
key="${1:-}"
if ! printf '%s' "$key" | grep -Eq '^[A-Z][A-Z0-9]+-[0-9]+$'; then
echo "BAD_KEY: '$key' does not look like PROJ-123. Ask the user for the key."
exit 0
fi
pasted=$(ls docs/tickets/"$key"-*/ticket.md 2>/dev/null | head -1)
if [ -n "$pasted" ]; then
echo "PASTED_TICKET from $pasted:"
cat "$pasted"
exit 0
fi
if [ -z "${JIRA_BASE_URL:-}" ]; then
echo "NO_TICKET: no ~/.jira-env and no pasted ticket.md. Ask the user to paste the ticket."
exit 0
fi
fields=summary,description,comment,issuelinks,status,labels,priority,parent,subtasks,issuetype,components
[ -n "${JIRA_AC_FIELD:-}" ] && fields="$fields,$JIRA_AC_FIELD"
# API v2 returns the description as plain wiki text, which is much shorter than v3's JSON document format.
if out=$(auth_header | curl -sf --max-time 20 -H @- -H 'Accept: application/json' \
"$JIRA_BASE_URL/rest/api/2/issue/$key?fields=$fields"); then
echo "JIRA_OK"
printf '%s\n' "$out"
else
echo "NO_TICKET: the Jira request failed (check ~/.jira-env, VPN and token). Ask the user to paste the ticket."
fi
exit 0
2 · /decidecustom
- The step with the strongest evidence. Agents allowed to ask clarifying questions did up to 74% better on unclear tasks (Ambig-SWE).
- One question at a time, with the recommendation first. Most decisions take one keypress. The two lines before the options (what was found, what breaks if it's wrong) are what make a fast answer a safe one.
- Decided by: agent default stops the agent from claiming later that you agreed to something you never saw.
- The ADR rule needs all three tests to pass, so you get a handful of ADRs a year, not one per ticket. See What is an ADR.
--- name: decide description: Settle the open design decisions for the current ticket by asking the user one question at a time. Use after ticket-intake on M or L tickets, or when the user says "grill me" about a ticket. disable-model-invocation: true --- # Decide Find KEY in the branch name (git branch --show-current). Read docs/tickets/<KEY>-*/brief.md, mainly its DECISIONS and PO QUESTIONS. ## Rules - Facts are your job. If the code, tests, docs or git history can answer a question, send an Explore subagent and record the answer as a FACT. Never ask the user something you could look up. - Ask ONE question at a time with AskUserQuestion, 2-4 options. The first option is your recommendation, labelled "(Recommended)". - Before the options, write two lines: what you found (with file:line) and what goes wrong if the choice is wrong. - Order the queue by impact x uncertainty. Ask a question only when every decision it depends on is settled. New decisions found along the way join the queue. - Aim for 5-10 questions. If you need more, the ticket is probably too big: say so and propose a split. - Check vague words against CONTEXT.md. If the user's answer contradicts the code, say so before recording it. - Questions only the PO can answer go to questions-for-po.md. Offer to post them as one Jira comment, and post only after an explicit yes. - Anything you chose yourself is recorded as "agent default", never as something the user agreed to. ## After each answer, append to decisions.md ### D-<n> . <short title> - Question: - Choice: - Rejected: <alternatives, one line each with why> - Evidence: <file:line or doc> - Decided by: user | agent default | PO - Affects: <acceptance criteria or files> . Reversible: yes | no ## ADRs Draft docs/adr/NNNN-<slug>.md only when a decision passes all three tests: hard to reverse, surprising without context, and a real trade-off. Sections: Status (proposed), Context, Decision, Consequences, Alternatives. It will be committed with the slice it belongs to. Add new domain terms to CONTEXT.md. ## Done when No open decisions remain. Show the full scope in one list and ask "Anything missing?" Do not /clear: /ticket-spec runs in this same session.
3 · /ticket-speccustom
- Runs in the same session as
/decide, so it never asks you the same thing twice. - Each acceptance criterion is in EARS form and names the test that proves it, so
/build-slicecan't quietly drop one. Untestable criteria become explicit MANUAL checks rather than disappearing. - Capped at about 150 lines, because nobody reviews a 1,000-line plan.
- Never committed.
/shipcopies its summary into the PR body, where the reviewer actually reads it.
---
name: ticket-spec
description: Write the PRD/spec for the current ticket from the brief and the decisions made in this session. Use right after /decide on M or L tickets.
disable-model-invocation: true
---
# Ticket spec
Do NOT interview the user again. Use brief.md, decisions.md and this conversation.
KEY comes from the branch name. Refuse to run on main or master.
1. Propose the test seams: where tests will touch the code. Prefer existing
seams and existing test files, ideally one seam. Confirm them with the
user in a single AskUserQuestion.
2. Write docs/tickets/<KEY>-*/spec.md with exactly these sections:
## Problem 2-4 lines, in the user's words
## Goals / Non-goals
## User stories numbered
## Acceptance criteria
AC-1 WHEN <trigger> THE SYSTEM SHALL <response>
-> test: <test file> "<test name>"
(EARS forms: WHEN / WHILE / IF ... THEN / WHERE; one behaviour per AC)
## Implementation decisions modules, interfaces, schema; cite D-n
## Code map from brief.md, file:line
## Testing decisions seams, existing tests to copy, fast check command
## Out of scope
## Verification one end-to-end check a human can run before pushing
## Open risks
3. Every acceptance criterion in the Jira ticket maps to at least one AC-n.
List any that don't, and ask about them.
4. Every AC names one test. An AC that can't be tested gets a manual check
under Verification, marked MANUAL.
5. Keep it under ~150 lines. The user must be able to review it in 5 minutes.
6. STOP and ask for approval (G2). The spec is not committed. It stays in the
untracked notes folder, and /ship copies its summary into the PR body.
4 · /slicecustom
- This is where your 5–6 reviewable commits are decided, before any code exists. Every slice carries its planned commit message.
- Vertical slices with prep refactors first, so each commit can be reviewed and reverted on its own.
- A subagent that sees only the spec and the task list checks coverage, which catches an AC with no slice before you build anything.
--- name: slice description: Split the approved spec into vertical slices, one slice per future commit. Use after /ticket-spec is approved, on M or L tickets. disable-model-invocation: true --- # Slice Read spec.md and brief.md from docs/tickets/<KEY>-*/ (KEY from the branch). Write tasks.md in the same folder. Each slice becomes exactly one commit. ## Rules - Preparatory refactors come first, as their own slices, with no behaviour change. - Each feature slice is a thin path through every layer it needs (schema -> API -> UI -> test). It can be shown working on its own and fits in one fresh session. - Aim for about 100 changed lines per slice; 300 is fine; more means split it. - Whole ticket over ~400 changed lines? Propose 2-3 PRs titled "<KEY> (1/3) ...", each squash-merged on its own. - Wide refactors: expand -> migrate in batches -> contract. - Every AC in spec.md belongs to at least one slice. ## Per slice - [ ] S-<n> <title> ACs: AC-? Depends on: S-? | none Files: <paths> Existing tests to run first: <names or command> First failing test: <file> "<name>" Verify: <fast check, plus anything slice-specific> Commit: "<KEY> <type>(<scope>): <summary>" PR: 1 (or 2, 3 if split) End the file with an empty section: ## NOTICED BUT NOT TOUCHING ## Check, then stop Launch one subagent with only spec.md and tasks.md. It reports any AC with no slice, any slice that contradicts the spec, and any slice that depends on a later one. Fix what it finds. STOP and ask: right size? right order? should any part be its own PR? (G3)
5 · /build-slicecustom
- One failing test at a time, starting from a green baseline. Writing all the tests first was rejected by every source.
- The Stop hook in the frontmatter only exists while this skill runs. It reruns the fast check, so the agent can't report "done" while it's red. Tested on Claude Code 2.1.302: the hook fires and receives
stop_hook_active. - Stages by path and parks unrelated problems under NOTICED BUT NOT TOUCHING, which keeps each commit about one thing.
- The final report (test names with output, surprises, one sentence for the reviewer) is written for your verification step, not as a victory message.
---
name: build-slice
description: Build the next unchecked slice of the current ticket test-first and commit it. Use once per slice, ideally in a fresh session.
disable-model-invocation: true
hooks:
Stop:
- hooks:
- type: command
command: bash "$HOME/.claude/hooks/fast-check-on-stop.sh"
timeout: 300
---
# Build one slice
1. Refuse to run on main or master. KEY comes from the branch name.
git status --porcelain must show no changes to TRACKED files
(the untracked notes folder is fine). If it does, stop and ask.
2. Read docs/tickets/<KEY>-*/: spec.md and tasks.md, and pick the first
unchecked slice. XS/S tickets have no tasks.md: brief.md is the one slice.
3. Run the existing tests for this area first. If they already fail, STOP and
tell the user. Never build on a red baseline.
4. Write ONE failing test at the agreed seam, in the style of the tests next
to it. Run it and confirm it fails for the expected reason, not a typo or
import error.
5. Write the smallest code that makes it pass. Rerun. Repeat steps 4-5 for
the slice's other ACs, one test at a time.
6. Run the fast check (the fastcheck file in the notes folder). Then read
your own diff once, looking for debug output, dead code and secrets.
7. Never edit, skip or delete an existing test without asking. Never change a
new test just to make it pass. Never write all the tests up front.
Don't fix anything outside this slice: add it under
"NOTICED BUT NOT TOUCHING" in tasks.md.
8. Stage only this slice's files, by path (never git add -A or git add .),
including any ADR or CONTEXT.md change that belongs to it.
9. Commit with the message planned in tasks.md. Tick the slice.
10. STOP and report:
- what changed, in 3-5 lines
- the new test names and the command output proving they pass
- anything surprising, and anything you were unsure about
- one sentence the user could say to a reviewer about this slice
Then suggest /clear before the next /build-slice.
6 · /ticket-reviewcustom
- Changed from the earlier draft. It no longer uses
context: fork. A forked skill runs as a subagent, and Claude Code subagents can't start other subagents, so the parallel reviewers would never have run. Now your main session coordinates and every reviewer is a new subagent. That keeps the important property: the reviewer never saw how the code was written. - Spec and standards are reviewed by two separate reviewers whose reports aren't merged. The mutation check runs alone, so it can't break the other reviewers' test runs.
- A final subagent tries to disprove each finding. Anthropic warns that a reviewer asked for gaps always finds some.
- Fixes are
--fixupcommits, so/shipcan fold each into its slice.
---
name: ticket-review
description: Review the current ticket branch with fresh-context subagents against the spec and the repo's standards. Use after all slices are built, before /ship.
disable-model-invocation: true
---
# Ticket review
You coordinate. The reviewing is done by subagents, which start with an empty
context, so they never see how the code was written.
1. git fetch origin. KEY from the branch. Notes folder: docs/tickets/<KEY>-*/.
Collect the paths only: spec.md, tasks.md, CLAUDE.md, and
CODING_STANDARDS.md or CONTRIBUTING.md if they exist.
2. Launch two subagents IN PARALLEL. Tell each: "Read-only. Do not edit files
or run tests. Run git diff origin/main...HEAD yourself and read the files
listed. Report findings as: problem . file:line . severity
(Critical / Required / Optional) . suggested fix. At most 10."
A) SPEC: for every AC, where is it implemented and which test proves it?
Report missing ACs, behaviour that differs from the spec, and scope
creep (changes no AC asks for). Check the tests really assert the AC
and are not hard-coded to pass.
B) STANDARDS: repo conventions (copy what nearby code does), error
handling, security (input, auth, secrets, injection), performance traps,
and code smells. No style preferences that a linter would not enforce.
Keep the two reports separate. Don't merge or re-rank them.
3. Then ONE subagent, not in parallel with anything: "Pick the 3 most
important conditions in the diff. For each: change it to its opposite,
run its tests, record whether a test failed, and revert it. At the end
git status --porcelain must show no tracked changes; prove it."
Any change that no test catches is a Required finding.
4. Launch one more subagent with the combined findings and the diff. It tries
to disprove each finding and drops the ones it cannot confirm.
5. Write review.md in the notes folder: confirmed findings grouped by
severity, the mutation results, and a short "dropped findings" list.
6. Show the user the Critical and Required findings and ask which to fix.
Each agreed fix becomes its own commit:
git commit --fixup=<sha of the slice it belongs to>
Optional findings are listed, not fixed, unless the user asks.
This does not replace the user's own reading of the diff before /ship.
7 · /shipcustom
- The only skill that touches the remote, so it has the G4 stop plus a backup branch and a
range-diffcheck that nothing was lost. - Never rewrites a branch that's already pushed, and recomputes the merge-base before any re-split. These were the audit's critical and high fixes.
ghis optional. Without it, the skill gives you the compare link and the title and body to paste.- The PR title and body are written as the future squash commit, because that's what lands on main.
---
name: ship
description: Tidy the slice commits, push the ticket branch, open the PR (squash-merged later into one commit on main) and comment on Jira. Use when review.md has no open Critical or Required findings.
disable-model-invocation: true
---
# Ship
The model: one commit per slice on the branch. The platform squash-merges the
PR into ONE commit on main. Never squash to one commit locally.
1. Preconditions: on feat/<KEY>-* (never main or master); git status
--porcelain shows no tracked changes; every slice is ticked; review.md has
no open Critical or Required finding. If any fails, stop and say which.
2. git fetch origin
BRANCH=$(git branch --show-current)
If git ls-remote --exit-code --heads origin "$BRANCH" succeeds, the branch
was already pushed: DO NOT rewrite history. Skip to step 6. To pick up new
commits from main, use git merge origin/main, never rebase.
3. BACKUP="backup/$(echo "$BRANCH" | tr / -)-$(date +%s)"; git branch "$BACKUP"
OLD_BASE=$(git merge-base origin/main HEAD)
4. Fold the fixups into their slices and rebase onto main, running the fast
check (fastcheck in the notes folder) on every commit:
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash -x "<fast check>" origin/main
If a check fails or a conflict stops the rebase, STOP and show the user.
Offer git rebase --abort, which restores the branch. Never continue silently.
5. Prove nothing was lost or added:
git range-diff "$OLD_BASE..$BACKUP" "origin/main..HEAD"
Expect only the fixups folded into their slices. Show it to the user.
If the commits don't match the slices, RECOMPUTE the base first:
BASE=$(git merge-base origin/main HEAD); git reset --soft "$BASE"
then recommit slice by slice (stage by path, or git apply --cached for
single hunks) and rerun the check from step 4.
6. Every commit message starts with <KEY>. No Jira smart-commit commands
(#time #comment #transition) in any message.
7. Write pr.md in the notes folder.
Line 1 = PR title = the future squash subject, in the team's format, e.g.
"<KEY> <type>(<scope>): <summary>".
The rest = PR body = the future squash body:
- Summary (3-5 lines)
- Slices: one line each
- Acceptance criteria -> evidence: table of AC-n, test name, result
- Decisions: the key D-n lines
- Risk: one-way or two-way door, blast radius, how to roll back
- Refs: <KEY>
- Co-authored-by: Claude <noreply@anthropic.com>
Also write jira-comment.txt: the PR link placeholder plus the summary.
8. SHOW the user: git log --oneline origin/main..HEAD, the PR title and body,
and the Jira comment. Wait for an explicit yes. (G4)
9. git push -u origin HEAD
If gh is available: gh pr create --title "<line 1>" --body-file <body file>
Otherwise give the user the compare URL git printed and the title and body
to paste.
10. Jira, only after a yes: put the PR URL into jira-comment.txt, then run
bash "${CLAUDE_SKILL_DIR}/jira-comment.sh" <KEY> <path to jira-comment.txt>
If it prints NO_JIRA, show the text for the user to paste. Change the
ticket status only if the user asks.
During review: every fix is a NEW commit (git commit --fixup=<sha>, then
git push). Never force-push a branch under review. Never merge the PR
yourself; a human or the merge queue squash-merges it.
After merge: git switch main && git pull --prune, then
git branch -D "$BRANCH" and the backup branch (-D because a squash leaves
no ancestry for git to see).
7 · jira-comment.shbundled script
- Posts the comment that
/shipshowed you, only after you say yes. It uses the same~/.jira-envas intake, and without one it tells you to paste the text yourself. - Escapes the text for JSON with sed and awk, so it needs no jq or Python. Tested with quotes, backslashes and tabs.
#!/usr/bin/env bash
# Posts a text file as a comment on a Jira ticket. Used by /ship after the user says yes.
# Usage: jira-comment.sh PROJ-123 path/to/comment.txt
# Uses the same ~/.jira-env as /ticket-intake. Without it, prints NO_JIRA and you paste by hand.
set -u
[ -f "$HOME/.jira-env" ] && . "$HOME/.jira-env"
key="${1:-}"; file="${2:-}"
printf '%s' "$key" | grep -Eq '^[A-Z][A-Z0-9]+-[0-9]+$' || { echo "BAD_KEY: $key"; exit 1; }
[ -f "$file" ] || { echo "NO_FILE: $file"; exit 1; }
[ -n "${JIRA_BASE_URL:-}" ] || { echo "NO_JIRA: no ~/.jira-env. Paste the comment into Jira by hand."; exit 1; }
auth_header() {
if [ -n "${JIRA_PAT:-}" ]; then
printf 'Authorization: Bearer %s\n' "$JIRA_PAT"
else
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "${JIRA_EMAIL:-}" "${JIRA_API_TOKEN:-}" | base64 | tr -d '\n')"
fi
}
# Escape the text for JSON: backslashes, quotes, tabs, carriage returns, then newlines.
body=$(sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e "s/$(printf '\t')/\\\\t/g" -e "s/$(printf '\r')//g" "$file" \
| awk '{ printf "%s\\n", $0 }')
# API v2 takes plain wiki text; v3 would need Atlassian's JSON document format.
if auth_header | curl -sf --max-time 20 -X POST -H @- \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
--data-binary "{\"body\":\"$body\"}" \
"$JIRA_BASE_URL/rest/api/2/issue/$key/comment" >/dev/null; then
echo "POSTED comment on $key"
else
echo "JIRA_POST_FAILED: paste the comment into Jira by hand."
exit 1
fi
8 · /ticket-retronew
- New. This is change 3 from the verdict ("measure from day one") as a skill. It appends one row per ticket to
~/ticket-log.md, outside every repo, and never rewrites old rows. - Most numbers come from git and the notes folder. It only asks you what it can't know: your hands-on hours, rework, and the biggest agent mistake.
- A mistake seen twice becomes one rule (in CLAUDE.md, a hook or one skill), not more prose.
- Every 5 tickets it compares against your baseline and says plainly if you're not faster, so you find out early.
--- name: ticket-retro description: Log how a finished ticket went, compare against the baseline, and turn a repeated agent mistake into one rule. Use after the PR is opened or merged. disable-model-invocation: true argument-hint: "[JIRA-KEY]" --- # Ticket retro KEY is $0, or comes from the branch name. Notes folder: docs/tickets/<KEY>-*/. 1. Gather facts without asking: size and track from brief.md; number of decisions in decisions.md; slices in tasks.md; findings per severity in review.md; time of the first slice commit and of the push (git log); PR created time and review rounds (gh pr view --json createdAt,reviews, if gh is available). 2. Ask the user in ONE AskUserQuestion round: hands-on hours (their own time, not wall-clock), whether they had to rework anything after review, and the biggest agent mistake, if any. 3. Append ONE row to ~/ticket-log.md, outside every repo. Create the file with this header only if it does not exist. Never rewrite earlier rows. | date | key | size | track | start->PR (h) | hands-on (h) | slices | review C/R/O | rework | agent mistake | note | The first row in the file is the baseline: the user's typical numbers from before using this pipeline. 4. Read the last 5 rows. If the same kind of agent mistake appears twice, propose ONE fix: a line for CLAUDE.md, a check in a hook, or a change to one skill. Show the exact text and add it only after a yes. 5. Every 5th row: compare the median start->PR and hands-on hours per size with the baseline. Say plainly if it is slower or the same. Suggest dropping stages that didn't pay off for that size.
Hooks and config
git-guard.shhook
- Runs before every Bash command in every session. A hook can't tell which skill is running, so the rules apply unconditionally.
- Blocks: pushing to main or master, pushing or committing while on main, force-push,
--no-verify,git add -Aor.,commit -a, andgh pr merge. - Asks first:
--force-with-lease,reset --hard,clean -f(it would delete the notes folder),branch -D,checkout -- .andstash drop. - Tested against more than 30 commands, including the ones
/shipneeds (reset --soft,rebase --autosquash,commit --fixup,merge origin/main), which all pass through. It uses jq if present (it ships with macOS 15 and later) and plain grep otherwise.
#!/usr/bin/env bash
# PreToolUse hook for Bash: blocks risky git/gh commands and asks before destructive ones.
# Applies in every session and every skill, because a hook can't tell which skill is running.
input=$(cat)
if command -v jq >/dev/null 2>&1; then
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
else
cmd=$input # no jq: match against the raw JSON, which still contains the command text
fi
[ -n "$cmd" ] || exit 0
block() { echo "Blocked by git-guard: $1" >&2; exit 2; }
ask() {
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"git-guard: %s"}}\n' "$1"
exit 0
}
has() { printf '%s' "$cmd" | grep -Eq -- "$1"; }
S='[[:space:]]'
END='([[:space:];&|"]|$)'
branch=$(git branch --show-current 2>/dev/null)
if has "git$S.*push"; then
has "push[^;&|]*($S|:|refs/heads/)(main|master)$END" && block "never push to main/master. Push the ticket branch and open a PR."
case "$branch" in main|master) block "you are on $branch. Push from the ticket branch." ;; esac
has "push[^;&|]*(--force$END|--force=|$S-f$END|$S\+[^[:space:]])" && block "no force-push. Push fixes as new fixup! commits."
has "push[^;&|]*--force-with-lease" && ask "force-push with lease. Only OK if this branch has never been reviewed."
fi
has "--no-verify" && block "don't skip the repo's git hooks."
has "git$S+add$S+(-A|--all|\.)$END" && block "stage files by path, never all at once."
has "git$S+commit($S[^;&|]*)?$S-[a-zA-Z]*a[a-zA-Z]*$END" && block "no commit -a. Stage by path, then commit."
has "git$S+commit($S|$)" && case "$branch" in main|master) block "don't commit on $branch. Create the ticket branch first." ;; esac
has "gh$S+pr$S+merge" && block "the agent never merges PRs. A human or the merge queue does."
has "git$S+reset$S[^;&|]*--hard" && ask "reset --hard discards uncommitted work."
has "git$S+clean$S+-[a-zA-Z]*f" && ask "git clean deletes untracked files (including the ticket notes)."
has "git$S+branch$S[^;&|]*-D$END" && ask "deleting a branch with -D."
has "git$S+(checkout|restore)$S[^;&|]*$S\.$END" && ask "this throws away local changes."
has "git$S+stash$S+(drop|clear)" && ask "dropping stashed work."
exit 0
fast-check-on-stop.shhook
- Only
/build-sliceuses it, through its frontmatter, so it doesn't run tests at the end of every turn in every stage. - If the second stop in a row is allowed through, a baseline that can never go green can't trap the agent in a loop.
- Tested: red check → blocked with the last 40 lines of output; second stop → allowed; green → allowed.
#!/usr/bin/env bash
# Stop hook, used only by /build-slice (declared in its frontmatter).
# Refuses to let the agent finish while the ticket's fast check fails.
input=$(cat)
# Second stop in a row: let it finish, so a baseline that can't go green never loops.
printf '%s' "$input" | grep -Eq '"stop_hook_active"[[:space:]]*:[[:space:]]*true' && exit 0
cd "${CLAUDE_PROJECT_DIR:-.}" 2>/dev/null || exit 0
key=$(git branch --show-current 2>/dev/null | grep -oE '[A-Z][A-Z0-9]+-[0-9]+' | head -1)
[ -n "$key" ] || exit 0
check=$(cat docs/tickets/"$key"-*/fastcheck 2>/dev/null | head -1)
[ -n "$check" ] || exit 0
if ! out=$(bash -c "$check" 2>&1); then
{
echo "Fast check failed: $check"
printf '%s\n' "$out" | tail -40
echo "Fix it, or tell the user exactly why it can't pass. Don't report the slice as done."
} >&2
exit 2
fi
exit 0
settings.jsonmerge, don't replace
- Turns on
git-guard.shfor every repo. If you already have a~/.claude/settings.json, add thePreToolUseentry to your existinghooksinstead of overwriting the file.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash \"$HOME/.claude/hooks/git-guard.sh\"" }
]
}
]
}
}
CLAUDE.md blockappend
- Personal rules that load in every repo, so your team's checked-in
CLAUDE.mdstays untouched. For one repo only, put them inCLAUDE.local.mdthere instead. - Short on purpose. Anything the agent keeps ignoring should become a hook check, not a longer paragraph.
## Ticket workflow (personal rules) - Work happens on a ticket branch: feat/<KEY>-<slug>. Never commit or push on main/master. - Every commit message and PR title starts with the Jira key. - Notes for a ticket live in docs/tickets/<KEY>-<slug>/. The folder is untracked (listed in .git/info/exclude) and never committed, except ADR or CONTEXT.md changes that belong to a slice. - One slice = one commit. Stage files by path; never git add -A, git add . or commit -a. - Never force-push, never merge a PR, never skip git hooks with --no-verify. - No Jira smart-commit commands (#time, #comment, #transition) in commit messages. - Never edit, skip or delete an existing test without asking. - Facts: look them up in the code yourself. Decisions: ask me, recommendation first. - Problems outside the current slice go under NOTICED BUT NOT TOUCHING in tasks.md. - Before saying something works, show the command and its output.
~/.jira-envoptional
- The only secret, kept in your home folder, never in a repo. Check that your company allows personal API tokens. If it doesn't, skip this file and paste tickets.
# ~/.jira-env (chmod 600 ~/.jira-env). Optional: without it you paste tickets by hand. JIRA_BASE_URL=https://yourcompany.atlassian.net # Jira Cloud: your login email + an API token (id.atlassian.com > Security > API tokens) JIRA_EMAIL=you@company.com JIRA_API_TOKEN=paste-token-here # Jira Server / Data Center instead: a personal access token, and delete the two lines above # JIRA_PAT=paste-token-here # Only if acceptance criteria live in a custom field (ask your Jira admin for the id) # JIRA_AC_FIELD=customfield_10042
setup-kit.shall files in one paste
- Writes the 8 skills, 2 bundled scripts and 2 hooks into
~/.claude/. It skips any file that already exists, so running it twice or over your edited versions changes nothing. It doesn't touchsettings.jsonorCLAUDE.md; merge those by hand. Tested in an empty home folder, and rerun to confirm nothing is overwritten. - Save it as a file and run
bash setup-kit.sh, or copy each file above by hand.
#!/usr/bin/env bash
# Writes the ticket-to-branch kit into ~/.claude. Never overwrites: existing files are skipped.
set -eu
put() { if [ -e "$1" ]; then echo "skip (exists): $1"; cat >/dev/null; else mkdir -p "$(dirname "$1")"; cat >"$1"; echo "wrote: $1"; fi; }
put "$HOME/.claude/skills/ticket-intake/SKILL.md" <<'KIT_EOF_0'
---
name: ticket-intake
description: Turn a Jira ticket into an understanding brief for an unfamiliar codebase. Use when starting work on a Jira key like PROJ-123.
disable-model-invocation: true
argument-hint: "<JIRA-KEY>"
allowed-tools: Bash(bash *fetch-ticket.sh*)
---
# Ticket intake
Ticket key: $0. It must look like PROJ-123. If it is missing or malformed, stop and ask.
## Ticket (fetched before you started)
!`bash "${CLAUDE_SKILL_DIR}/fetch-ticket.sh" "$0"`
If the block above starts with NO_TICKET or BAD_KEY, ask the user to paste the
ticket (summary, description, acceptance criteria, important comments). Never guess it.
## Steps
1. Size it. XS: one-line fix. S: 1-3 files, under a day, no open decisions.
M: several files or at least one real decision. L: crosses modules, changes
a schema or public API, or will be over ~400 changed lines.
2. Branch: git fetch origin, then git switch -c feat/$0-<short-slug> origin/main
(use the team's prefix if CLAUDE.md names one). If a branch for $0 already
exists, switch to it instead. Never work on main or master.
3. Notes folder: docs/tickets/$0-<short-slug>/. Add the line docs/tickets/ to
.git/info/exclude if it is not there. Save the ticket text as ticket.md.
Never edit the Jira issue in this skill.
4. Fast check: find the quickest command that typechecks, lints and runs the
tests for one area (package.json scripts, Makefile, CI config, README).
Write it as one line to fastcheck in the notes folder and run it once.
Note whether the baseline is green.
5. XS or S: write brief.md with Task, Files, Existing tests (names and how to
run them) and Done when, in about 10 lines. STOP and suggest /build-slice.
6. M or L: read CLAUDE.md, CONTEXT.md and docs/adr/ if they exist.
7. Write 5-10 research questions about the code ("where is X computed?",
"what calls Y?", "which tests cover Z?"). Launch 2-3 Explore subagents in
parallel and give them ONLY the questions, not the ticket. They return facts
with file:line and no proposals.
8. Run git log --oneline -15 on the 3-5 main files. Summarise how the area
changed recently and which commits matter.
9. If the fetch above printed JIRA_OK, run
bash "${CLAUDE_SKILL_DIR}/fetch-ticket.sh" --search "<2-3 keywords>"
and look for duplicates or earlier rejected attempts.
10. Write brief.md:
- Task in plain words (2-4 lines). Acceptance criteria exactly as Jira states them.
- Code map: entry points, files likely touched, patterns to copy (file:line).
- Current behaviour, and the EXISTING TESTS that cover it (names + run command).
- Fast check command and whether the baseline is green.
- Risks and unknowns. Size (XS/S/M/L) and why.
- Open questions, in three groups:
FACTS: answered by the research, each with file:line.
DECISIONS: need the user; each with 2-3 options and your recommendation.
PO QUESTIONS: only the ticket author can answer.
11. STOP. Show a summary of at most 15 lines and ask: "Is this the task?" (G1)
KIT_EOF_0
put "$HOME/.claude/skills/ticket-intake/fetch-ticket.sh" <<'KIT_EOF_1'
#!/usr/bin/env bash
# Prints a Jira ticket for /ticket-intake. Always exits 0 so the skill still loads.
#
# Usage: fetch-ticket.sh PROJ-123
# fetch-ticket.sh --search "two or three keywords"
#
# Optional setup, once: put these lines in ~/.jira-env and run chmod 600 ~/.jira-env
# JIRA_BASE_URL=https://yourcompany.atlassian.net
# JIRA_EMAIL=you@company.com # Jira Cloud
# JIRA_API_TOKEN=... # Cloud: id.atlassian.com > Security > API tokens
# JIRA_PAT=... # Jira Server / Data Center, instead of the two above
# JIRA_AC_FIELD=customfield_10042 # only if acceptance criteria live in a custom field
# Without it, paste the ticket into docs/tickets/<KEY>-<slug>/ticket.md and this script prints that.
[ -f "$HOME/.jira-env" ] && . "$HOME/.jira-env"
# The token goes to curl on stdin (-H @-), so it never shows up in the process list.
auth_header() {
if [ -n "${JIRA_PAT:-}" ]; then
printf 'Authorization: Bearer %s\n' "$JIRA_PAT"
else
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "${JIRA_EMAIL:-}" "${JIRA_API_TOKEN:-}" | base64 | tr -d '\n')"
fi
}
if [ "${1:-}" = "--search" ]; then
[ -n "${JIRA_BASE_URL:-}" ] || { echo "NO_JIRA: no ~/.jira-env, so search Jira by hand for duplicates."; exit 0; }
# Jira Cloud removed /rest/api/2/search; Server/Data Center still uses it.
if [ -n "${JIRA_PAT:-}" ]; then path=/rest/api/2/search; else path=/rest/api/3/search/jql; fi
auth_header | curl -sfG --max-time 20 -H @- -H 'Accept: application/json' "$JIRA_BASE_URL$path" \
--data-urlencode "jql=text ~ \"${2:-}\" ORDER BY updated DESC" \
--data-urlencode "fields=summary,status,resolution,updated" \
--data-urlencode "maxResults=15" || echo "JIRA_SEARCH_FAILED"
exit 0
fi
key="${1:-}"
if ! printf '%s' "$key" | grep -Eq '^[A-Z][A-Z0-9]+-[0-9]+$'; then
echo "BAD_KEY: '$key' does not look like PROJ-123. Ask the user for the key."
exit 0
fi
pasted=$(ls docs/tickets/"$key"-*/ticket.md 2>/dev/null | head -1)
if [ -n "$pasted" ]; then
echo "PASTED_TICKET from $pasted:"
cat "$pasted"
exit 0
fi
if [ -z "${JIRA_BASE_URL:-}" ]; then
echo "NO_TICKET: no ~/.jira-env and no pasted ticket.md. Ask the user to paste the ticket."
exit 0
fi
fields=summary,description,comment,issuelinks,status,labels,priority,parent,subtasks,issuetype,components
[ -n "${JIRA_AC_FIELD:-}" ] && fields="$fields,$JIRA_AC_FIELD"
# API v2 returns the description as plain wiki text, which is much shorter than v3's JSON document format.
if out=$(auth_header | curl -sf --max-time 20 -H @- -H 'Accept: application/json' \
"$JIRA_BASE_URL/rest/api/2/issue/$key?fields=$fields"); then
echo "JIRA_OK"
printf '%s\n' "$out"
else
echo "NO_TICKET: the Jira request failed (check ~/.jira-env, VPN and token). Ask the user to paste the ticket."
fi
exit 0
KIT_EOF_1
put "$HOME/.claude/skills/decide/SKILL.md" <<'KIT_EOF_2'
---
name: decide
description: Settle the open design decisions for the current ticket by asking the user one question at a time. Use after ticket-intake on M or L tickets, or when the user says "grill me" about a ticket.
disable-model-invocation: true
---
# Decide
Find KEY in the branch name (git branch --show-current). Read
docs/tickets/<KEY>-*/brief.md, mainly its DECISIONS and PO QUESTIONS.
## Rules
- Facts are your job. If the code, tests, docs or git history can answer a
question, send an Explore subagent and record the answer as a FACT.
Never ask the user something you could look up.
- Ask ONE question at a time with AskUserQuestion, 2-4 options. The first
option is your recommendation, labelled "(Recommended)".
- Before the options, write two lines: what you found (with file:line) and
what goes wrong if the choice is wrong.
- Order the queue by impact x uncertainty. Ask a question only when every
decision it depends on is settled. New decisions found along the way join
the queue.
- Aim for 5-10 questions. If you need more, the ticket is probably too big:
say so and propose a split.
- Check vague words against CONTEXT.md. If the user's answer contradicts the
code, say so before recording it.
- Questions only the PO can answer go to questions-for-po.md. Offer to post
them as one Jira comment, and post only after an explicit yes.
- Anything you chose yourself is recorded as "agent default", never as
something the user agreed to.
## After each answer, append to decisions.md
### D-<n> . <short title>
- Question:
- Choice:
- Rejected: <alternatives, one line each with why>
- Evidence: <file:line or doc>
- Decided by: user | agent default | PO
- Affects: <acceptance criteria or files> . Reversible: yes | no
## ADRs
Draft docs/adr/NNNN-<slug>.md only when a decision passes all three tests:
hard to reverse, surprising without context, and a real trade-off. Sections:
Status (proposed), Context, Decision, Consequences, Alternatives. It will be
committed with the slice it belongs to. Add new domain terms to CONTEXT.md.
## Done when
No open decisions remain. Show the full scope in one list and ask
"Anything missing?" Do not /clear: /ticket-spec runs in this same session.
KIT_EOF_2
put "$HOME/.claude/skills/ticket-spec/SKILL.md" <<'KIT_EOF_3'
---
name: ticket-spec
description: Write the PRD/spec for the current ticket from the brief and the decisions made in this session. Use right after /decide on M or L tickets.
disable-model-invocation: true
---
# Ticket spec
Do NOT interview the user again. Use brief.md, decisions.md and this conversation.
KEY comes from the branch name. Refuse to run on main or master.
1. Propose the test seams: where tests will touch the code. Prefer existing
seams and existing test files, ideally one seam. Confirm them with the
user in a single AskUserQuestion.
2. Write docs/tickets/<KEY>-*/spec.md with exactly these sections:
## Problem 2-4 lines, in the user's words
## Goals / Non-goals
## User stories numbered
## Acceptance criteria
AC-1 WHEN <trigger> THE SYSTEM SHALL <response>
-> test: <test file> "<test name>"
(EARS forms: WHEN / WHILE / IF ... THEN / WHERE; one behaviour per AC)
## Implementation decisions modules, interfaces, schema; cite D-n
## Code map from brief.md, file:line
## Testing decisions seams, existing tests to copy, fast check command
## Out of scope
## Verification one end-to-end check a human can run before pushing
## Open risks
3. Every acceptance criterion in the Jira ticket maps to at least one AC-n.
List any that don't, and ask about them.
4. Every AC names one test. An AC that can't be tested gets a manual check
under Verification, marked MANUAL.
5. Keep it under ~150 lines. The user must be able to review it in 5 minutes.
6. STOP and ask for approval (G2). The spec is not committed. It stays in the
untracked notes folder, and /ship copies its summary into the PR body.
KIT_EOF_3
put "$HOME/.claude/skills/slice/SKILL.md" <<'KIT_EOF_4'
---
name: slice
description: Split the approved spec into vertical slices, one slice per future commit. Use after /ticket-spec is approved, on M or L tickets.
disable-model-invocation: true
---
# Slice
Read spec.md and brief.md from docs/tickets/<KEY>-*/ (KEY from the branch).
Write tasks.md in the same folder. Each slice becomes exactly one commit.
## Rules
- Preparatory refactors come first, as their own slices, with no behaviour change.
- Each feature slice is a thin path through every layer it needs
(schema -> API -> UI -> test). It can be shown working on its own and
fits in one fresh session.
- Aim for about 100 changed lines per slice; 300 is fine; more means split it.
- Whole ticket over ~400 changed lines? Propose 2-3 PRs titled
"<KEY> (1/3) ...", each squash-merged on its own.
- Wide refactors: expand -> migrate in batches -> contract.
- Every AC in spec.md belongs to at least one slice.
## Per slice
- [ ] S-<n> <title>
ACs: AC-? Depends on: S-? | none
Files: <paths>
Existing tests to run first: <names or command>
First failing test: <file> "<name>"
Verify: <fast check, plus anything slice-specific>
Commit: "<KEY> <type>(<scope>): <summary>"
PR: 1 (or 2, 3 if split)
End the file with an empty section:
## NOTICED BUT NOT TOUCHING
## Check, then stop
Launch one subagent with only spec.md and tasks.md. It reports any AC with
no slice, any slice that contradicts the spec, and any slice that depends on
a later one. Fix what it finds.
STOP and ask: right size? right order? should any part be its own PR? (G3)
KIT_EOF_4
put "$HOME/.claude/skills/build-slice/SKILL.md" <<'KIT_EOF_5'
---
name: build-slice
description: Build the next unchecked slice of the current ticket test-first and commit it. Use once per slice, ideally in a fresh session.
disable-model-invocation: true
hooks:
Stop:
- hooks:
- type: command
command: bash "$HOME/.claude/hooks/fast-check-on-stop.sh"
timeout: 300
---
# Build one slice
1. Refuse to run on main or master. KEY comes from the branch name.
git status --porcelain must show no changes to TRACKED files
(the untracked notes folder is fine). If it does, stop and ask.
2. Read docs/tickets/<KEY>-*/: spec.md and tasks.md, and pick the first
unchecked slice. XS/S tickets have no tasks.md: brief.md is the one slice.
3. Run the existing tests for this area first. If they already fail, STOP and
tell the user. Never build on a red baseline.
4. Write ONE failing test at the agreed seam, in the style of the tests next
to it. Run it and confirm it fails for the expected reason, not a typo or
import error.
5. Write the smallest code that makes it pass. Rerun. Repeat steps 4-5 for
the slice's other ACs, one test at a time.
6. Run the fast check (the fastcheck file in the notes folder). Then read
your own diff once, looking for debug output, dead code and secrets.
7. Never edit, skip or delete an existing test without asking. Never change a
new test just to make it pass. Never write all the tests up front.
Don't fix anything outside this slice: add it under
"NOTICED BUT NOT TOUCHING" in tasks.md.
8. Stage only this slice's files, by path (never git add -A or git add .),
including any ADR or CONTEXT.md change that belongs to it.
9. Commit with the message planned in tasks.md. Tick the slice.
10. STOP and report:
- what changed, in 3-5 lines
- the new test names and the command output proving they pass
- anything surprising, and anything you were unsure about
- one sentence the user could say to a reviewer about this slice
Then suggest /clear before the next /build-slice.
KIT_EOF_5
put "$HOME/.claude/skills/ticket-review/SKILL.md" <<'KIT_EOF_6'
---
name: ticket-review
description: Review the current ticket branch with fresh-context subagents against the spec and the repo's standards. Use after all slices are built, before /ship.
disable-model-invocation: true
---
# Ticket review
You coordinate. The reviewing is done by subagents, which start with an empty
context, so they never see how the code was written.
1. git fetch origin. KEY from the branch. Notes folder: docs/tickets/<KEY>-*/.
Collect the paths only: spec.md, tasks.md, CLAUDE.md, and
CODING_STANDARDS.md or CONTRIBUTING.md if they exist.
2. Launch two subagents IN PARALLEL. Tell each: "Read-only. Do not edit files
or run tests. Run git diff origin/main...HEAD yourself and read the files
listed. Report findings as: problem . file:line . severity
(Critical / Required / Optional) . suggested fix. At most 10."
A) SPEC: for every AC, where is it implemented and which test proves it?
Report missing ACs, behaviour that differs from the spec, and scope
creep (changes no AC asks for). Check the tests really assert the AC
and are not hard-coded to pass.
B) STANDARDS: repo conventions (copy what nearby code does), error
handling, security (input, auth, secrets, injection), performance traps,
and code smells. No style preferences that a linter would not enforce.
Keep the two reports separate. Don't merge or re-rank them.
3. Then ONE subagent, not in parallel with anything: "Pick the 3 most
important conditions in the diff. For each: change it to its opposite,
run its tests, record whether a test failed, and revert it. At the end
git status --porcelain must show no tracked changes; prove it."
Any change that no test catches is a Required finding.
4. Launch one more subagent with the combined findings and the diff. It tries
to disprove each finding and drops the ones it cannot confirm.
5. Write review.md in the notes folder: confirmed findings grouped by
severity, the mutation results, and a short "dropped findings" list.
6. Show the user the Critical and Required findings and ask which to fix.
Each agreed fix becomes its own commit:
git commit --fixup=<sha of the slice it belongs to>
Optional findings are listed, not fixed, unless the user asks.
This does not replace the user's own reading of the diff before /ship.
KIT_EOF_6
put "$HOME/.claude/skills/ship/SKILL.md" <<'KIT_EOF_7'
---
name: ship
description: Tidy the slice commits, push the ticket branch, open the PR (squash-merged later into one commit on main) and comment on Jira. Use when review.md has no open Critical or Required findings.
disable-model-invocation: true
---
# Ship
The model: one commit per slice on the branch. The platform squash-merges the
PR into ONE commit on main. Never squash to one commit locally.
1. Preconditions: on feat/<KEY>-* (never main or master); git status
--porcelain shows no tracked changes; every slice is ticked; review.md has
no open Critical or Required finding. If any fails, stop and say which.
2. git fetch origin
BRANCH=$(git branch --show-current)
If git ls-remote --exit-code --heads origin "$BRANCH" succeeds, the branch
was already pushed: DO NOT rewrite history. Skip to step 6. To pick up new
commits from main, use git merge origin/main, never rebase.
3. BACKUP="backup/$(echo "$BRANCH" | tr / -)-$(date +%s)"; git branch "$BACKUP"
OLD_BASE=$(git merge-base origin/main HEAD)
4. Fold the fixups into their slices and rebase onto main, running the fast
check (fastcheck in the notes folder) on every commit:
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash -x "<fast check>" origin/main
If a check fails or a conflict stops the rebase, STOP and show the user.
Offer git rebase --abort, which restores the branch. Never continue silently.
5. Prove nothing was lost or added:
git range-diff "$OLD_BASE..$BACKUP" "origin/main..HEAD"
Expect only the fixups folded into their slices. Show it to the user.
If the commits don't match the slices, RECOMPUTE the base first:
BASE=$(git merge-base origin/main HEAD); git reset --soft "$BASE"
then recommit slice by slice (stage by path, or git apply --cached for
single hunks) and rerun the check from step 4.
6. Every commit message starts with <KEY>. No Jira smart-commit commands
(#time #comment #transition) in any message.
7. Write pr.md in the notes folder.
Line 1 = PR title = the future squash subject, in the team's format, e.g.
"<KEY> <type>(<scope>): <summary>".
The rest = PR body = the future squash body:
- Summary (3-5 lines)
- Slices: one line each
- Acceptance criteria -> evidence: table of AC-n, test name, result
- Decisions: the key D-n lines
- Risk: one-way or two-way door, blast radius, how to roll back
- Refs: <KEY>
- Co-authored-by: Claude <noreply@anthropic.com>
Also write jira-comment.txt: the PR link placeholder plus the summary.
8. SHOW the user: git log --oneline origin/main..HEAD, the PR title and body,
and the Jira comment. Wait for an explicit yes. (G4)
9. git push -u origin HEAD
If gh is available: gh pr create --title "<line 1>" --body-file <body file>
Otherwise give the user the compare URL git printed and the title and body
to paste.
10. Jira, only after a yes: put the PR URL into jira-comment.txt, then run
bash "${CLAUDE_SKILL_DIR}/jira-comment.sh" <KEY> <path to jira-comment.txt>
If it prints NO_JIRA, show the text for the user to paste. Change the
ticket status only if the user asks.
During review: every fix is a NEW commit (git commit --fixup=<sha>, then
git push). Never force-push a branch under review. Never merge the PR
yourself; a human or the merge queue squash-merges it.
After merge: git switch main && git pull --prune, then
git branch -D "$BRANCH" and the backup branch (-D because a squash leaves
no ancestry for git to see).
KIT_EOF_7
put "$HOME/.claude/skills/ship/jira-comment.sh" <<'KIT_EOF_8'
#!/usr/bin/env bash
# Posts a text file as a comment on a Jira ticket. Used by /ship after the user says yes.
# Usage: jira-comment.sh PROJ-123 path/to/comment.txt
# Uses the same ~/.jira-env as /ticket-intake. Without it, prints NO_JIRA and you paste by hand.
set -u
[ -f "$HOME/.jira-env" ] && . "$HOME/.jira-env"
key="${1:-}"; file="${2:-}"
printf '%s' "$key" | grep -Eq '^[A-Z][A-Z0-9]+-[0-9]+$' || { echo "BAD_KEY: $key"; exit 1; }
[ -f "$file" ] || { echo "NO_FILE: $file"; exit 1; }
[ -n "${JIRA_BASE_URL:-}" ] || { echo "NO_JIRA: no ~/.jira-env. Paste the comment into Jira by hand."; exit 1; }
auth_header() {
if [ -n "${JIRA_PAT:-}" ]; then
printf 'Authorization: Bearer %s\n' "$JIRA_PAT"
else
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "${JIRA_EMAIL:-}" "${JIRA_API_TOKEN:-}" | base64 | tr -d '\n')"
fi
}
# Escape the text for JSON: backslashes, quotes, tabs, carriage returns, then newlines.
body=$(sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e "s/$(printf '\t')/\\\\t/g" -e "s/$(printf '\r')//g" "$file" \
| awk '{ printf "%s\\n", $0 }')
# API v2 takes plain wiki text; v3 would need Atlassian's JSON document format.
if auth_header | curl -sf --max-time 20 -X POST -H @- \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
--data-binary "{\"body\":\"$body\"}" \
"$JIRA_BASE_URL/rest/api/2/issue/$key/comment" >/dev/null; then
echo "POSTED comment on $key"
else
echo "JIRA_POST_FAILED: paste the comment into Jira by hand."
exit 1
fi
KIT_EOF_8
put "$HOME/.claude/skills/ticket-retro/SKILL.md" <<'KIT_EOF_9'
---
name: ticket-retro
description: Log how a finished ticket went, compare against the baseline, and turn a repeated agent mistake into one rule. Use after the PR is opened or merged.
disable-model-invocation: true
argument-hint: "[JIRA-KEY]"
---
# Ticket retro
KEY is $0, or comes from the branch name. Notes folder: docs/tickets/<KEY>-*/.
1. Gather facts without asking: size and track from brief.md; number of
decisions in decisions.md; slices in tasks.md; findings per severity in
review.md; time of the first slice commit and of the push (git log);
PR created time and review rounds (gh pr view --json createdAt,reviews,
if gh is available).
2. Ask the user in ONE AskUserQuestion round: hands-on hours (their own
time, not wall-clock), whether they had to rework anything after review,
and the biggest agent mistake, if any.
3. Append ONE row to ~/ticket-log.md, outside every repo. Create the file
with this header only if it does not exist. Never rewrite earlier rows.
| date | key | size | track | start->PR (h) | hands-on (h) | slices | review C/R/O | rework | agent mistake | note |
The first row in the file is the baseline: the user's typical numbers
from before using this pipeline.
4. Read the last 5 rows. If the same kind of agent mistake appears twice,
propose ONE fix: a line for CLAUDE.md, a check in a hook, or a change to
one skill. Show the exact text and add it only after a yes.
5. Every 5th row: compare the median start->PR and hands-on hours per size
with the baseline. Say plainly if it is slower or the same. Suggest
dropping stages that didn't pay off for that size.
KIT_EOF_9
put "$HOME/.claude/hooks/git-guard.sh" <<'KIT_EOF_10'
#!/usr/bin/env bash
# PreToolUse hook for Bash: blocks risky git/gh commands and asks before destructive ones.
# Applies in every session and every skill, because a hook can't tell which skill is running.
input=$(cat)
if command -v jq >/dev/null 2>&1; then
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
else
cmd=$input # no jq: match against the raw JSON, which still contains the command text
fi
[ -n "$cmd" ] || exit 0
block() { echo "Blocked by git-guard: $1" >&2; exit 2; }
ask() {
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"git-guard: %s"}}\n' "$1"
exit 0
}
has() { printf '%s' "$cmd" | grep -Eq -- "$1"; }
S='[[:space:]]'
END='([[:space:];&|"]|$)'
branch=$(git branch --show-current 2>/dev/null)
if has "git$S.*push"; then
has "push[^;&|]*($S|:|refs/heads/)(main|master)$END" && block "never push to main/master. Push the ticket branch and open a PR."
case "$branch" in main|master) block "you are on $branch. Push from the ticket branch." ;; esac
has "push[^;&|]*(--force$END|--force=|$S-f$END|$S\+[^[:space:]])" && block "no force-push. Push fixes as new fixup! commits."
has "push[^;&|]*--force-with-lease" && ask "force-push with lease. Only OK if this branch has never been reviewed."
fi
has "--no-verify" && block "don't skip the repo's git hooks."
has "git$S+add$S+(-A|--all|\.)$END" && block "stage files by path, never all at once."
has "git$S+commit($S[^;&|]*)?$S-[a-zA-Z]*a[a-zA-Z]*$END" && block "no commit -a. Stage by path, then commit."
has "git$S+commit($S|$)" && case "$branch" in main|master) block "don't commit on $branch. Create the ticket branch first." ;; esac
has "gh$S+pr$S+merge" && block "the agent never merges PRs. A human or the merge queue does."
has "git$S+reset$S[^;&|]*--hard" && ask "reset --hard discards uncommitted work."
has "git$S+clean$S+-[a-zA-Z]*f" && ask "git clean deletes untracked files (including the ticket notes)."
has "git$S+branch$S[^;&|]*-D$END" && ask "deleting a branch with -D."
has "git$S+(checkout|restore)$S[^;&|]*$S\.$END" && ask "this throws away local changes."
has "git$S+stash$S+(drop|clear)" && ask "dropping stashed work."
exit 0
KIT_EOF_10
put "$HOME/.claude/hooks/fast-check-on-stop.sh" <<'KIT_EOF_11'
#!/usr/bin/env bash
# Stop hook, used only by /build-slice (declared in its frontmatter).
# Refuses to let the agent finish while the ticket's fast check fails.
input=$(cat)
# Second stop in a row: let it finish, so a baseline that can't go green never loops.
printf '%s' "$input" | grep -Eq '"stop_hook_active"[[:space:]]*:[[:space:]]*true' && exit 0
cd "${CLAUDE_PROJECT_DIR:-.}" 2>/dev/null || exit 0
key=$(git branch --show-current 2>/dev/null | grep -oE '[A-Z][A-Z0-9]+-[0-9]+' | head -1)
[ -n "$key" ] || exit 0
check=$(cat docs/tickets/"$key"-*/fastcheck 2>/dev/null | head -1)
[ -n "$check" ] || exit 0
if ! out=$(bash -c "$check" 2>&1); then
{
echo "Fast check failed: $check"
printf '%s\n' "$out" | tail -40
echo "Fix it, or tell the user exactly why it can't pass. Don't report the slice as done."
} >&2
exit 2
fi
exit 0
KIT_EOF_11
chmod +x "$HOME/.claude/hooks/"*.sh "$HOME/.claude/skills/"*/*.sh
echo
echo "Next, by hand: merge settings.json into ~/.claude/settings.json and append the CLAUDE.md block (see the page)."
Hooks and settings that make the rules stick
Instructions in CLAUDE.md are advisory, and hooks are deterministic (Anthropic best-practices doc). Anything the agent keeps forgetting becomes a hook or a test.
Block risky git
A PreToolUse hook always rejects:
git pushtomain/master;--forceor+refspecwithout--force-with-lease;git add -A,git add .andgit commit -a.
Hooks can't tell which skill is running, so make the rules unconditional. The full script is in the kit.
Tests gate "done"
A Stop hook runs the fast test command. If it fails, the agent keeps working instead of reporting success. Define it in /build-slice's frontmatter, or have it exit early unless source files changed. Also check stop_hook_active. Otherwise it runs the tests at the end of every turn in every stage, and loops if the baseline is already red.
Jira key everywhere
Put this in CLAUDE.md: branch feat/PROJ-123-slug, every commit message and PR title starts with the key. Rename Claude's default worktree-<name> branches, which don't contain it. No smart-commit commands in commits, because squash makes them run twice.
Squash-only main
Repo settings, not instructions: squash merging only, "Require linear history", required checks. The agent never merges to main itself (git-guard.sh blocks gh pr merge).
Context budget
Keep each session under roughly 40% of the context window (about 80k tokens of a 200k window). Start fresh between slices rather than compacting. Decide → spec is the one place to keep a single session going.
Patterns that repeat across the research
The agent finds facts, you decide
Pocock, Spec Kit and superpowers all separate the two. Questions always come with a recommended answer.
Research without the solution
Dex Horthy's 2026 update: hide the ticket from the research step so it reports what the code does, not what it guesses you want.
Vertical slices, one at a time
Tickets, tests and commits all go one thin end-to-end path at a time. Writing all the tests first was rejected by every source.
Read the code, not the plan
Humans review short upstream docs and the actual diff. Pocock's demo showed tests passing while a missing DB table only appeared in manual QA.
Fresh-context review
The reviewer gets the diff and the spec, never the writing session's history (superpowers, Anthropic, Pocock).
Write lessons back
Glossary, ADRs, CLAUDE.md. Mechanical mistakes become checks, judgement calls become standards (Pocock's retro).
Read and watched for this plan
Four research agents ran in parallel: two read every SKILL.md in the two named repos, one read the official docs and other frameworks, and one transcribed 12 new YouTube videos through the sandbox Docker Chrome. It builds on the earlier 10x Engineer survey.
Repos and docs
- mattpocock/skills: plugin v1.3.1, last commit 2026-10-09
- addyosmani/agent-skills: 25 skills, v0.6.13, last commit 2026-10-13
- github/spec-kit:
/clarify,/analyze - obra/superpowers: brainstorming, writing-plans, finishing-a-development-branch
- Claude Code best practices
- Jira Cloud REST API v2 · API tokens and basic auth
- Kiro: EARS requirements
- github/gh-stack · git-spice
- GitHub: configuring commit squashing · require linear history
- GitLab: squash and merge · LLVM: GitHub workflow
- Google: small CLs
- Atlassian: smart commits · reference issues in dev work
Videos
- Pocock: Building a REAL feature, every step
- Pocock: I stopped using /grill-me for coding
- Pocock: 9 things people get wrong with /grill-*
- Pocock: Red Green Refactor with Claude Code
- Pocock: Full walkthrough, AI Engineer Europe 2026
- Dex Horthy: What we got wrong about RPI
- Dex Horthy: No Vibes Allowed
- Spec Kit on existing projects
- Kiro spec-driven development
- BMad-Method masterclass
- Anthropic: Claude Code best practices
- Bitovi: Jira + Figma MCP with Claude Code
- GitHub: stacked PRs with gh-stack skill
Verification: studies and critiques
- METR 2025 RCT · METR 2026 update
- Stanford ~100k-developer study
- DORA 2025 report · AI capabilities model
- Cui et al., Management Science
- Harvard / Jellyfish, 718 firms · Faros 2026
- Anthropic: AI assistance and coding skills · How AI is transforming work at Anthropic
- Böckeler: Kiro, spec-kit and Tessl
- Anthropic: when to skip the plan
Verification: videos
- Stanford 100k developers (Denisov-Blanch)
- Dave Farley: we studied 150 developers using AI
- Kent Beck on The Pragmatic Engineer · Kent Beck at Prodacity 2026
- Boris Cherny on Lenny's Podcast
- Theo: you're using AI agents wrong
- John Kim (Meta): plan until it's right
- The new spec-driven workflow is a mess
- Gergely Orosz: slow down to speed up