pincer-workflow 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +79 -13
  2. package/bin/pincer.js +11 -5
  3. package/package.json +3 -3
  4. package/template/.claude/commands/pincer-code.md +35 -16
  5. package/template/.claude/commands/pincer-evaluate.md +4 -1
  6. package/template/.claude/commands/pincer-narrow.md +11 -4
  7. package/template/.claude/commands/pincer-plan.md +4 -0
  8. package/template/.claude/commands/pincer-release.md +7 -3
  9. package/template/.claude/commands/pincer-status.md +21 -0
  10. package/template/.claude/hooks/ticket-guard.sh +66 -0
  11. package/template/.claude/references/ticket-template.md +7 -1
  12. package/template/.claude/settings.json +9 -0
  13. package/template/.codex/README.md +18 -3
  14. package/template/.codex/prompts/pincer-code.md +35 -16
  15. package/template/.codex/prompts/pincer-evaluate.md +4 -1
  16. package/template/.codex/prompts/pincer-narrow.md +11 -4
  17. package/template/.codex/prompts/pincer-plan.md +4 -0
  18. package/template/.codex/prompts/pincer-release.md +7 -3
  19. package/template/.codex/prompts/pincer-status.md +19 -0
  20. package/template/.github/prompts/pincer-code.prompt.md +35 -16
  21. package/template/.github/prompts/pincer-evaluate.prompt.md +4 -1
  22. package/template/.github/prompts/pincer-narrow.prompt.md +11 -4
  23. package/template/.github/prompts/pincer-plan.prompt.md +4 -0
  24. package/template/.github/prompts/pincer-release.prompt.md +7 -3
  25. package/template/.github/prompts/pincer-status.prompt.md +23 -0
  26. package/template/AGENTS.md +8 -1
  27. package/template/docs/dry-run-checklist.md +9 -3
  28. package/template/scripts/pincer-status.sh +111 -0
  29. package/template/scripts/pincer-ticket.sh +134 -0
  30. package/template/scripts/sync-prompts.sh +2 -1
@@ -8,7 +8,10 @@ run the pipeline, then present results.
8
8
 
9
9
  ## Steps
10
10
 
11
- 1. Get the full diff of the session: `git log --oneline` and `git diff <first-commit>..HEAD`.
11
+ 1. Run `scripts/pincer-status.sh`. Every ticket should be `done` with a receipt; if one
12
+ is still open or in progress, stop and ask whether it was cut (then it goes in the
13
+ PRD's Out of Scope) or should be finished first via `/pincer-code`. Then get the full
14
+ diff of the session: `git log --oneline` and `git diff <first-commit>..HEAD`.
12
15
  2. Dispatch a `code-quality-reviewer` agent with: the diff, the PRD's Success Criteria and
13
16
  Scope sections, and the list of tickets. If the diff is large, split by area and
14
17
  dispatch two in parallel. (No subagents on this platform? Review the diff yourself
@@ -11,14 +11,19 @@ build window.
11
11
 
12
12
  ## Steps
13
13
 
14
- 1. Read the PRD (`$ARGUMENTS` or the latest `.prd/prd-v*.md`). If its status isn't
15
- `draft`, ask which PRD to use.
14
+ 1. Run `scripts/pincer-status.sh`. If tickets already exist, ask before adding to them
15
+ new tickets continue the numbering, existing ones are never renumbered. Then read the
16
+ PRD (`$ARGUMENTS` or the latest `.prd/prd-v*.md`). If its status isn't `draft`, ask
17
+ which PRD to use.
16
18
  2. Decompose into tickets. Rules:
17
19
  - Each ticket is one coherent unit: sized S or M, never L. Split anything larger.
18
20
  - Ticket 1 is always the walking skeleton: project scaffold + a thin end-to-end slice
19
21
  that runs. Everything after builds on a working base.
20
22
  - Order by dependency; note blockers explicitly ("depends on T-01").
21
- - Every ticket gets a verification command or check the builder can actually run.
23
+ - Every ticket gets a runnable command in its Verification block a fenced `bash`
24
+ block that exits 0 only when the ticket is done. `scripts/pincer-ticket.sh verify`
25
+ runs it verbatim and stamps the receipt that `done` requires, so it must be
26
+ non-interactive and self-contained (no "check by hand").
22
27
  - If the brief or stack implies automated tests, at least one ticket's verification
23
28
  command must be the test runner (e.g. `npm test`) — manual checks alone don't count.
24
29
  - Any ticket whose surface accepts external input (HTTP endpoint, form, file,
@@ -32,7 +37,9 @@ build window.
32
37
  is preceded by a characterization ticket — a test that pins the current
33
38
  behavior before any ticket is allowed to change it.
34
39
  3. Write each ticket to `tickets/T-{NN}-{slug}.md` using
35
- `.claude/references/ticket-template.md`.
40
+ `.claude/references/ticket-template.md`, with `status: open`. The other state fields
41
+ (`started`, `verified`, `finished`) are added later by `scripts/pincer-ticket.sh` —
42
+ never write them yourself.
36
43
  4. Present the ticket list (number, title, size, dependencies) as a table.
37
44
 
38
45
  **Gate (medium):** Ask for approval of the breakdown and build order. Adjust if pushed back.
@@ -9,6 +9,10 @@ timebox (~2 hours total), so discovery is brief and the PRD is lean. The PRD fee
9
9
 
10
10
  **Initial request:** $ARGUMENTS
11
11
 
12
+ First run `scripts/pincer-status.sh`. If a PRD already exists, say so and ask whether
13
+ this is a new version of it (`.prd/prd-v{N+1}.md` — old versions are never overwritten)
14
+ or a fresh start; if tickets are in progress, stop and point at `/pincer-code` instead.
15
+
12
16
  ## Phase 1: Discovery (~5 min)
13
17
 
14
18
  1. If `$ARGUMENTS` contains the brief, extract what you can before asking anything.
@@ -11,12 +11,16 @@ report pass/fail, never fix anything — fixes belong to the stage commands.
11
11
  ## Steps
12
12
 
13
13
  1. Read `docs/dry-run-checklist.md` — it is the source of truth for what to check.
14
- 2. Determine which stages have run, from the artifacts present (`.prd/`, `tickets/`,
15
- git log, `NOTES.md`). If `$ARGUMENTS` names a stage, check only up to that stage.
14
+ 2. Run `scripts/pincer-status.sh` to determine which stages have run (it reads `.prd/`,
15
+ `tickets/`, `NOTES.md`; add `git log`). If `$ARGUMENTS` names a stage, check only up
16
+ to that stage.
16
17
  3. Check every applicable item mechanically where possible:
17
18
  - File existence and frontmatter: read the files.
18
19
  - Commit format and story: `git log --oneline`.
19
- - Ticket verification commands: run at least two, report actual output.
20
+ - Receipts: every done ticket carries `verified:` (a status warning means one was
21
+ marked done by hand). Re-run at least two checks with
22
+ `scripts/pincer-ticket.sh verify T-{NN}` — on a done ticket it re-checks without
23
+ touching the receipt — and report actual output.
20
24
  4. For judgment items (tickets genuinely S/M, history reads as a story), give your
21
25
  verdict AND one sentence of evidence — never a bare pass.
22
26
  5. Present a table: checklist item | pass/fail/skipped | evidence. Order by stage.
@@ -0,0 +1,19 @@
1
+ <!-- Generated from .claude/commands/pincer-status.md by scripts/sync-prompts.sh — edit the source, not this file -->
2
+
3
+
4
+ # /pincer-status — Where are we?
5
+
6
+ You are orienting in a repo that uses PINCER, typically after a context reset or at the
7
+ start of a session. Read-only: change nothing.
8
+
9
+ ## Steps
10
+
11
+ 1. Run `scripts/pincer-status.sh`. It reads the artifacts on disk (`.prd/`, `tickets/`,
12
+ `NOTES.md`) and prints the PRD state, every ticket with its state and clock-based
13
+ elapsed time, what is blocked, build time against the budget, any warnings (a ticket
14
+ marked done without a verification receipt), and the next command to run.
15
+ 2. Report in three lines: where the workflow is, what is in progress or blocked, and the
16
+ next command. Quote the `Next` line as-is.
17
+ 3. If a ticket is `in_progress`, read it and `git status`, then offer to resume it with
18
+ `/pincer-code T-{NN}`. If the script printed a warning, surface it — a done ticket
19
+ without a receipt was marked by hand and needs `scripts/pincer-ticket.sh verify T-{NN}`.
@@ -12,40 +12,59 @@ user confirms the starting point, run continuously and report progress between t
12
12
 
13
13
  **Initial request:** ${input:request:Task brief or arguments (optional)}
14
14
 
15
+ Ticket state lives in the ticket file's frontmatter and is written **only** by
16
+ `scripts/pincer-ticket.sh` (`start` → `verify` → `done`). `verify` runs the ticket's
17
+ Verification block and stamps a receipt only on a green exit; `done` refuses without a
18
+ receipt that matches the current check, or with unticked acceptance criteria. Never edit
19
+ `status`, `started`, `verified`, or `finished` by hand — on Claude Code a hook blocks it.
20
+
21
+ ## Before the loop
22
+
23
+ Run `scripts/pincer-status.sh`. It lists every ticket's state, what is blocked, elapsed
24
+ build time from the clock, and the next action. If a ticket is `in_progress`, you are
25
+ resuming: read it, check `git status` / `git diff` for uncommitted work, and continue
26
+ from wherever the receipt says you are. Confirm the starting point with the user, then go.
27
+
15
28
  ## Loop (per ticket, in dependency order)
16
29
 
17
- 1. **Read the ticket** and the files it references. Announce: "Starting T-{NN}: {title}."
30
+ 1. **Start:** `scripts/pincer-ticket.sh start T-{NN}` — refuses while a `depends_on` ticket
31
+ isn't done, and stamps the start time. Read the ticket and the files it references.
32
+ Announce: "Starting T-{NN}: {title}."
18
33
  2. **Implement.** Follow the conventions in `CLAUDE.md` and the PRD's architecture and
19
34
  visual direction. Installing a dependency not named in the PRD's architecture is a
20
35
  stop-and-ask: verify it's the real package on the registry (linked repo, downloads —
21
- hallucinated names get typosquatted), say why it earns its place, and wait for a yes. For an S ticket, implement directly. For an M ticket touching
22
- isolated files, you may dispatch a subagent with a clean prompt: paste the full ticket
23
- body, the relevant conventions, and nothing else.
24
- 3. **Verify.** Run the ticket's verification command(s). If they fail, fix before moving
25
- on never mark a ticket done on a red check. Report actual output, not assumptions.
36
+ hallucinated names get typosquatted), say why it earns its place, and wait for a yes.
37
+ For an S ticket, implement directly. For an M ticket touching isolated files, you may
38
+ dispatch a subagent with a clean prompt: paste the full ticket body, the relevant
39
+ conventions, and nothing else.
40
+ 3. **Verify:** `scripts/pincer-ticket.sh verify T-{NN}` runs the Verification block and
41
+ writes the receipt only if it exits 0. Red → fix and re-run; report the actual output,
42
+ not assumptions. Green output is the definition of done, not your confidence.
26
43
  4. **Self-review the diff** before committing: silent failures (empty catches,
27
44
  un-awaited promises), leftover debug code, drift from the ticket's acceptance criteria.
28
45
  Then a security sweep of the same diff:
29
46
  - No secret values: run
30
- `git diff --cached | grep -iE '(api[_-]?key|secret|token|password)[[:space:]]*[:=]'`
47
+ `git diff | grep -iE '(api[_-]?key|secret|token|password)[[:space:]]*[:=]'`
31
48
  and treat any hit that isn't a `process.env` reference or a name in
32
49
  `.env.example` as a blocker.
33
50
  - External input touched by this diff is validated server-side, and untrusted
34
51
  content (user input, LLM output) is escaped where rendered — per the
35
52
  Security defaults in `CLAUDE.md`.
36
53
  - No error path leaks internals (stack traces, key names with values) to the client.
37
- 5. **Commit** with message `T-{NN}: {title}` and mark the ticket done: set
38
- `status: done` in its frontmatter and tick every verified acceptance-criteria
39
- checkbox (`- [ ]` `- [x]`) in the same edit. A ticket is never `done` with
40
- unticked criteria — if a criterion was cut, that's a scope change to record,
41
- not a box to skip.
42
- 6. Give a one-line progress update ("T-02 done, 3 remaining, ~40 min elapsed") and continue.
54
+ If the review changed code, run `verify` again the receipt must match the code you commit.
55
+ 5. **Close the ticket:** tick every verified acceptance-criteria checkbox (`- [ ]` → `- [x]`;
56
+ editing the checkboxes is allowed), then `scripts/pincer-ticket.sh done T-{NN}`. A
57
+ criterion that was cut is a scope change to record in the PRD, not a box to skip.
58
+ Commit code and ticket file together: `git add -A && git commit -m "T-{NN}: {title}"`.
59
+ 6. Give a one-line progress update using the elapsed figure from
60
+ `scripts/pincer-status.sh` ("T-02 done, 3 remaining, 38m elapsed of 75m") and continue.
43
61
 
44
62
  ## Timebox rules
45
63
 
46
- - Track elapsed time against the ~75-minute build budget. If you're at risk of running
47
- out, stop and propose a scope cut: which remaining tickets to drop or shrink. Cutting
48
- scope deliberately beats an unfinished mess record the cut in the PRD's Out of Scope.
64
+ - The budget is ~75 minutes of build time, measured by `scripts/pincer-status.sh` from
65
+ the first ticket's start stamp never estimated. If the remaining tickets won't fit,
66
+ stop and propose a scope cut: which remaining tickets to drop or shrink. Cutting scope
67
+ deliberately beats an unfinished mess — record the cut in the PRD's Out of Scope.
49
68
  - If a ticket reveals the plan was wrong, stop and say so rather than silently diverging.
50
69
  Update the ticket/PRD, then continue.
51
70
 
@@ -12,7 +12,10 @@ run the pipeline, then present results.
12
12
 
13
13
  ## Steps
14
14
 
15
- 1. Get the full diff of the session: `git log --oneline` and `git diff <first-commit>..HEAD`.
15
+ 1. Run `scripts/pincer-status.sh`. Every ticket should be `done` with a receipt; if one
16
+ is still open or in progress, stop and ask whether it was cut (then it goes in the
17
+ PRD's Out of Scope) or should be finished first via `/pincer-code`. Then get the full
18
+ diff of the session: `git log --oneline` and `git diff <first-commit>..HEAD`.
16
19
  2. Dispatch a `code-quality-reviewer` agent with: the diff, the PRD's Success Criteria and
17
20
  Scope sections, and the list of tickets. If the diff is large, split by area and
18
21
  dispatch two in parallel. (No subagents on this platform? Review the diff yourself
@@ -15,14 +15,19 @@ build window.
15
15
 
16
16
  ## Steps
17
17
 
18
- 1. Read the PRD (`${input:request:Task brief or arguments (optional)}` or the latest `.prd/prd-v*.md`). If its status isn't
19
- `draft`, ask which PRD to use.
18
+ 1. Run `scripts/pincer-status.sh`. If tickets already exist, ask before adding to them
19
+ new tickets continue the numbering, existing ones are never renumbered. Then read the
20
+ PRD (`${input:request:Task brief or arguments (optional)}` or the latest `.prd/prd-v*.md`). If its status isn't `draft`, ask
21
+ which PRD to use.
20
22
  2. Decompose into tickets. Rules:
21
23
  - Each ticket is one coherent unit: sized S or M, never L. Split anything larger.
22
24
  - Ticket 1 is always the walking skeleton: project scaffold + a thin end-to-end slice
23
25
  that runs. Everything after builds on a working base.
24
26
  - Order by dependency; note blockers explicitly ("depends on T-01").
25
- - Every ticket gets a verification command or check the builder can actually run.
27
+ - Every ticket gets a runnable command in its Verification block a fenced `bash`
28
+ block that exits 0 only when the ticket is done. `scripts/pincer-ticket.sh verify`
29
+ runs it verbatim and stamps the receipt that `done` requires, so it must be
30
+ non-interactive and self-contained (no "check by hand").
26
31
  - If the brief or stack implies automated tests, at least one ticket's verification
27
32
  command must be the test runner (e.g. `npm test`) — manual checks alone don't count.
28
33
  - Any ticket whose surface accepts external input (HTTP endpoint, form, file,
@@ -36,7 +41,9 @@ build window.
36
41
  is preceded by a characterization ticket — a test that pins the current
37
42
  behavior before any ticket is allowed to change it.
38
43
  3. Write each ticket to `tickets/T-{NN}-{slug}.md` using
39
- `.claude/references/ticket-template.md`.
44
+ `.claude/references/ticket-template.md`, with `status: open`. The other state fields
45
+ (`started`, `verified`, `finished`) are added later by `scripts/pincer-ticket.sh` —
46
+ never write them yourself.
40
47
  4. Present the ticket list (number, title, size, dependencies) as a table.
41
48
 
42
49
  **Gate (medium):** Ask for approval of the breakdown and build order. Adjust if pushed back.
@@ -13,6 +13,10 @@ timebox (~2 hours total), so discovery is brief and the PRD is lean. The PRD fee
13
13
 
14
14
  **Initial request:** ${input:request:Task brief or arguments (optional)}
15
15
 
16
+ First run `scripts/pincer-status.sh`. If a PRD already exists, say so and ask whether
17
+ this is a new version of it (`.prd/prd-v{N+1}.md` — old versions are never overwritten)
18
+ or a fresh start; if tickets are in progress, stop and point at `/pincer-code` instead.
19
+
16
20
  ## Phase 1: Discovery (~5 min)
17
21
 
18
22
  1. If `${input:request:Task brief or arguments (optional)}` contains the brief, extract what you can before asking anything.
@@ -15,12 +15,16 @@ report pass/fail, never fix anything — fixes belong to the stage commands.
15
15
  ## Steps
16
16
 
17
17
  1. Read `docs/dry-run-checklist.md` — it is the source of truth for what to check.
18
- 2. Determine which stages have run, from the artifacts present (`.prd/`, `tickets/`,
19
- git log, `NOTES.md`). If `${input:request:Task brief or arguments (optional)}` names a stage, check only up to that stage.
18
+ 2. Run `scripts/pincer-status.sh` to determine which stages have run (it reads `.prd/`,
19
+ `tickets/`, `NOTES.md`; add `git log`). If `${input:request:Task brief or arguments (optional)}` names a stage, check only up
20
+ to that stage.
20
21
  3. Check every applicable item mechanically where possible:
21
22
  - File existence and frontmatter: read the files.
22
23
  - Commit format and story: `git log --oneline`.
23
- - Ticket verification commands: run at least two, report actual output.
24
+ - Receipts: every done ticket carries `verified:` (a status warning means one was
25
+ marked done by hand). Re-run at least two checks with
26
+ `scripts/pincer-ticket.sh verify T-{NN}` — on a done ticket it re-checks without
27
+ touching the receipt — and report actual output.
24
28
  4. For judgment items (tickets genuinely S/M, history reads as a story), give your
25
29
  verdict AND one sentence of evidence — never a bare pass.
26
30
  5. Present a table: checklist item | pass/fail/skipped | evidence. Order by stage.
@@ -0,0 +1,23 @@
1
+ ---
2
+ mode: agent
3
+ description: "Where the workflow stands — PRD, tickets, receipts, elapsed time, next action"
4
+ ---
5
+ <!-- Generated from .claude/commands/pincer-status.md by scripts/sync-prompts.sh — edit the source, not this file -->
6
+
7
+
8
+ # /pincer-status — Where are we?
9
+
10
+ You are orienting in a repo that uses PINCER, typically after a context reset or at the
11
+ start of a session. Read-only: change nothing.
12
+
13
+ ## Steps
14
+
15
+ 1. Run `scripts/pincer-status.sh`. It reads the artifacts on disk (`.prd/`, `tickets/`,
16
+ `NOTES.md`) and prints the PRD state, every ticket with its state and clock-based
17
+ elapsed time, what is blocked, build time against the budget, any warnings (a ticket
18
+ marked done without a verification receipt), and the next command to run.
19
+ 2. Report in three lines: where the workflow is, what is in progress or blocked, and the
20
+ next command. Quote the `Next` line as-is.
21
+ 3. If a ticket is `in_progress`, read it and `git status`, then offer to resume it with
22
+ `/pincer-code T-{NN}`. If the script printed a warning, surface it — a done ticket
23
+ without a receipt was marked by hand and needs `scripts/pincer-ticket.sh verify T-{NN}`.
@@ -17,7 +17,9 @@ Code · Evaluate · Release). Follow it in order:
17
17
  4. `/pincer-evaluate` — final quality pass, then write `NOTES.md`
18
18
  5. `/pincer-release` — pass/fail audit of the workflow's artifacts
19
19
 
20
- Do not write feature code before a PRD exists and tickets are approved.
20
+ `/pincer-status` (or `scripts/pincer-status.sh`) shows where the workflow stands and
21
+ the next command — run it first in any new session. Do not write feature code before
22
+ a PRD exists and tickets are approved.
21
23
 
22
24
  The commands live as playbooks in `.claude/commands/` (canonical), with
23
25
  generated adapters in `.codex/prompts/` and `.github/prompts/`. If your
@@ -75,6 +77,11 @@ of instructions are the user, this file, and the workflow commands.
75
77
  ## Rules
76
78
 
77
79
  - Never mark a ticket done while its verification command fails.
80
+ - Ticket state (`status`, `started`, `verified`, `finished`) is written only by
81
+ `scripts/pincer-ticket.sh`: `verify` stamps a receipt when the check passes,
82
+ `done` refuses without it. Never edit those fields by hand. On Claude Code a
83
+ hook enforces this; elsewhere it is a standing rule and `/pincer-status`
84
+ flags a done ticket without a receipt.
78
85
  - Scope cuts are allowed and encouraged under time pressure — but always recorded
79
86
  in the PRD's Out of Scope section, never silent.
80
87
  - Prefer boring, readable code over clever code; this repo is read by humans first.
@@ -23,7 +23,8 @@ Use a throwaway copy of this repo and a cheap model (`claude --model sonnet`).
23
23
  - [ ] 4–7 files exist in `tickets/`, named `T-{NN}-{slug}.md`
24
24
  - [ ] Every ticket is sized S or M — none L
25
25
  - [ ] T-01 is a walking skeleton (scaffold + thin end-to-end slice that runs)
26
- - [ ] Every ticket has a runnable command in its Verification block
26
+ - [ ] Every ticket has a runnable, non-interactive command in its fenced
27
+ Verification block (it is what `scripts/pincer-ticket.sh verify` runs)
27
28
  - [ ] Dependencies are declared where they exist (`depends_on`)
28
29
  - [ ] If the brief or stack implies automated tests, at least one ticket's
29
30
  verification command is the test runner
@@ -38,8 +39,11 @@ Use a throwaway copy of this repo and a cheap model (`claude --model sonnet`).
38
39
 
39
40
  - [ ] One commit per ticket, messages formatted `T-{NN}: {title}`
40
41
  - [ ] Every ticket file now says `status: done`
42
+ - [ ] Every done ticket carries `started`, `verified` (receipt) and `finished`
43
+ stamps — `scripts/pincer-status.sh` prints no "done without a receipt" warning
41
44
  - [ ] Every done ticket has all acceptance-criteria checkboxes ticked
42
- - [ ] Running each ticket's verification command passes (spot-check at least two)
45
+ - [ ] `scripts/pincer-ticket.sh verify T-{NN}` passes on done tickets (spot-check
46
+ at least two)
43
47
  - [ ] Any scope cut made during build is recorded in the PRD's Out of Scope section
44
48
  - [ ] PRD frontmatter now says `status: built`
45
49
 
@@ -69,4 +73,6 @@ Use a throwaway copy of this repo and a cheap model (`claude --model sonnet`).
69
73
  before being modified
70
74
  - [ ] Platform adapters in sync: `scripts/sync-prompts.sh` then `git status`
71
75
  shows no changes in `.codex/prompts/` or `.github/prompts/`
72
- - [ ] Total wall-clock time fit the ~2-hour budget (note where time went if not)
76
+ - [ ] `scripts/pincer-status.sh` says `Next /pincer-release` and its build elapsed
77
+ figure fit the ~75-minute build budget (note where time went if not)
78
+ - [ ] Total wall-clock time fit the ~2-hour budget
@@ -0,0 +1,111 @@
1
+ #!/bin/bash
2
+ # PINCER status — where the workflow stands, read from the artifacts on disk.
3
+ # Read-only. Every playbook runs this first; /pincer-status wraps it.
4
+ #
5
+ # scripts/pincer-status.sh
6
+ #
7
+ # Elapsed times come from the `started` / `finished` stamps that
8
+ # scripts/pincer-ticket.sh writes, i.e. from the clock — never estimated.
9
+ # Build budget: PINCER_BUILD_BUDGET_MIN (default 75).
10
+ set -uo pipefail
11
+
12
+ ROOT=${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}
13
+ cd "$ROOT"
14
+ BUDGET=${PINCER_BUILD_BUDGET_MIN:-75}
15
+ NOW=$(date -u +%s)
16
+
17
+ fm_get() { # file key -> value with any inline comment stripped; empty if absent
18
+ awk -v k="$2" '
19
+ NR == 1 && $0 != "---" { exit }
20
+ NR > 1 && $0 == "---" { exit }
21
+ NR > 1 && index($0, k ":") == 1 {
22
+ v = substr($0, length(k) + 2); sub(/#.*/, "", v)
23
+ gsub(/^[ \t]+|[ \t]+$/, "", v); print v; exit
24
+ }' "$1"
25
+ }
26
+ to_epoch() { # ISO-8601 UTC -> seconds (GNU date, then BSD date)
27
+ date -u -d "$1" +%s 2>/dev/null || date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$1" +%s 2>/dev/null || echo 0
28
+ }
29
+ mins() { echo "$(( ($2 - $1) / 60 ))m"; }
30
+ hhmm() { [ -n "$1" ] && printf '%s' "$1" | cut -c12-16 || printf '—'; }
31
+
32
+ echo "PINCER status · $(date -u +%Y-%m-%dT%H:%MZ) · $ROOT"
33
+
34
+ # ── PRD ──
35
+ prd=$(ls .prd/prd-v*.md 2>/dev/null | sort -V | tail -1 || true)
36
+ prd_status=""
37
+ if [ -z "$prd" ]; then
38
+ echo "PRD none"
39
+ else
40
+ prd_status=$(fm_get "$prd" status)
41
+ echo "PRD $prd · status: ${prd_status:-?} · date: $(fm_get "$prd" date)"
42
+ fi
43
+
44
+ # ── Tickets ──
45
+ n_open=0; n_prog=0; n_done=0; first_start=""; in_prog=""; next_open=""; warn=""
46
+ files=$(ls tickets/T-[0-9]*.md 2>/dev/null | sort || true)
47
+ if [ -z "$files" ]; then
48
+ echo "Tickets none"
49
+ else
50
+ rows=""
51
+ for f in $files; do
52
+ id=$(fm_get "$f" ticket); [ -n "$id" ] || id=$(basename "$f" | cut -c1-4)
53
+ st=$(fm_get "$f" status); size=$(fm_get "$f" size)
54
+ started=$(fm_get "$f" started); verified=$(fm_get "$f" verified); finished=$(fm_get "$f" finished)
55
+ deps=$(fm_get "$f" depends_on | grep -oE 'T-[0-9]+' | tr '\n' ' ' || true)
56
+ if [ -n "$started" ]; then
57
+ se=$(to_epoch "$started")
58
+ [ -z "$first_start" ] || [ "$se" -lt "$first_start" ] && first_start=$se
59
+ fi
60
+ case "$st" in
61
+ done)
62
+ n_done=$((n_done + 1))
63
+ detail="started $(hhmm "$started") · finished $(hhmm "$finished")"
64
+ [ -n "$started" ] && [ -n "$finished" ] && detail="$detail ($(mins "$(to_epoch "$started")" "$(to_epoch "$finished")"))"
65
+ [ -n "$verified" ] || warn="$warn WARN $id is done without a verification receipt — was it marked done by hand?\n"
66
+ ;;
67
+ in_progress)
68
+ n_prog=$((n_prog + 1)); in_prog="$in_prog $id"
69
+ detail="started $(hhmm "$started")"
70
+ [ -n "$started" ] && detail="$detail · elapsed $(mins "$(to_epoch "$started")" "$NOW")"
71
+ if [ -n "$verified" ]; then detail="$detail · receipt ✓"; else detail="$detail · no receipt yet"; fi
72
+ ;;
73
+ *)
74
+ n_open=$((n_open + 1)); st=${st:-open}
75
+ blocked=""
76
+ for d in $deps; do
77
+ df=$(ls "tickets/$d"-*.md 2>/dev/null | head -1 || true)
78
+ [ -n "$df" ] && [ "$(fm_get "$df" status)" = done ] || blocked="$blocked $d"
79
+ done
80
+ if [ -n "$blocked" ]; then detail="blocked by${blocked}"; else detail="ready"; [ -n "$next_open" ] || next_open=$id; fi
81
+ ;;
82
+ esac
83
+ rows="$rows$(printf ' %-5s %-12s %-2s %s' "$id" "$st" "${size:-?}" "$detail")\n"
84
+ done
85
+ echo "Tickets $((n_open + n_prog + n_done)) total · $n_done done · $n_prog in progress · $n_open open"
86
+ printf '%b' "$rows"
87
+ printf '%b' "$warn"
88
+ if [ -n "$first_start" ]; then
89
+ echo "Build elapsed $(mins "$first_start" "$NOW") since the first ticket started · budget ${BUDGET}m"
90
+ fi
91
+ fi
92
+
93
+ [ -f NOTES.md ] && notes=yes || notes=no
94
+ echo "Notes NOTES.md: $notes"
95
+
96
+ # ── Next action ──
97
+ if [ -z "$prd" ]; then
98
+ next="/pincer-plan <brief> — no PRD yet"
99
+ elif [ -z "$files" ]; then
100
+ next="/pincer-narrow — PRD exists, no tickets yet"
101
+ elif [ -n "$in_prog" ]; then
102
+ next="resume${in_prog}: /pincer-code${in_prog} (check git status for uncommitted work; then verify → done)"
103
+ elif [ "$n_open" -gt 0 ]; then
104
+ next="/pincer-code — next ready ticket: ${next_open:-none (all remaining are blocked — check depends_on)}"
105
+ elif [ "$notes" = no ]; then
106
+ next="/pincer-evaluate — all tickets done"
107
+ [ "$prd_status" = built ] || next="$next (PRD status is '${prd_status:-?}', expected 'built')"
108
+ else
109
+ next="/pincer-release — evaluate has run (NOTES.md exists); audit the artifacts"
110
+ fi
111
+ echo "Next $next"
@@ -0,0 +1,134 @@
1
+ #!/bin/bash
2
+ # PINCER ticket state machine — the ONLY writer of a ticket's state fields
3
+ # (status / started / verified / finished). Works on every platform; on Claude
4
+ # Code a PreToolUse hook (.claude/hooks/ticket-guard.sh) blocks hand edits of
5
+ # those fields so "done" can only be reached through a passing check.
6
+ #
7
+ # scripts/pincer-ticket.sh start T-03 open -> in_progress; refuses while a depends_on ticket isn't done
8
+ # scripts/pincer-ticket.sh verify T-03 runs the ticket's Verification block; exit 0 stamps a receipt
9
+ # scripts/pincer-ticket.sh done T-03 needs a receipt matching the current Verification block
10
+ # and no unticked acceptance criteria; in_progress -> done
11
+ #
12
+ # The receipt is `verified: <UTC time> <12-hex hash of the Verification block>`.
13
+ # Change the check after it passed and `done` refuses until it passes again.
14
+ set -euo pipefail
15
+
16
+ ROOT=${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}
17
+ cd "$ROOT"
18
+
19
+ die() { printf 'pincer-ticket: %b\n' "$*" >&2; exit 1; }
20
+ now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
21
+ sha256() { if command -v sha256sum >/dev/null; then sha256sum; else shasum -a 256; fi; }
22
+
23
+ normalize() { # T-3 / t-03 / 3 -> T-03
24
+ local n=${1#T-}; n=${n#t-}
25
+ [[ $n =~ ^[0-9]+$ ]] || die "not a ticket id: $1"
26
+ printf 'T-%02d' "$((10#$n))"
27
+ }
28
+
29
+ ticket_file() { # id -> tickets/T-NN-*.md (exactly one)
30
+ local id; id=$(normalize "$1")
31
+ local matches; matches=$(ls "tickets/$id"-*.md 2>/dev/null || true)
32
+ [ -n "$matches" ] || die "no ticket file tickets/$id-*.md"
33
+ [ "$(printf '%s\n' "$matches" | wc -l)" -eq 1 ] || die "several files match tickets/$id-*.md"
34
+ printf '%s' "$matches"
35
+ }
36
+
37
+ fm_get() { # file key -> value with any inline comment stripped; empty if absent
38
+ awk -v k="$2" '
39
+ NR == 1 && $0 != "---" { exit }
40
+ NR > 1 && $0 == "---" { exit }
41
+ NR > 1 && index($0, k ":") == 1 {
42
+ v = substr($0, length(k) + 2); sub(/#.*/, "", v)
43
+ gsub(/^[ \t]+|[ \t]+$/, "", v); print v; exit
44
+ }' "$1"
45
+ }
46
+
47
+ fm_set() { # file key value — replace inside the frontmatter (keeping an inline comment) or add before the closing ---
48
+ local tmp; tmp=$(mktemp)
49
+ awk -v k="$2" -v v="$3" '
50
+ NR == 1 { print; next }
51
+ !closed && !done && index($0, k ":") == 1 {
52
+ c = $0; if (sub(/^[^#]*#/, "#", c)) print k ": " v " " c; else print k ": " v
53
+ done = 1; next
54
+ }
55
+ !closed && $0 == "---" { if (!done) print k ": " v; closed = 1 }
56
+ { print }' "$1" > "$tmp" && mv "$tmp" "$1"
57
+ }
58
+
59
+ verification_cmds() { # the fenced block under "## Verification"
60
+ awk '
61
+ /^## Verification/ { inv = 1; next }
62
+ inv && /^## / { exit }
63
+ inv && /^```/ { if (inb) exit; inb = 1; next }
64
+ inb { print }' "$1"
65
+ }
66
+
67
+ verify_hash() { verification_cmds "$1" | sha256 | cut -c1-12; }
68
+
69
+ unticked() { # unticked boxes inside "## Acceptance Criteria"
70
+ awk '
71
+ /^## Acceptance Criteria/ { ina = 1; next }
72
+ ina && /^## / { exit }
73
+ ina && /^- \[ \]/ { print }' "$1"
74
+ }
75
+
76
+ cmd_start() {
77
+ local f id st dep df
78
+ f=$(ticket_file "$1"); id=$(normalize "$1"); st=$(fm_get "$f" status)
79
+ case "$st" in
80
+ in_progress) echo "$id already in progress (started $(fm_get "$f" started))"; return 0 ;;
81
+ done) die "$id is already done" ;;
82
+ esac
83
+ for dep in $(fm_get "$f" depends_on | grep -oE 'T-[0-9]+' || true); do
84
+ df=$(ticket_file "$dep")
85
+ [ "$(fm_get "$df" status)" = done ] || die "$id depends on $dep, which is '$(fm_get "$df" status)' — finish $dep first (or fix depends_on in $f)"
86
+ done
87
+ fm_set "$f" status in_progress
88
+ [ -n "$(fm_get "$f" started)" ] || fm_set "$f" started "$(now)"
89
+ echo "▶ $id started $(fm_get "$f" started) — $f"
90
+ }
91
+
92
+ cmd_verify() {
93
+ local f id st cmds rc
94
+ f=$(ticket_file "$1"); id=$(normalize "$1"); st=$(fm_get "$f" status)
95
+ case "$st" in
96
+ open) cmd_start "$id" ;;
97
+ done) echo "$id is done — re-running its check (receipt left unchanged)" ;;
98
+ esac
99
+ cmds=$(verification_cmds "$f")
100
+ printf '%s\n' "$cmds" | grep -vE '^[[:space:]]*(#|$)' >/dev/null || die "no runnable command in the Verification block of $f"
101
+ echo "── $id verification ──"
102
+ printf '%s\n' "$cmds" | sed 's/^/ $ /'
103
+ set +e; bash -eo pipefail -c "$cmds"; rc=$?; set -e
104
+ if [ "$rc" -ne 0 ]; then
105
+ echo "✗ $id verification FAILED (exit $rc) — no receipt written. Fix, then re-run." >&2
106
+ exit "$rc"
107
+ fi
108
+ [ "$st" = done ] || fm_set "$f" verified "$(now) $(verify_hash "$f")"
109
+ echo "✓ $id verified — receipt: $(fm_get "$f" verified)"
110
+ }
111
+
112
+ cmd_done() {
113
+ local f id st rec cur u slug
114
+ f=$(ticket_file "$1"); id=$(normalize "$1"); st=$(fm_get "$f" status)
115
+ [ "$st" = done ] && { echo "$id already done"; return 0; }
116
+ [ "$st" = in_progress ] || die "$id is '$st' — run '$0 verify $id' first"
117
+ rec=$(fm_get "$f" verified)
118
+ [ -n "$rec" ] || die "no verification receipt on $id — run '$0 verify $id' and get a green check first"
119
+ cur=$(verify_hash "$f")
120
+ [ "${rec##* }" = "$cur" ] || die "receipt hash ${rec##* } does not match the current Verification block ($cur): the check changed after it passed — run '$0 verify $id' again"
121
+ u=$(unticked "$f")
122
+ [ -z "$u" ] || die "unticked acceptance criteria on $id:\n$u\nTick each verified criterion; a criterion that was cut is a scope change to record in the PRD, not a box to skip."
123
+ fm_set "$f" status done
124
+ fm_set "$f" finished "$(now)"
125
+ slug=$(basename "$f" .md); slug=${slug#T-[0-9][0-9]-}
126
+ echo "✓ $id done. Commit it with the code: git add -A && git commit -m \"$id: ${slug//-/ }\""
127
+ }
128
+
129
+ case "${1:-}" in
130
+ start) [ $# -eq 2 ] || die "usage: $0 start T-NN"; cmd_start "$2" ;;
131
+ verify) [ $# -eq 2 ] || die "usage: $0 verify T-NN"; cmd_verify "$2" ;;
132
+ done) [ $# -eq 2 ] || die "usage: $0 done T-NN"; cmd_done "$2" ;;
133
+ *) sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;;
134
+ esac
@@ -4,7 +4,8 @@
4
4
  # of truth: edit them, re-run this script, commit the result.
5
5
  #
6
6
  # .codex/prompts/pincer-*.md — Codex CLI custom prompts
7
- # (install: cp .codex/prompts/*.md ~/.codex/prompts/)
7
+ # (install: mkdir -p ~/.codex/prompts && cp .codex/prompts/*.md ~/.codex/prompts/;
8
+ # invoked as /prompts:pincer-*)
8
9
  # .github/prompts/pincer-*.prompt.md — VS Code Copilot prompt files
9
10
  # (enable: "chat.promptFiles": true)
10
11
  set -euo pipefail