pincer-workflow 0.3.0 → 0.4.0

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 (31) hide show
  1. package/README.md +6 -2
  2. package/bin/pincer.js +1 -1
  3. package/package.json +2 -2
  4. package/template/.agents/skills/pincer-code/SKILL.md +46 -4
  5. package/template/.agents/skills/pincer-evaluate/SKILL.md +64 -13
  6. package/template/.agents/skills/pincer-narrow/SKILL.md +46 -7
  7. package/template/.agents/skills/pincer-plan/SKILL.md +30 -1
  8. package/template/.agents/skills/pincer-release/SKILL.md +17 -3
  9. package/template/.agents/skills/pincer-status/SKILL.md +5 -3
  10. package/template/.claude/commands/pincer-code.md +46 -4
  11. package/template/.claude/commands/pincer-evaluate.md +64 -13
  12. package/template/.claude/commands/pincer-narrow.md +46 -7
  13. package/template/.claude/commands/pincer-plan.md +30 -1
  14. package/template/.claude/commands/pincer-release.md +17 -3
  15. package/template/.claude/commands/pincer-status.md +5 -3
  16. package/template/.claude/hooks/hook-policy.cjs +101 -8
  17. package/template/.claude/references/prd-template.md +38 -6
  18. package/template/.claude/references/ticket-template.md +15 -0
  19. package/template/.github/prompts/pincer-code.prompt.md +46 -4
  20. package/template/.github/prompts/pincer-evaluate.prompt.md +64 -13
  21. package/template/.github/prompts/pincer-narrow.prompt.md +46 -7
  22. package/template/.github/prompts/pincer-plan.prompt.md +30 -1
  23. package/template/.github/prompts/pincer-release.prompt.md +17 -3
  24. package/template/.github/prompts/pincer-status.prompt.md +5 -3
  25. package/template/AGENTS.md +9 -2
  26. package/template/docs/dry-run-checklist.md +126 -14
  27. package/template/docs/release-checklist.md +6 -5
  28. package/template/scripts/pincer-evidence.cjs +292 -0
  29. package/template/scripts/pincer-status.sh +21 -5
  30. package/template/scripts/pincer-ticket-lib.sh +56 -5
  31. package/template/scripts/pincer-ticket.sh +1 -1
@@ -18,13 +18,26 @@ run the pipeline, then present results.
18
18
  its ticket commits and recorded context. If it cannot be established, resolve
19
19
  that uncertainty before claiming a complete review. Record full commit IDs for
20
20
  `base` and `candidate` (`git rev-parse HEAD`), then review `git diff <base>..<candidate>`.
21
- Require a clean candidate before review, excluding only the notes being written.
21
+ The candidate is the clean, committed tree that already includes the implementation,
22
+ the ticket closures and the PRD `status: built` commit: `git status --short` must be
23
+ empty before review. If anything is uncommitted or the PRD is not yet built, return
24
+ to `/pincer-code`; do not review a dirty tree.
22
25
  2. Dispatch a `code-quality-reviewer` agent with: the diff, the PRD's Success Criteria and
23
26
  Scope sections, and the list of tickets. If the diff is large, split by area and
24
27
  dispatch two in parallel. (No subagents on this platform? Review the diff yourself
25
28
  in a separate pass, applying `.claude/agents/code-quality-reviewer.md` as the rubric.)
26
- 3. Yourself, in parallel, check spec compliance: does what was built match every ticket's
27
- acceptance criteria and the PRD scope? List any gaps.
29
+ Keep the reviewer's report or its explicit no-findings statement for step 9,
30
+ where it is saved as an artifact; a review that left no record cannot be audited.
31
+ 3. Yourself, in parallel, check spec compliance. For every requirement `R-NN` in the
32
+ PRD record one disposition: `delivered` (evidence on this candidate), `blocked`
33
+ (required behavior failed or was left unverified — this blocks PASS; do not relabel
34
+ it a known limitation to pass), or `deferred` (only with explicit user authorization;
35
+ record the scope decision in the PRD's Out of Scope and evaluate the revised
36
+ candidate). Compare what was built against every ticket's acceptance criteria and
37
+ the PRD scope, and list any gaps. Whether the requirement map is complete and each
38
+ check is semantically adequate is your judgment as reviewer — record that judgment
39
+ in the evaluation; the kit is not a mechanical traceability engine and does not
40
+ validate requirement-revision impact.
28
41
  4. If the project has a UI, look at it — don't only read the code. Start it, open it in
29
42
  the browser (screenshot via Chrome DevTools MCP if available), and check it against
30
43
  the PRD's Visual Direction and Success Criteria. Note anything visibly broken or off.
@@ -42,28 +55,66 @@ run the pipeline, then present results.
42
55
  concrete bugs, silent failures, misleading code. Drop nitpicks and style opinions.
43
56
  7. Present findings as a short list with `file:line` references, ordered by severity.
44
57
  Security findings always rank above style-adjacent ones. For each, say whether you
45
- recommend fixing now (within the timebox) or noting as known-issue.
58
+ recommend fixing now through a ticket or recording it as a known issue.
46
59
  8. Fix findings clearly within the authorized PRD through a new ticket associated with
47
60
  that PRD. Use `pincer-ticket.sh` to start, verify, and close it, then make a scoped
48
61
  `T-{NN}: {title}` commit. Ask only when a fix changes scope, architecture, or another
49
- material decision; never make an ad-hoc `review: fixes` commit.
50
- 9. Close out: write a brief `NOTES.md` at the repo root with frontmatter:
62
+ material decision; never make an ad-hoc `review: fixes` commit. Every fix commit
63
+ produces a new candidate: re-record `candidate`, re-run the checks against it, and
64
+ write fresh evidence in step 9 — never reuse a manifest from a previous candidate.
65
+ 9. Persist evidence for the candidate under `.prd/evidence/prd-vN/<candidate>/`:
66
+ - `checks/C-NN.log` — the command and a redacted summary or safe log of each
67
+ executable check. Never secrets, never an environment dump.
68
+ - `visual/<scenario>.png` — each visual capture from step 4, with its scenario,
69
+ viewport and observed result recorded in the manifest. When nothing renders,
70
+ record `visual_review: {applicable: false, reason}` and say why.
71
+ - `review/code-quality.md` — the reviewer's findings from step 2 with their
72
+ dispositions, or its explicit no-findings statement, recorded as a check of
73
+ kind `review` and referenced by the requirements it covers.
74
+ - `manifest.json` — evidence schema 1 (field list in the header of
75
+ `scripts/pincer-evidence.cjs`): selected PRD, full `base` and `candidate` IDs,
76
+ `created`, `environment` with tool limitations, `coverage_review` (your judgment
77
+ from step 3), one `requirements` entry per `R-NN` with its disposition, tickets
78
+ and check IDs, one `checks` entry per check with `kind`, `required`, `result`,
79
+ `command`, timestamp and artifact paths, and `artifacts` with digests from
80
+ `node scripts/pincer-evidence.cjs digest <file>...`.
81
+ A tool you cannot run yields a check with `result: unverified` and a note — never a
82
+ fabricated artifact. A deferred requirement carries `authorized_by` naming the
83
+ user's explicit authorization. Then run
84
+ `node scripts/pincer-evidence.cjs validate .prd/evidence/prd-vN/<candidate>/manifest.json --candidate <candidate> --prd .prd/prd-vN.md`
85
+ and correct the manifest until it prints `ok`; the same validator runs in status
86
+ and release. It checks the record's consistency, not that the commands ran.
87
+ 10. Close out: write a brief `NOTES.md` at the repo root with frontmatter:
51
88
  ```yaml
52
89
  ---
53
90
  prd: .prd/prd-vN.md
54
91
  base: <full reviewed base commit ID>
55
92
  candidate: <full reviewed candidate commit ID>
93
+ evidence: .prd/evidence/prd-vN/<candidate>/manifest.json
56
94
  ---
57
95
  ```
58
- Record the candidate before the separate NOTES commit. Status accepts a later
59
- commit only when its diff from the candidate changes solely `NOTES.md`; changes
60
- to source, tickets, or PRD require reevaluation. Legacy notes without these
61
- references do not establish readiness. Then describe what was built, what was cut
96
+ Then commit NOTES.md, the manifest and its listed artifacts and nothing else
97
+ as `evaluate: PRD vN candidate <short sha>`. Status accepts this later commit only
98
+ when its diff from the candidate is limited to `NOTES.md` and the evidence files
99
+ the manifest lists; changes to source, tests, configuration, tickets, the PRD or
100
+ other evaluations require reevaluation. Legacy notes without these references
101
+ do not establish readiness. Then describe what was built, what was cut
62
102
  and why, known issues, and what you'd do next with more time. Then a **Handover**
63
103
  section, written for the stranger who inherits this repo in six months: how to get
64
104
  oriented (which file to read first), what each dependency is for and why it earned
65
105
  its place, and what breaks first as the code ages (the riskiest assumption, the
66
- least-tested path). Commit it. This is the first document a reviewer of this repo
67
- should read.
68
- 10. Suggest `/pincer-release` as the final step: "Run `/pincer-release` for a pass/fail audit of the
106
+ least-tested path). This is the first document a reviewer of this repo should read;
107
+ summarize the requirement dispositions from the manifest in it.
108
+ 11. Suggest `/pincer-release` as the final step: "Run `/pincer-release` for a pass/fail audit of the
69
109
  whole workflow's artifacts."
110
+
111
+ ## Authorization rule (shared by plan, narrow, code and evaluate)
112
+
113
+ Reuse explicit authorization for the same scope and decisions; ask only about a
114
+ material choice not already authorized, and prepare the concrete proposal before
115
+ asking. A decision the user delegated (for example "pick the architecture") does not
116
+ need another approval when you exercise it, but a newly discovered consequential
117
+ choice is surfaced before implementation. Record the authorization basis and the
118
+ scope it covers in the PRD or the handover. An agent-written record or a status
119
+ field is not authenticated human approval. When resuming without the context that
120
+ granted authorization, do not invent it — ask.
@@ -7,7 +7,10 @@ argument-hint: "Path to PRD (optional — defaults to the latest in .prd/)"
7
7
 
8
8
  You are decomposing the PRD into coherent, independently verifiable tickets stored as
9
9
  local markdown files (no external tracker needed). Ticket count and size follow the
10
- change's dependencies and risk, plus any budget the user supplied.
10
+ change's dependencies and risk, plus any budget the user supplied. There is no hard
11
+ one-to-two-ticket cap for small PRDs and no default timebox: cohesion and dependencies
12
+ decide the count. A breakdown that follows the PRD needs no second approval; a newly
13
+ discovered consequential choice is surfaced before implementation.
11
14
 
12
15
  **Initial request:** $ARGUMENTS
13
16
 
@@ -26,10 +29,29 @@ change's dependencies and risk, plus any budget the user supplied.
26
29
  brownfield work, begin with the smallest protected vertical change; add a
27
30
  characterization ticket before changing load-bearing code that lacks coverage.
28
31
  - Order by dependency; note blockers explicitly ("depends on T-01").
32
+ - Build the requirement map: for every `R-NN` in the PRD and each of its
33
+ scenarios, name the ticket that owns the implementation and the executable
34
+ check that exercises it, or an explicit review method when no executable check
35
+ exists. Record the IDs in each ticket's Context as `Implements: R-NN, R-MM`.
36
+ Enabling work that implements no requirement states its purpose in the ticket
37
+ Objective. Resolve missing coverage and conflicting criteria with the user
38
+ before implementation; do not start with an unmapped required scenario.
29
39
  - Every ticket gets a runnable command in its Verification block — a fenced `bash`
30
40
  block that exits 0 only when the ticket is done. `scripts/pincer-ticket.sh verify`
31
41
  runs it verbatim and stamps the receipt that `done` requires, so it must be
32
42
  non-interactive and self-contained (no "check by hand").
43
+ - Each Verification section opens with `Proves:` — what the check establishes and
44
+ which regression it detects. A check for an executable change must exercise
45
+ observable behavior (including relevant rejection paths and, in brownfield work,
46
+ preservation of existing behavior) and fail when the behavior is wrong, not only
47
+ when an identifier is renamed. Reuse adequate focused tests. A build, a syntax
48
+ check, or an identifier grep alone is not proof; static assertions are primary
49
+ evidence only for static contracts such as generated files, and `Proves:` says so.
50
+ - Adequacy is a judgment about what the command observes, never a word match: do
51
+ not call a command sufficient because it contains `grep`, `test`, or a runner
52
+ name, nor insufficient for lacking them. Manual visual judgment is recorded
53
+ separately in evaluation; a tool the check needs but cannot run yields an
54
+ explicit `unverified` result, never fabricated evidence or a silent waiver.
33
55
  - If the brief or stack implies automated tests, at least one ticket's verification
34
56
  command must be the test runner (e.g. `npm test`) — manual checks alone don't count.
35
57
  - Any ticket whose surface accepts external input (HTTP endpoint, form, file,
@@ -46,12 +68,29 @@ change's dependencies and risk, plus any budget the user supplied.
46
68
  numbering or old notes. The other state fields
47
69
  (`started`, `last_check`, `verified`, `finished`) are added later by `scripts/pincer-ticket.sh` —
48
70
  never write them yourself.
49
- 4. Present the ticket list (number, title, size, dependencies) as a table.
71
+ 4. Present the ticket list (number, title, size, dependencies) as a table, followed
72
+ by the requirement map as a second table (requirement · scenario · ticket · check
73
+ or review method). Whether the map is complete and each check is adequate is your
74
+ judgment as the author; say so rather than presenting the table as mechanical proof.
50
75
 
51
- Present the concrete breakdown and build order. Reuse existing authorization for the same
52
- scope and order; ask only when the breakdown introduces a material decision or scope change.
76
+ Present the concrete breakdown and build order as a report, not a question. Reuse
77
+ existing authorization for the same scope and order.
53
78
 
54
- 5. Once authorized, update the selected PRD frontmatter to `status: ticketed`. Inspect
55
- existing staged changes, stage that PRD and the explicit new ticket paths, review
56
- `git diff --cached`, and commit only those paths. Finish with:
79
+ 5. Finalize. A breakdown that follows the PRD is already authorized by the PRD: do not
80
+ ask whether to proceed. Update the selected PRD frontmatter to `status: ticketed`,
81
+ inspect existing staged changes, stage that PRD and the explicit new ticket paths,
82
+ review `git diff --cached`, and commit only those paths. Ask first — and finalize
83
+ once it is resolved — only when step 4 surfaced a newly discovered consequential
84
+ choice or a scope change the PRD does not cover. Finish with:
57
85
  "Tickets ready in `tickets/`. Run `/pincer-code` to start implementing."
86
+
87
+ ## Authorization rule (shared by plan, narrow, code and evaluate)
88
+
89
+ Reuse explicit authorization for the same scope and decisions; ask only about a
90
+ material choice not already authorized, and prepare the concrete proposal before
91
+ asking. A decision the user delegated (for example "pick the architecture") does not
92
+ need another approval when you exercise it, but a newly discovered consequential
93
+ choice is surfaced before implementation. Record the authorization basis and the
94
+ scope it covers in the PRD or the handover. An agent-written record or a status
95
+ field is not authenticated human approval. When resuming without the context that
96
+ granted authorization, do not invent it — ask.
@@ -9,6 +9,14 @@ You are turning a task brief into a reviewable PRD. Scale discovery and detail t
9
9
  the change's uncertainty, risk, and any time budget the user supplied. The PRD feeds
10
10
  `/pincer-narrow` next.
11
11
 
12
+ Choose the planning profile and record why it fits: `profile: small` for bounded
13
+ scope, low risk, known behavior and straightforward verification; otherwise
14
+ `standard` (the default). Few changed lines alone do not qualify — migrations,
15
+ authorization boundaries, uncertain requirements and broad effects need proper
16
+ investigation even for a tiny patch. Honor an explicit budget the user supplied;
17
+ there is no default timebox, and a budget never silently cuts requirements —
18
+ record any cut in Out of Scope.
19
+
12
20
  **Initial request:** $ARGUMENTS
13
21
 
14
22
  First run `scripts/pincer-status.sh`. If a PRD already exists, preserve it and select
@@ -20,7 +28,11 @@ unless the user explicitly authorized a separate change.
20
28
  ## Phase 1: Discovery
21
29
 
22
30
  1. If `$ARGUMENTS` contains the brief, extract what you can before asking anything.
23
- Never ask a question the brief already answers.
31
+ Never ask a question the brief already answers. Preserve or link the original
32
+ brief in the PRD, and record the desired outcome, assumptions and exclusions.
33
+ If the user supplied a PRD, review it: keep its meaning and existing requirement
34
+ IDs; do not silently replace either. Where its structure needs adapting to the
35
+ template, record a mapping table (`their section or ID → R-NN`) inside the PRD.
24
36
  2. Ask only the questions whose answers would change the architecture or scope.
25
37
  Batch them (max 3–4 at once). Typical ones:
26
38
  - What does "done" look like — what will be run, demoed, or reviewed at the end?
@@ -68,6 +80,11 @@ concrete scope and architecture; do not repeat an approval already given for the
68
80
  ## Phase 4: Write the PRD
69
81
 
70
82
  1. Load `.claude/references/prd-template.md` and write all core sections.
83
+ In Requirements, assign stable `R-NN` IDs within the selected PRD: a revision
84
+ keeps existing IDs and adds new ones, never renumbers. Every requirement has
85
+ observable acceptance scenarios, the relevant failure paths, and the existing
86
+ behavior it must preserve — `/pincer-narrow` maps each scenario to a ticket and
87
+ a check, and `/pincer-evaluate` dispositions every ID.
71
88
  2. Include optional sections when risk or the product context warrants them.
72
89
  3. Save to the next unused `.prd/prd-v{N}.md` (create `.prd/` if needed), with `N`
73
90
  matching the filename and frontmatter:
@@ -76,6 +93,7 @@ concrete scope and architecture; do not repeat an approval already given for the
76
93
  version: {N}
77
94
  status: draft
78
95
  date: {today}
96
+ profile: small # only when small; omit for standard
79
97
  ---
80
98
  ```
81
99
  4. If `.git/` doesn't exist, run `git init`. Commit the PRD and only the intended setup
@@ -85,3 +103,14 @@ concrete scope and architecture; do not repeat an approval already given for the
85
103
  Present the saved draft and obtain approval only when the same scope/architecture was not
86
104
  already authorized. Finish with: "PRD saved to `.prd/prd-v{N}.md`. Run `/pincer-narrow`
87
105
  to break it into work items."
106
+
107
+ ## Authorization rule (shared by plan, narrow, code and evaluate)
108
+
109
+ Reuse explicit authorization for the same scope and decisions; ask only about a
110
+ material choice not already authorized, and prepare the concrete proposal before
111
+ asking. A decision the user delegated (for example "pick the architecture") does not
112
+ need another approval when you exercise it, but a newly discovered consequential
113
+ choice is surfaced before implementation. Record the authorization basis and the
114
+ scope it covers in the PRD or the handover. An agent-written record or a status
115
+ field is not authenticated human approval. When resuming without the context that
116
+ granted authorization, do not invent it — ask.
@@ -6,7 +6,11 @@ argument-hint: "Stage to check (optional: plan | narrow | code | evaluate — de
6
6
  # /pincer-release — Workflow Audit
7
7
 
8
8
  You are auditing the current repo state against `docs/release-checklist.md`. Read-only:
9
- report pass/fail, never fix anything — fixes belong to the stage commands.
9
+ report pass/fail, never fix anything — fixes belong to the stage commands. Release does
10
+ not repair tickets, rewrite evidence, change PRD state, or publish. A check that mutates
11
+ the candidate invalidates the audit: if `git status --short` is not empty afterwards,
12
+ the verdict is FAIL and names the mutation. The verdict is reported to the user; a
13
+ durable runtime-owned release record is later work.
10
14
 
11
15
  **Requested stage:** $ARGUMENTS
12
16
 
@@ -25,13 +29,23 @@ report pass/fail, never fix anything — fixes belong to the stage commands.
25
29
  - Receipts: every done ticket carries current `last_check` and `verified` evidence;
26
30
  any status warning fails the audit. Do not call `pincer-ticket.sh` from Release:
27
31
  it writes receipts and would invalidate the evaluated candidate.
32
+ - Evidence: `scripts/pincer-status.sh` runs the shared validator
33
+ (`scripts/pincer-evidence.cjs`) against the manifest `NOTES.md` names. The `Notes`
34
+ line must read `current` and the `Evidence` line `ok`; any other text fails the
35
+ audit with that diagnostic. Do not re-implement evidence checks and do not accept
36
+ screenshots described in chat: read the manifest's `checks`, requirement
37
+ dispositions and `visual_review`. Validation establishes that the record is
38
+ consistent, not that the commands ran — say so if asked.
39
+ - Every file the manifest lists is tracked, and `git status --short` is empty before
40
+ and after the audit.
28
41
  - Run the repository's candidate-wide release gate directly (`npm test`, or the
29
42
  equivalent declared by the project) and report its actual output. Any failure
30
43
  blocks PASS. Confirm `git status --short` remains clean afterward.
31
44
  4. For judgment items (tickets genuinely S/M, history reads as a story), give your
32
45
  verdict AND one sentence of evidence — never a bare pass.
33
46
  5. Present a table: checklist item | pass/fail/skipped | evidence. Order by stage.
34
- 6. End with a one-line verdict: "PASS — workflow artifacts complete" or
35
- "FAIL — {n} items failed; fix via {command}". For each failure, name the command
47
+ 6. End with a one-line verdict naming the candidate: "PASS — candidate {sha}: workflow
48
+ artifacts complete" or "FAIL — candidate {sha}: {n} items failed; fix via {command}".
49
+ Fixes go through a new ticket, a new candidate and a new evaluation. For each failure, name the command
36
50
  file (`.claude/commands/*.md`) whose instructions should be tightened if the
37
51
  failure is a workflow bug rather than a run mistake.
@@ -11,11 +11,13 @@ start of a session. Read-only: change nothing.
11
11
  ## Steps
12
12
 
13
13
  1. Run `scripts/pincer-status.sh`. It reads the artifacts on disk (`.prd/`, `tickets/`,
14
- `NOTES.md`) and prints the PRD state, every ticket with its state and clock-based
15
- elapsed time, what is blocked, build time against any explicit user budget, any warnings (a ticket
16
- marked done without a verification receipt), and the next command to run.
14
+ `NOTES.md`) and prints the PRD state and profile, every ticket with its state and
15
+ clock-based elapsed time, what is blocked, wall-clock build time while a ticket is in
16
+ progress or against an explicit user budget, the evidence verdict for the evaluated
17
+ candidate, any warnings (each readiness problem once), and the next command to run.
17
18
  2. Report in three lines: where the workflow is, what is in progress or blocked, and the
18
19
  next command. Quote the `Next` line as-is.
19
20
  3. If a ticket is `in_progress`, read it and `git status`, then offer to resume it with
20
21
  `/pincer-code T-{NN}`. If the script printed a warning, surface it — a done ticket
21
22
  without a receipt was marked by hand and needs `scripts/pincer-ticket.sh verify T-{NN}`.
23
+ Never restore a ticket file from git to clear a warning; a failed attempt is a record.
@@ -86,18 +86,34 @@ function commandParts(command) {
86
86
  while (i < words.length && words[i].startsWith('-')) i++;
87
87
  }
88
88
  while (['command', 'builtin', 'exec'].includes(words[i])) i++;
89
- const executable = words[i] ? path.basename(words[i]) : '';
89
+ // Transparent wrappers: the command they run is the one that matters.
90
+ while (['nice', 'nohup', 'time', 'timeout', 'xargs'].includes(words[i])) {
91
+ const wrapper = words[i++];
92
+ while (i < words.length && words[i].startsWith('-')) {
93
+ if (['-n', '-s', '-k', '--signal', '--kill-after', '-I', '-L', '-P', '-d', '-a'].includes(words[i])) i++;
94
+ i++;
95
+ }
96
+ if (wrapper === 'timeout' && /^[0-9]/.test(words[i] || '')) i++;
97
+ }
98
+ const executable = words[i] ? path.basename(words[i]).toLowerCase() : '';
90
99
  return { executable, args: words.slice(i + 1), words };
91
100
  }
92
101
 
102
+ // Index of a shell's -c flag, including bundled forms such as -lc or -ec.
103
+ function shellCommandFlag(args) {
104
+ return args.findIndex(arg => /^-[A-Za-z]*c[A-Za-z]*$/.test(arg));
105
+ }
106
+
93
107
  function gitSubcommand(args) {
94
- let i = 0;
108
+ let i = 0, cdir = '';
95
109
  while (i < args.length) {
96
- if (['-C', '-c', '--git-dir', '--work-tree'].includes(args[i])) i += 2;
110
+ if (args[i] === '-C') { cdir = args[i + 1] || ''; i += 2; }
111
+ else if (args[i] === '-c' && /^alias\./.test(args[i + 1] || '')) return { name: 'alias', args: [], cdir };
112
+ else if (['-c', '--git-dir', '--work-tree'].includes(args[i])) i += 2;
97
113
  else if (args[i].startsWith('-')) i++;
98
- else return { name: args[i], args: args.slice(i + 1) };
114
+ else return { name: args[i], args: args.slice(i + 1), cdir };
99
115
  }
100
- return { name: '', args: [] };
116
+ return { name: '', args: [], cdir };
101
117
  }
102
118
 
103
119
  function dangerousReason(source, depth = 0) {
@@ -129,7 +145,7 @@ function dangerousReason(source, depth = 0) {
129
145
  if (executable === 'chmod' && args.some(arg => /^(0?777|a\+rwx)$/.test(arg)))
130
146
  return 'mass permission changes are not allowed.';
131
147
  if (['sh', 'bash', 'zsh'].includes(executable)) {
132
- const c = args.indexOf('-c');
148
+ const c = shellCommandFlag(args);
133
149
  if (c !== -1 && typeof args[c + 1] === 'string') {
134
150
  const nested = dangerousReason(args[c + 1], depth + 1);
135
151
  if (nested) return nested;
@@ -223,11 +239,88 @@ function isExactPincerCall(source) {
223
239
  return action === 'bind' ? words.length === 4 : words.length === 3;
224
240
  }
225
241
 
226
- function ticketShellMutation(source) {
242
+ // A pathspec is "wide" when, after normalisation, it cannot be shown to stay
243
+ // outside tickets/: the whole tree, an absolute or unexpanded path, a glob at
244
+ // the top level, or anything whose first segment is tickets. Exclude entries
245
+ // never widen. `cdir` is a `git -C <dir>` prefix.
246
+ function widePathspec(arg, cdir = '') {
247
+ if (/[$`]/.test(arg) || /[$`]/.test(cdir) || cdir.startsWith('/') || cdir.startsWith('~') || /^[A-Za-z]:[\\/]/.test(cdir)) return true;
248
+ let p = arg;
249
+ if (p.startsWith(':(')) {
250
+ const end = p.indexOf(')');
251
+ if (end < 0) return true;
252
+ const magic = p.slice(2, end).split(',').map(s => s.trim());
253
+ if (magic.includes('exclude')) return false;
254
+ p = p.slice(end + 1);
255
+ if (magic.includes('top')) cdir = '';
256
+ } else if (p.startsWith(':/')) { p = p.slice(2); cdir = ''; }
257
+ else if (p.startsWith(':!') || p.startsWith(':^')) return false;
258
+ else if (p.startsWith(':')) p = p.slice(1);
259
+ if (p.startsWith('/') || p.startsWith('~') || /^[A-Za-z]:[\\/]/.test(p)) return true;
260
+ if (cdir) p = `${cdir}/${p}`;
261
+ const segments = [];
262
+ for (const segment of p.split(/[\\/]+/)) {
263
+ if (segment === '' || segment === '.') continue;
264
+ if (segment === '..') { if (!segments.length) return true; segments.pop(); continue; }
265
+ segments.push(segment);
266
+ }
267
+ if (!segments.length) return true;
268
+ if (/[*?[{]/.test(segments[0])) return true;
269
+ return segments[0].toLowerCase() === 'tickets';
270
+ }
271
+
272
+ // Git forms that restore the working tree wholesale — and with it any ticket file
273
+ // whose failed attempt would be erased and whose revoked receipt would come back.
274
+ function wholeTreeRestore(sub) {
275
+ const { name, args, cdir } = sub;
276
+ const positional = [];
277
+ for (let i = 0; i < args.length; i++) {
278
+ const arg = args[i];
279
+ if (arg === '--') { positional.push(...args.slice(i + 1)); break; }
280
+ if (['-e', '--exclude', '--source', '-b', '-B', '--orphan', '-c', '-C', '--conflict'].includes(arg)) { i++; continue; }
281
+ if (arg.startsWith('-')) continue;
282
+ positional.push(arg);
283
+ }
284
+ const force = args.some(arg => arg === '--force' || arg === '--discard-changes' || /^-[A-Za-z]*f[A-Za-z]*$/.test(arg));
285
+ const fromFile = args.some(arg => arg === '--pathspec-from-file' || arg.startsWith('--pathspec-from-file='));
286
+ const patch = args.some(arg => arg === '-p' || arg === '--patch');
287
+ // `git checkout <ref>` / `git switch <ref>`: one positional, no `--`, no force flag is a branch switch,
288
+ // allowed even through a variable; every pathspec form keeps the wide test.
289
+ const branchSwitch = (name === 'checkout' || name === 'switch') && positional.length === 1 && !args.includes('--');
290
+ const wide = branchSwitch ? widePathspec(positional[0].replace(/[$`]/g, 'x'), cdir) : positional.some(p => widePathspec(p, cdir));
291
+ switch (name) {
292
+ case 'alias': return true;
293
+ case 'checkout': case 'switch': return force || fromFile || wide || (patch && positional.length === 0);
294
+ case 'restore': return fromFile || wide || positional.length === 0;
295
+ case 'reset': return args.some(arg => ['--hard', '--merge', '--keep'].includes(arg));
296
+ case 'stash':
297
+ if (args.some(arg => arg === '-h' || arg === '--help')) return false;
298
+ return !['list', 'show', 'create', 'store'].includes(positional[0] || '');
299
+ case 'clean': return force && (positional.length === 0 || wide);
300
+ case 'checkout-index': return args.some(arg => arg === '--all' || /^-[A-Za-z]*a/.test(arg));
301
+ case 'read-tree': return args.some(arg => arg === '--reset' || /^-[A-Za-z]*u/.test(arg));
302
+ default: return false;
303
+ }
304
+ }
305
+
306
+ function ticketShellMutation(source, depth = 0) {
307
+ if (depth > 2) return false;
227
308
  if (isExactPincerCall(source)) return false;
228
309
  const commands = shellCommands(source);
229
310
  for (const command of commands) {
230
311
  const { executable, args, words } = commandParts(command);
312
+ if (['sh', 'bash', 'zsh'].includes(executable)) {
313
+ const c = shellCommandFlag(args);
314
+ if (c !== -1 && typeof args[c + 1] === 'string' && ticketShellMutation(args[c + 1], depth + 1)) return true;
315
+ }
316
+ if (executable === 'eval' && ticketShellMutation(args.join(' '), depth + 1)) return true;
317
+ if (executable === 'git') {
318
+ const sub = gitSubcommand(args);
319
+ if (wholeTreeRestore(sub)) return true;
320
+ const viaXargs = command.words.some(word => path.basename(word).toLowerCase() === 'xargs');
321
+ const stdinPathspec = !sub.args.some(arg => !arg.startsWith('-')) || sub.args[sub.args.length - 1] === '--';
322
+ if (viaXargs && ['checkout', 'restore', 'clean'].includes(sub.name) && stdinPathspec) return true;
323
+ }
231
324
  const hasTicket = words.some(ticketPath) || /(^|[\s'"`])tickets[\\/]T-[0-9]+[^\s'"`]*/.test(source);
232
325
  if (!hasTicket) continue;
233
326
  if (command.operators.some(op => op === '>' || op === '>>')) return true;
@@ -251,7 +344,7 @@ if (mode === 'dangerous') {
251
344
  if (['Edit', 'Write', 'MultiEdit'].includes(payload.tool_name)) guardEdits(payload.tool_name, payload.tool_input);
252
345
  else if (payload.tool_name === 'Bash') {
253
346
  if (typeof payload.tool_input.command !== 'string') block('Bash payload must contain a string command.');
254
- if (ticketShellMutation(payload.tool_input.command)) block('shell commands may not write or reset ticket files; use pincer-ticket.sh for lifecycle state.');
347
+ if (ticketShellMutation(payload.tool_input.command)) block('shell commands may not write, reset, or restore ticket files (including whole-tree checkout/restore, reset --hard, stash, clean -f); use pincer-ticket.sh for lifecycle state.');
255
348
  }
256
349
  } else {
257
350
  block('hook policy mode is invalid.');
@@ -3,15 +3,31 @@
3
3
  Used by `/pincer-plan` Phase 4. Core sections are always included. Add optional detail
4
4
  when uncertainty, product context, or risk warrants it; a small fix may remain compact.
5
5
 
6
+ ## Profile
7
+
8
+ The frontmatter field `profile: small | standard` sets the planning weight; a PRD
9
+ without it is `standard`. `small` means bounded scope, low risk, known behavior and
10
+ straightforward verification. Few changed lines alone do not qualify: migrations,
11
+ authorization boundaries, uncertain requirements and broad effects stay `standard`
12
+ even for a tiny patch. The PRD records in one or two sentences why the profile fits.
13
+
14
+ A small PRD keeps Problem (with the outcome), Scope, Requirements with scenarios,
15
+ Success Criteria (its verification), risks and exclusions, and omits empty sections
16
+ and repetition. Interface examples may clarify a contract; implementation code must
17
+ not substitute for requirements in any profile.
18
+
6
19
  ---
7
20
 
8
21
  ## Core Sections (always include)
9
22
 
10
23
  ### 1. Problem
11
- What problem does this solve? Who has it? (2–4 sentences.)
24
+ What problem does this solve? Who has it? (2–4 sentences.) Preserve or link the
25
+ original brief (quote it in an appendix or name where it lives) so the source of
26
+ every requirement stays reviewable.
12
27
 
13
28
  ### 2. Solution
14
- One-paragraph summary of what we're building.
29
+ One-paragraph summary of what we're building: the desired outcome, the assumptions
30
+ it rests on, and what it deliberately excludes.
15
31
 
16
32
  ### 3. Scope
17
33
 
@@ -19,7 +35,22 @@ One-paragraph summary of what we're building.
19
35
  | --- | --- |
20
36
  | ... | ... |
21
37
 
22
- ### 4. Architecture
38
+ ### 4. Requirements
39
+ One entry per requirement with a stable ID. IDs are assigned once within this PRD
40
+ and never renumbered: a revision keeps existing IDs and adds new ones. Tickets
41
+ name the IDs they implement and evaluation dispositions every ID.
42
+
43
+ #### R-01 — short title
44
+ - Scenario: an observable acceptance scenario (given / when / then, or a command
45
+ and its expected output). Add one line per scenario.
46
+ - Failure path: what invalid input or the relevant failure produces.
47
+ - Preserve: existing behavior this must not change (brownfield).
48
+
49
+ When the user supplied a PRD, keep its meaning and its existing requirement IDs.
50
+ If its structure needs adapting to this template, add a `Requirement mapping`
51
+ table under this section (`their section or ID → R-NN`) instead of rewriting it.
52
+
53
+ ### 5. Architecture
23
54
 
24
55
  #### Structure
25
56
  ```
@@ -32,13 +63,13 @@ What each component does, owns, and depends on.
32
63
  #### Data flow
33
64
  Input → processing → output.
34
65
 
35
- ### 5. Success Criteria
66
+ ### 6. Success Criteria
36
67
 
37
68
  | Criterion | How to verify |
38
69
  | --- | --- |
39
70
  | ... | a command to run or a thing to observe |
40
71
 
41
- ### 6. Out of Scope
72
+ ### 7. Out of Scope
42
73
  Explicit list. Anything cut for time during `/pincer-code` gets appended here with a reason.
43
74
 
44
75
  ---
@@ -62,7 +93,8 @@ Include when something outside our control or a migration/rollback concern could
62
93
 
63
94
  ## Formatting Rules
64
95
 
65
- - Save as `.prd/prd-v{N}.md` with YAML frontmatter (`version`, `status`, `date`).
96
+ - Save as `.prd/prd-v{N}.md` with YAML frontmatter (`version`, `status`, `date`,
97
+ and `profile` when small).
66
98
  - Status lifecycle: `draft → ticketed → built`.
67
99
  - Diagrams as ASCII or markdown tables only.
68
100
  - No implementation code and no exact line numbers — those belong in tickets.
@@ -17,6 +17,8 @@ One sentence: what to build and why.
17
17
  ## Context
18
18
  - Relevant files: `src/path/to/file.ts` (what's there / what to follow)
19
19
  - PRD section: which part of the PRD this implements
20
+ - Implements: R-01, R-03 (requirement IDs from the PRD; enabling work that
21
+ implements no requirement states its purpose in the Objective instead)
20
22
 
21
23
  ## Requirements
22
24
  - Concrete, checkable requirements. No vague "handle errors properly" —
@@ -27,6 +29,7 @@ One sentence: what to build and why.
27
29
  - [ ] Observable behavior 2
28
30
 
29
31
  ## Verification
32
+ Proves: one line — what this check establishes and which regression it detects.
30
33
  ```bash
31
34
  # command(s) the builder runs to prove the criteria — tests, build, curl, etc.
32
35
  ```
@@ -57,6 +60,18 @@ Rules:
57
60
  requires that receipt to match the current block. Never write these by hand.
58
61
  - The Verification block is a fenced `bash` block that exits 0 only when the
59
62
  ticket is done — non-interactive, no "check by hand".
63
+ - The Verification section opens with a one-line `Proves:` statement: what the
64
+ check establishes and which regression it detects. Executable changes need
65
+ checks that exercise observable behavior — including relevant rejection paths
66
+ and, in brownfield work, preservation of existing behavior. Reuse adequate
67
+ focused tests rather than inventing ad-hoc commands. A build, a syntax check, or
68
+ an identifier grep alone does not prove a feature works: the check must fail
69
+ when the behavior is wrong, not only when a name is renamed. Static assertions
70
+ may be primary evidence for static contracts (generated files, adapter wording)
71
+ when `Proves:` explains that fit.
72
+ - Manual visual judgment is recorded separately during evaluation, never as the
73
+ Verification command. When a tool the check needs is unavailable, the result is
74
+ an explicit `unverified`, never fabricated output or a silent waiver.
60
75
  - Every ticket must be verifiable without human judgment where possible.
61
76
  - If the ticket's surface accepts external input (HTTP, form, file, LLM output),
62
77
  Requirements must state the validation and the rejection behavior, and