pincer-workflow 0.7.0 → 1.0.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 (139) hide show
  1. package/README.md +161 -193
  2. package/bin/pincer.js +189 -166
  3. package/package.json +4 -3
  4. package/template/.agents/skills/pincer-blueprint/SKILL.md +70 -0
  5. package/template/.agents/skills/pincer-status/SKILL.md +29 -55
  6. package/template/.claude/commands/pincer-blueprint.md +7 -0
  7. package/template/.claude/commands/pincer-status.md +4 -59
  8. package/template/.claude/hooks/hook-policy.cjs +60 -25
  9. package/template/.claude/hooks/ticket-guard.sh +6 -3
  10. package/template/.claude/pincer/library/agents/accessibility-reviewer.md +27 -0
  11. package/template/.claude/pincer/library/agents/code-quality-reviewer.md +25 -0
  12. package/template/.claude/pincer/library/agents/codebase-explorer.md +23 -0
  13. package/template/.claude/pincer/library/agents/fix-agent.md +23 -0
  14. package/template/.claude/pincer/library/agents/history-reviewer.md +26 -0
  15. package/template/.claude/pincer/library/agents/implementer.md +24 -0
  16. package/template/.claude/pincer/library/agents/security-reviewer.md +26 -0
  17. package/template/.claude/pincer/library/agents/silent-failure-hunter.md +27 -0
  18. package/template/.claude/pincer/library/agents/standards-reviewer.md +25 -0
  19. package/template/.claude/pincer/library/agents/test-coverage-reviewer.md +26 -0
  20. package/template/.claude/pincer/library/agents-skeleton.md +89 -0
  21. package/template/.claude/pincer/library/agreement.md +46 -0
  22. package/template/.claude/pincer/library/models.json +6 -0
  23. package/template/.claude/pincer/library/stages/blueprint/SKILL.md +69 -0
  24. package/template/.claude/pincer/library/stages/blueprint/formats/harness.md +82 -0
  25. package/template/.claude/pincer/library/stages/blueprint/formats/roster-rules.md +61 -0
  26. package/template/.claude/pincer/library/stages/blueprint/prompts/confirm-kind.md +19 -0
  27. package/template/.claude/pincer/library/stages/blueprint/prompts/first-job.md +18 -0
  28. package/template/.claude/pincer/library/stages/blueprint/prompts/q-handoffs.md +21 -0
  29. package/template/.claude/pincer/library/stages/blueprint/prompts/q-models.md +20 -0
  30. package/template/.claude/pincer/library/stages/blueprint/prompts/q-pipeline.md +27 -0
  31. package/template/.claude/pincer/library/stages/blueprint/prompts/q-repo.md +21 -0
  32. package/template/.claude/pincer/library/stages/blueprint/prompts/q-responsibility.md +30 -0
  33. package/template/.claude/pincer/library/stages/blueprint/prompts/q-roster.md +20 -0
  34. package/template/.claude/pincer/library/stages/blueprint/prompts/q-simplify.md +26 -0
  35. package/template/.claude/pincer/library/stages/blueprint/prompts/q-systems.md +28 -0
  36. package/template/.claude/pincer/library/stages/code/SKILL.md +59 -0
  37. package/template/.claude/pincer/library/stages/code/formats/pr.md +55 -0
  38. package/template/.claude/pincer/library/stages/code/prompts/implement.md +25 -0
  39. package/template/.claude/pincer/library/stages/evaluate/SKILL.md +51 -0
  40. package/template/.claude/pincer/library/stages/evaluate/formats/evidence.md +49 -0
  41. package/template/.claude/pincer/library/stages/evaluate/formats/finding-report.md +72 -0
  42. package/template/.claude/pincer/library/stages/evaluate/formats/finding.md +35 -0
  43. package/template/.claude/pincer/library/stages/evaluate/prompts/review.md +25 -0
  44. package/template/.claude/pincer/library/stages/narrow/SKILL.md +47 -0
  45. package/template/.claude/pincer/library/stages/narrow/formats/ticket.md +126 -0
  46. package/template/.claude/pincer/library/stages/narrow/prompts/explore.md +19 -0
  47. package/template/.claude/pincer/library/stages/plan/SKILL.md +46 -0
  48. package/template/.claude/pincer/library/stages/plan/formats/spec.md +100 -0
  49. package/template/.claude/pincer/library/stages/plan/prompts/explore.md +20 -0
  50. package/template/.claude/pincer/library/stages/release/SKILL.md +51 -0
  51. package/template/.claude/pincer/library/stages/status/SKILL.md +37 -0
  52. package/template/.claude/skills/pincer-blueprint/SKILL.md +69 -0
  53. package/template/.claude/skills/pincer-blueprint/formats/harness.md +82 -0
  54. package/template/.claude/skills/pincer-blueprint/formats/roster-rules.md +61 -0
  55. package/template/.claude/skills/pincer-blueprint/prompts/confirm-kind.md +19 -0
  56. package/template/.claude/skills/pincer-blueprint/prompts/first-job.md +18 -0
  57. package/template/.claude/skills/pincer-blueprint/prompts/q-handoffs.md +21 -0
  58. package/template/.claude/skills/pincer-blueprint/prompts/q-models.md +20 -0
  59. package/template/.claude/skills/pincer-blueprint/prompts/q-pipeline.md +27 -0
  60. package/template/.claude/skills/pincer-blueprint/prompts/q-repo.md +21 -0
  61. package/template/.claude/skills/pincer-blueprint/prompts/q-responsibility.md +30 -0
  62. package/template/.claude/skills/pincer-blueprint/prompts/q-roster.md +20 -0
  63. package/template/.claude/skills/pincer-blueprint/prompts/q-simplify.md +26 -0
  64. package/template/.claude/skills/pincer-blueprint/prompts/q-systems.md +28 -0
  65. package/template/.claude/skills/pincer-status/SKILL.md +37 -0
  66. package/template/.codex/README.md +39 -35
  67. package/template/.github/copilot-instructions.md +19 -14
  68. package/template/.github/prompts/pincer-blueprint.prompt.md +70 -0
  69. package/template/.github/prompts/pincer-status.prompt.md +29 -55
  70. package/template/AGENTS.md +34 -67
  71. package/template/docs/engine.md +249 -0
  72. package/template/docs/release-checklist.md +27 -28
  73. package/template/scripts/pincer/blueprint.cjs +137 -0
  74. package/template/scripts/pincer/cli.cjs +233 -0
  75. package/template/scripts/pincer/evidence.cjs +142 -0
  76. package/template/scripts/pincer/generate.cjs +375 -0
  77. package/template/scripts/pincer/harness.cjs +269 -0
  78. package/template/scripts/pincer/models.cjs +121 -0
  79. package/template/scripts/{pincer-runtime → pincer}/parse.cjs +83 -133
  80. package/template/scripts/pincer/recon.cjs +205 -0
  81. package/template/scripts/pincer/status.cjs +173 -0
  82. package/template/scripts/pincer/ticket.cjs +204 -0
  83. package/template/scripts/pincer/util.cjs +104 -0
  84. package/template/scripts/pincer-blueprint.cjs +7 -0
  85. package/template/scripts/pincer-evidence.cjs +5 -73
  86. package/template/scripts/pincer-status.sh +10 -12
  87. package/template/scripts/pincer-ticket.sh +15 -18
  88. package/template/scripts/sync-prompts.sh +2 -50
  89. package/template/.agents/skills/pincer-code/SKILL.md +0 -240
  90. package/template/.agents/skills/pincer-evaluate/SKILL.md +0 -178
  91. package/template/.agents/skills/pincer-narrow/SKILL.md +0 -148
  92. package/template/.agents/skills/pincer-plan/SKILL.md +0 -134
  93. package/template/.agents/skills/pincer-release/SKILL.md +0 -82
  94. package/template/.claude/agents/code-quality-reviewer.md +0 -38
  95. package/template/.claude/agents/codebase-explorer.md +0 -23
  96. package/template/.claude/commands/pincer-code.md +0 -238
  97. package/template/.claude/commands/pincer-evaluate.md +0 -176
  98. package/template/.claude/commands/pincer-narrow.md +0 -146
  99. package/template/.claude/commands/pincer-plan.md +0 -132
  100. package/template/.claude/commands/pincer-release.md +0 -80
  101. package/template/.claude/references/prd-template.md +0 -108
  102. package/template/.claude/references/ticket-template.md +0 -86
  103. package/template/.github/prompts/pincer-code.prompt.md +0 -240
  104. package/template/.github/prompts/pincer-evaluate.prompt.md +0 -178
  105. package/template/.github/prompts/pincer-narrow.prompt.md +0 -148
  106. package/template/.github/prompts/pincer-plan.prompt.md +0 -134
  107. package/template/.github/prompts/pincer-release.prompt.md +0 -82
  108. package/template/docs/dry-run-checklist.md +0 -264
  109. package/template/docs/runtime-contracts.md +0 -1769
  110. package/template/scripts/pincer-runtime/adopt.cjs +0 -132
  111. package/template/scripts/pincer-runtime/agreement.cjs +0 -240
  112. package/template/scripts/pincer-runtime/authorization.cjs +0 -167
  113. package/template/scripts/pincer-runtime/changes.cjs +0 -517
  114. package/template/scripts/pincer-runtime/checks.cjs +0 -48
  115. package/template/scripts/pincer-runtime/coverage.cjs +0 -361
  116. package/template/scripts/pincer-runtime/dispositions.cjs +0 -95
  117. package/template/scripts/pincer-runtime/evidence.cjs +0 -676
  118. package/template/scripts/pincer-runtime/fsutil.cjs +0 -37
  119. package/template/scripts/pincer-runtime/gates.cjs +0 -73
  120. package/template/scripts/pincer-runtime/identity.cjs +0 -163
  121. package/template/scripts/pincer-runtime/impact.cjs +0 -177
  122. package/template/scripts/pincer-runtime/io.cjs +0 -41
  123. package/template/scripts/pincer-runtime/lifecycle.cjs +0 -311
  124. package/template/scripts/pincer-runtime/locator.cjs +0 -158
  125. package/template/scripts/pincer-runtime/migrate.cjs +0 -204
  126. package/template/scripts/pincer-runtime/phases.cjs +0 -245
  127. package/template/scripts/pincer-runtime/readiness.cjs +0 -102
  128. package/template/scripts/pincer-runtime/requirements.cjs +0 -255
  129. package/template/scripts/pincer-runtime/resume.cjs +0 -273
  130. package/template/scripts/pincer-runtime/routing.cjs +0 -54
  131. package/template/scripts/pincer-runtime/runner.cjs +0 -242
  132. package/template/scripts/pincer-runtime/sanitize.cjs +0 -63
  133. package/template/scripts/pincer-runtime/scaffold.cjs +0 -254
  134. package/template/scripts/pincer-runtime/source.cjs +0 -129
  135. package/template/scripts/pincer-runtime/state.cjs +0 -314
  136. package/template/scripts/pincer-runtime/status.cjs +0 -514
  137. package/template/scripts/pincer-runtime/transaction.cjs +0 -200
  138. package/template/scripts/pincer-runtime/transitions.cjs +0 -134
  139. package/template/scripts/pincer-runtime.cjs +0 -686
@@ -0,0 +1,249 @@
1
+ # The PINCER engine
2
+
3
+ The contract between the stage skills and the scripts under `scripts/pincer/`. The
4
+ skills are prose for an agent and bend to the agreement; the engine is the part that
5
+ does not bend. It stamps the receipts, records the acceptance, validates the evidence
6
+ and generates the project's copies. Read this when you want to know exactly what a
7
+ script does, or when a session has to recover from a mistake.
8
+
9
+ ## What the engine is
10
+
11
+ Four entry points over one dependency-free Node core (`scripts/pincer/`, Node 22 or
12
+ newer, CommonJS, `node:` modules only). `scripts/pincer-ticket.sh` moves a ticket
13
+ through its lifecycle and is the only writer of its lifecycle fields.
14
+ `scripts/pincer-status.sh` reports where the work stands and gates the stages.
15
+ `scripts/pincer-evidence.cjs` validates and digests evidence.
16
+ `scripts/pincer-blueprint.cjs` reads the repository (recon), writes the agreement
17
+ (write) and generates the project copies (generate); `scripts/sync-prompts.sh` is
18
+ `generate` under its old name. Every entry point resolves the project root from
19
+ `--root <dir>`, else the nearest directory up from the working directory that holds
20
+ `harness.json`, else the working directory. The engine never talks to the forge; the
21
+ skills do, with `gh`.
22
+
23
+ ## harness.json
24
+
25
+ The machine-readable agreement at the repository root, written by the blueprint stage
26
+ through `write` and read by every other stage. Schema 1. Unknown keys are kept and
27
+ ignored. `blueprint` and `status` never appear under `stages`: they are always on and
28
+ the validator rejects an entry for them.
29
+
30
+ | Field | Values | Default |
31
+ |---|---|---|
32
+ | `schema` | `1` | required |
33
+ | `project.kind` | `greenfield`, `brownfield` | required |
34
+ | `project.name`, `project.reason` | text | `""` |
35
+ | `people[]` | `{name, role, accepts}`, at least one with `accepts: true` | required |
36
+ | `systems.forge` | `github`, `none`, `other` | `none` |
37
+ | `systems.remote` | URL without credentials | `""` |
38
+ | `systems.ci` | true, false | `false` |
39
+ | `systems.deploy`, `systems.secrets` | text | `""` |
40
+ | `repo.branches` | `per-ticket`, `main` | `main` |
41
+ | `repo.pr` | true, false | `false` |
42
+ | `repo.merge` | `merge`, `squash`, `rebase` | `merge` |
43
+ | `repo.protection` | text | `""` |
44
+ | `repo.checks` | list of commands | `[]` |
45
+ | `homes.specs`, `homes.tickets`, `homes.evidence` | relative paths inside the repository, no leading `/` or `..` | `specs`, `tickets`, `evidence` |
46
+ | `stages.plan` | `{on, spec: file or forge, gate, reason}` | `on: true, spec: file, gate: ""` |
47
+ | `stages.narrow` | `{on, tickets: file or forge, reason}` | `on: true, tickets: file` |
48
+ | `stages.code` | `{on, implementer, reason}` | `on: true, implementer: false` |
49
+ | `stages.evaluate` | `{on, where: local or pr, evidence, reason}` | `on: true, where: local, evidence: true` |
50
+ | `stages.release` | `{on, audit: tree or pr, reason}` | `on: true, audit: tree` |
51
+ | `roster.plan`, `roster.narrow`, `roster.code`, `roster.evaluate` | lists of agent names from the library | `[]` |
52
+ | `models.stages.<stage>` | a tier or `{tier, effort}`; `plan`, `narrow`, `code`, `evaluate`, `release` | not set |
53
+ | `models.agents.<name>` | a tier or `{tier, effort}`, for a library agent name | not set |
54
+ | `models.map.<platform>.<tier>` | a model name, for `claude`, `codex`, `copilot` and `strong`, `standard`, `fast` | none |
55
+ | `open` | list of text | `[]` |
56
+ | `firstJob` | text | `""` |
57
+
58
+ A file that fails validation makes every entry point print `harness.json: <problem>`
59
+ and exit 3. A missing file prints `harness.json: no agreement yet: run /pincer-blueprint`
60
+ and exits 3, except for `status`, which reports it and exits 0, and the always-on gates.
61
+
62
+ ## Ticket fields and receipts
63
+
64
+ A ticket is `<homes.tickets>/T-NN-<slug>.md` with a closed `---` frontmatter of
65
+ unique, unindented `key: value` lines; `#` starts a comment, except in
66
+ `accepted_words`, which is read verbatim. Required keys: `ticket`, `status`, `size`
67
+ (`S`, `M`, `L`), `depends_on` (an inline list such as `[T-01]`). Optional: `spec` (a
68
+ relative `.md` path that must exist), `forge` (free text), `timeout` (seconds, default
69
+ 600). The body needs one `## Acceptance Criteria` section with checkboxes and one
70
+ `## Verification` section with one fenced `bash` block.
71
+
72
+ The eight lifecycle fields are written only by `scripts/pincer-ticket.sh`:
73
+
74
+ | Field | Written by | Format |
75
+ |---|---|---|
76
+ | `status` | start, done | `open`, `in_progress`, `done` |
77
+ | `started` | start | ISO UTC timestamp, `YYYY-MM-DDTHH:MM:SSZ` |
78
+ | `last_check` | verify | `<timestamp> running|passed|failed|interrupted <hash>` |
79
+ | `verified` | verify, on a pass | `<timestamp> <hash>` |
80
+ | `finished` | done | timestamp |
81
+ | `accepted` | accept | timestamp |
82
+ | `accepted_by` | accept | a name from `people` with `accepts: true` |
83
+ | `accepted_words` | accept | what the person said, verbatim, on one line |
84
+
85
+ The hash is the first 12 hex characters of sha256 over the Verification block: the
86
+ lines inside the fence joined with newlines plus one trailing newline. Change the
87
+ block and the receipt no longer matches it. `verify` writes `running` first, runs the
88
+ block with `bash -eo pipefail` under the ticket's timeout, then writes `passed`
89
+ (together with `verified`), `failed`, or `interrupted` for a signal or a timeout. A
90
+ ticket is ready when `verified` carries the current hash and the `last_check` for that
91
+ hash is not a failure.
92
+
93
+ ## Commands and exit codes
94
+
95
+ Exit codes everywhere: 0 ok, 1 refused (the reason on stderr), 2 usage, 3 no
96
+ agreement, stage off or invalid `harness.json`, 130 interrupted.
97
+
98
+ `scripts/pincer-ticket.sh` runs `node scripts/pincer/cli.cjs ticket <verb> <T-NN>`:
99
+ - `start T-NN`: `open` to `in_progress`, stamps `started`. Refuses when a `depends_on` ticket is not done and ready, when the ticket is already done, and with exit 3 when `harness.json` is missing or `stages.code.on` is false.
100
+ - `verify T-NN`: starts an open ticket, runs the block, stamps the receipt only on exit 0. A failed check exits 1 and prints `FAILED (exit N)`; a block with no runnable command is refused.
101
+ - `done T-NN`: `in_progress` to `done` when the receipt matches the current block, no newer check failed, and every criterion is ticked; stamps `finished`.
102
+ - `accept T-NN --by "<name>" --words "<their words>"`: on a done ticket writes the three acceptance fields together; refuses a name that is not an acceptor and empty words.
103
+
104
+ `scripts/pincer-status.sh` runs `node scripts/pincer/cli.cjs status`: the report, or
105
+ `--json` for data. `--stage <name>` prints `on` and exits 0, or the reason and exits 3;
106
+ a name that is not a stage exits 2. An invalid `harness.json` still renders the report
107
+ and then exits 3.
108
+
109
+ `scripts/pincer-evidence.cjs` runs `node scripts/pincer/cli.cjs evidence`:
110
+ `validate <manifest> [--candidate <sha>]` prints `ok <candidate>` or one
111
+ `evidence: <manifest>: <problem>` line each and exits 1; `digest <file>...` prints
112
+ `<sha256> <file>` per file.
113
+
114
+ `scripts/pincer-blueprint.cjs` has three verbs:
115
+ - `recon [--json] [--network]` runs `node scripts/pincer/cli.cjs recon`: the kind, the reasons, the facts, the prefill and what it could not see. Offline unless `--network`, which lets it ask `gh`.
116
+ - `write [--answers <file>]` runs `node scripts/pincer/cli.cjs write`: validates the answers (default: `harness.json` itself), writes `harness.json`, renders the agreement between the markers in `AGENTS.md`, runs the generator. Validation problems: one per line, exit 3, nothing written. One agreement marker without the other: exit 1, nothing written.
117
+ - `generate [--harness <file>] [--platform <claude,codex,copilot|all>] [--overwrite]` runs `node scripts/pincer/cli.cjs generate`, also reachable as `scripts/sync-prompts.sh`. See Generation below.
118
+
119
+ ## Stage gate
120
+
121
+ Every stage skill's first step runs `scripts/pincer-status.sh --stage <name>`.
122
+ `blueprint` and `status` are always on and print `on` even without `harness.json`. The
123
+ other five (`plan`, `narrow`, `code`, `evaluate`, `release`) print `on` when the
124
+ agreement has them on, else the reason (`no agreement yet: run /pincer-blueprint`,
125
+ `stage <name> is off in harness.json`, or the validation problem) with exit 3.
126
+ `scripts/pincer-ticket.sh start` and `verify` apply the same gate for `code`. The hook
127
+ on Claude Code does not read `harness.json`; the gate lives in the scripts, so it holds
128
+ on every platform.
129
+
130
+ ## Acceptance
131
+
132
+ A ticket is `done` only on a receipt and accepted only when a named person said so.
133
+ `accept` writes `accepted`, `accepted_by` and `accepted_words` together; the validator
134
+ refuses a partial trio. The name must be in `people` with `accepts: true`; quote the
135
+ person's words verbatim. Acceptance does not block the next ticket, but status warns
136
+ about each done ticket without it and points to release (or `accept` if release is off).
137
+ Release passes only when every done ticket is accepted. Run `accept` only after the person says so; it never commits or publishes.
138
+
139
+ Release first audits read-only, allowing only `done without acceptance` warnings; all other warnings fail.
140
+ Record the manifest's evaluated `candidate` and current `audit_head` separately; only retained evidence
141
+ and existing acceptance fields may differ, never implementation, checks, spec or agreement.
142
+ Without evidence, `audit_head` is the candidate. The technical audit starts and ends with a clean tree.
143
+
144
+ After the person accepts, inspect working-tree, staged and committed diffs against `audit_head`:
145
+ only `accepted`, `accepted_by` and `accepted_words` in those tickets' frontmatter may change.
146
+ Commit separately as `accept: <candidate short sha>`, recording its full `acceptance_commit`;
147
+ skip an empty commit when nothing was newly accepted. Require a clean tree, status without warnings
148
+ and unchanged evidence validated against the original candidate. The verdict names both identities;
149
+ acceptance is not a newly tested candidate. Unexpected changes stop release for re-evaluation.
150
+
151
+ Branch and PR rules still apply: a required acceptance PR must be merged with passing checks before PASS.
152
+ Never push, merge or publish without the user's authorization. Unanswered acceptance or an incomplete
153
+ required remote step is pending, not passed.
154
+
155
+ ## Evidence manifest schema 1
156
+
157
+ `<homes.evidence>/<candidate>/manifest.json`, where the directory's basename is the
158
+ candidate's full commit id. Top-level keys, all required and no others: `schema` (1),
159
+ `candidate` and `base` (40 hex characters), `created` (UTC timestamp), `spec` (a path
160
+ that exists), `environment` (`tools` and `limitations`, each a list of short
161
+ strings), `checks`, `visual_review`, `artifacts`. Each check has `id` (`C-NN`), `kind` (`command`, `review`, `visual`),
162
+ `required`, `result` (`passed`, `failed`, `unverified`), `command` (required for a
163
+ command check), `log` (an artifact path) and an optional `note`; a required check
164
+ must be `passed`. `visual_review` has `applicable` and, when false, `reason`.
165
+ `artifacts` maps paths inside the manifest's directory to their sha256, and the
166
+ digests must match the files. The validator checks that the record is consistent, not
167
+ that the commands ran. Status reports `Evidence:` per manifest and release reads the
168
+ same validator.
169
+
170
+ ## Generation and .new sidecars
171
+
172
+ `generate` reads the library (`.claude/pincer/library/`, the project's own copy) and
173
+ the agreement. For every stage that is on it copies `library/stages/<stage>/` to
174
+ `.claude/skills/pincer-<stage>/` (the project copy: edit this one), writes the thin
175
+ `.claude/commands/pincer-<stage>.md`, and per installed platform the Codex skill
176
+ `.agents/skills/pincer-<stage>/SKILL.md` (`/pincer-x` mentions become `$pincer-x`,
177
+ `$ARGUMENTS` becomes a sentence) and the Copilot prompt
178
+ `.github/prompts/pincer-<stage>.prompt.md`, both derived from the project copy as it
179
+ stands. Agents named by the rosters of the stages that are on are rendered to
180
+ `.claude/agents/<name>.md`: the library copy with its `tier:` line dropped and the
181
+ model and effort of its tier appended; with Codex installed each also gets
182
+ `.codex/agents/<name>.toml` (see Models). The model and effort lines of an agent file
183
+ and of a project copy belong to the generator and the tier belongs in the agreement: a
184
+ file that differs from the library copy only in those lines is the generator's to
185
+ overwrite or remove when no baseline records it; with a baseline in `.pincer.json`, a
186
+ hand-written model line conflicts like any other edit and gets a sidecar.
187
+
188
+ Baselines live in `.pincer.json` under `generated` when the installer wrote it. A
189
+ target whose hash matches its baseline or the fresh content is overwritten; one that
190
+ matches neither was edited by hand and is kept, with the fresh content beside it as
191
+ `<file>.new`. Derived files carry the comment
192
+ `<!-- Generated by scripts/pincer/cli.cjs generate from <source> ... -->`; a file that
193
+ still carries it is the generator's to overwrite or remove. The files of a stage
194
+ switched off, or of an agent dropped from every active roster, are removed when
195
+ unedited and reported when not. `--overwrite` treats every target as unedited.
196
+ Platforms come from `.pincer.json`, all three without one; `--platform` overrides them
197
+ for one run. Kit maintainers regenerate the shipped copies the same way, with
198
+ `--root template`, `--platform all` and a `--harness` file in which every optional
199
+ stage is off; a test fails when the shipped copies are stale.
200
+
201
+ ## Models
202
+
203
+ The agreement names a tier per role under `models.stages.<stage>` (the five optional
204
+ stages) and `models.agents.<name>`: `strong`, `standard`, `fast`, `inherit` for the
205
+ session's model, or `""` for not set, each with an optional effort `low`, `medium` or
206
+ `high`. The library's `models.json` maps a tier to each platform's model name: the
207
+ Claude row holds the aliases `opus`, `sonnet`, `haiku`; the Codex and Copilot rows name
208
+ the vendors' current models and change when a vendor ships. `models.map.<platform>.<tier>`
209
+ in the agreement overrides one entry. A library agent file may carry `tier:` as the
210
+ kit's default for that role (`codebase-explorer`: `standard`). Resolution, in
211
+ `scripts/pincer/models.cjs`: the agreement's tier, else the library's, else not set.
212
+ `inherit` and not set write no model field, so the host's own order stands; an effort
213
+ is written whenever set, with or without a model. The always-on stages take no tier:
214
+ their copies are installer-owned and the validator rejects an entry for them.
215
+
216
+ What the generator writes. Claude Code: `model:` and `effort:` in the agent files and,
217
+ for a stage with a tier, in its project copy and thin command; the model applies to the
218
+ stage's first turn and the session's model resumes on the next prompt. Codex:
219
+ `.codex/agents/<name>.toml` per roster agent, with `model` and
220
+ `model_reasoning_effort`, and one hint line at the top of the stage skill, which has no
221
+ model field. Copilot: `model:` in the prompt file; the agents are the Claude files,
222
+ which VS Code reads and maps itself. A tier the table cannot translate, or a missing
223
+ table, is reported once and the field is left out; doctor prints the resolved mapping
224
+ per installed platform. The host decides last: Claude Code's model allowlist and
225
+ subagent env vars, Copilot org policy and Codex project trust override any file.
226
+
227
+ | Role | Claude Code | Codex | Copilot (VS Code) |
228
+ |---|---|---|---|
229
+ | agent model | frontmatter, under the host's resolution order | agent file | the Claude file, mapped by VS Code |
230
+ | agent effort | frontmatter | agent file | not available |
231
+ | stage model | first turn of the stage | a hint in the skill | the prompt file |
232
+ | stage effort | frontmatter | the same hint | not available |
233
+
234
+ ## Recovery
235
+
236
+ - A failed check: fix the cause and run `verify` again. The failed `last_check` stays until a pass replaces it; it is information, not damage.
237
+ - An interrupted check (a signal, the timeout, exit 130): run `verify` again.
238
+ - A ticket that status reports as `invalid`, or whose lifecycle fields were edited by hand: the person repairs the file in their own terminal, then `verify` runs again (and `done` again if it was done). The session reports the problem and never edits those fields itself.
239
+ - A check that changed after it passed: `done` refuses with both hashes; run `verify` again.
240
+ - The rule under all of these: you never restore a ticket file from git (`checkout`, `restore`, `reset --hard`, `stash`) to clear a warning, because that revives an old receipt as if it were new evidence. On Claude Code the hook blocks it; elsewhere it is the standing rule in `AGENTS.md`.
241
+ - `write` refused: fix the answers file and run it again; nothing was written. One agreement marker without the other in `AGENTS.md`: put the missing marker back by hand, then `write`.
242
+ - A `.new` sidecar: compare it with the project copy, take what you want, delete the sidecar. `generate` writes a new one only when the fresh content differs again.
243
+
244
+ ## Platform notes
245
+
246
+ - Claude Code: `.claude/settings.json` wires two PreToolUse hooks. `block-dangerous.sh` refuses destructive commands. `ticket-guard.sh` refuses edits to the eight lifecycle fields and any shell command that writes, resets or restores a ticket file, allowing only exact calls of the project's own `scripts/pincer-ticket.sh start|verify|done|accept` and the `cli.cjs ticket` form at the same path; a lookalike script elsewhere is refused. It also refuses edits and shell writes to the engine (`scripts/pincer-*`, `scripts/pincer/`) and to the hooks (`.claude/hooks/`, `.claude/settings.json`), which `npx pincer-workflow update` refreshes. The guard matches `T-NN-*.md` under any directory named `tickets`; a `homes.tickets` that does not end in `tickets` loses hook coverage, and the rule in `AGENTS.md` carries the weight alone. The hook does not read `harness.json`. It is a guardrail against documented mistake forms, not a security boundary: the sandbox and the approval controls remain that boundary.
247
+ - Codex: reads `AGENTS.md` natively; the stages are skills under `.agents/skills/`, invoked as `$pincer-<stage>`; the roster agents are `.codex/agents/<name>.toml`, generated from the library, and Codex loads `.codex/` only in a project marked trusted. No hook. Posture in `~/.codex/config.toml`: `approval_policy = "on-request"` and `sandbox_mode = "workspace-write"`; never run with approvals disabled. Status warns about missing, failed or stale receipts and about hand-edited fields.
248
+ - Copilot: `.github/copilot-instructions.md` points at `AGENTS.md`; the stages are prompt files under `.github/prompts/`, invoked as `/pincer-<stage>` with prompt files enabled; the roster agents are the files under `.claude/agents/`, which VS Code reads in the Claude format. No hook; the same rule.
249
+ - Everywhere: Node 22 or newer, git and bash. `gh` only for the forge-facing steps the skills name. The engine runs offline.
@@ -1,38 +1,37 @@
1
- # PINCER Release Checklist
1
+ # Release checklist
2
2
 
3
- Audit the selected PRD and its evaluated candidate. This checklist applies to product
4
- changes in greenfield and brownfield repositories. It is read-only: failures return work
5
- to the owning stage or a new ticket.
3
+ `/pincer-release` first audits without writing files, then records the person's
4
+ acceptance and commits only those acceptance fields. Technical checks must pass
5
+ before asking for acceptance. Mark acceptance items pending until the person has
6
+ responded; pending acceptance alone is not a technical audit failure. Final PASS
7
+ requires every applicable item below. Give one sentence of evidence per item.
6
8
 
7
- ## Change identity and state
9
+ ## Tickets
8
10
 
9
- - [ ] `scripts/pincer-status.sh` selects the intended PRD with `status: built` and no warnings
10
- - [ ] Every ticket associated with that PRD is done and ready: current `last_check` and `verified` evidence before migration, a current passing attempt after it (`node scripts/pincer-runtime.cjs ready` exits 0)
11
- - [ ] `NOTES.md` names the selected PRD, reviewed base, candidate, and `evidence:` manifest; status reports the notes current and the evidence `ok`
12
- - [ ] The `Provenance` line names the evidence schema; a schema 2 candidate has no newer nonpassing local attempt for the same check and source inputs, and a fresh clone's `local verification history unavailable` limit is stated, not claimed as verification
13
- - [ ] Change records only: the selected change is `completed`, its authorization is `current` for the current agreement, `.prd/evidence/changes/<id>.json` names the evaluated candidate, and the audit selected, activated and completed nothing
14
- - [ ] Every file the evidence manifest lists is tracked; the working tree is clean before and after the audit
15
- - [ ] Strict coverage only (`Coverage strict …` in status): `node scripts/pincer-runtime.cjs coverage` reports structure complete, every in-scope scenario `delivered` on the evaluated candidate, every deferral or removal backed by its decision and user authorization, `delivery` distinguishing original from agreed scope, and an `adequate` adequacy judgment; `ready` names `COVERAGE_INCOMPLETE`, `SCOPE_UNAUTHORIZED`, `OBLIGATION_MISSING`, `REVIEW_MISSING` or `ADEQUACY_REQUIRED` as failures. A change without the capability is labeled `unverified`: say so, never imply strict coverage
11
+ - [ ] status prints the agreement; before acceptance, its only permitted warnings are `T-NN done without acceptance`; after acceptance there are no warnings
12
+ - [ ] every ticket of the change is `done` with `verified` on its status line
13
+ - [ ] no ticket line reads `invalid`
14
+ - [ ] `git log --oneline` shows one `T-NN: <title>` commit per ticket, in dependency order
16
15
 
17
- ## Scope and evidence
16
+ ## Evidence
18
17
 
19
- - [ ] Every requirement in the PRD has a disposition in the evidence manifest: delivered with passing checks, or deferred with recorded user authorization; none is blocked (strict coverage: the manifest is schema 3, its scenario rows are the inventory's, and the validator reconciled them with the committed candidate)
20
- - [ ] Ticket dependencies, acceptance criteria, and verification commands match the implemented change
21
- - [ ] The repository's candidate-wide release gate passes when run directly, without invoking the ticket state writer
22
- - [ ] Scope cuts and known limitations are explicit in the PRD or evaluation notes
18
+ - [ ] record the full evaluated `candidate` and pre-acceptance `audit_head`; their diff contains only retained evaluation artifacts and previously recorded acceptance fields, with no implementation, check, spec or agreement changes
19
+ - [ ] `git status --short` is empty before and after the read-only audit; no check wrote files or moved `HEAD`
20
+ - [ ] with tree audit, every command in `repo.checks` passes when run directly, output quoted
21
+ - [ ] with evidence on, `node scripts/pincer-evidence.cjs validate <manifest> --candidate <candidate>` prints `ok <candidate>` and every listed artifact is tracked; repeat after acceptance against the same original candidate
22
+ - [ ] with PR audit, the implementation PR is merged with green checks and the named deploy was observed; any acceptance PR required by the agreement is also merged with its required checks passing before final PASS
23
23
 
24
24
  ## Review
25
25
 
26
- - [ ] High-confidence review findings have file and line evidence and a disposition
27
- - [ ] Fixes made after evaluation use a new ticket, current verification, and a scoped commit
28
- - [ ] UI changes have visual checks with saved images in the evidence manifest (scenario, viewport, observed result); non-UI changes record `visual_review.applicable: false` with a reason
29
- - [ ] Security review reports locations and remediation without printing candidate secret values
30
- - [ ] No secret environment file is tracked; dependency and invalid-input checks run when applicable
26
+ - [ ] the review report exists and every finding has a file, a line and a disposition
27
+ - [ ] every implementation fix after evaluate went through its own ticket and `T-NN:` commit, followed by a new evaluation
28
+ - [ ] no secret environment file is tracked: `git ls-files | grep -i '^\.env'` shows only `.env.example`, or no entries when no environment example is needed
31
29
 
32
- ## Handover and verdict
30
+ ## Acceptance
33
31
 
34
- - [ ] Commit history identifies the plan, ticket work, evaluation, and any review-fix tickets
35
- - [ ] `NOTES.md` covers what shipped, cuts, limitations, next steps, dependencies, and the riskiest aging assumption
36
- - [ ] Platform or environment limits are stated without claiming untested support
37
- - [ ] The audit repaired no ticket, rewrote no evidence, changed no PRD state, and published nothing
38
- - [ ] Every failed or skipped required item is named before the final PASS or FAIL verdict, which names the candidate
32
+ - [ ] the person saw the result and every ticket's `Done when` lines before being asked
33
+ - [ ] every done ticket shows `accepted by <name>` in status, the name from `people` with `accepts: true`
34
+ - [ ] `accepted_words` holds the person's own words, not a paraphrase or synthetic approval
35
+ - [ ] only `accept` wrote acceptance; the complete staged and committed diff from `audit_head` changes only `accepted`, `accepted_by`, and `accepted_words` in the intended tickets' frontmatter, never their body, receipt or any other file
36
+ - [ ] new acceptance is retained in a separate `accept: <candidate short sha>` commit; the verdict identifies both the evaluated candidate and acceptance commit, and `git status --short` is empty
37
+ - [ ] no push, merge or publication occurred without the user's authorization; any required remote acceptance step is complete or explicitly pending rather than passed
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+ // PINCER blueprint — the writer (docs/engine.md, "Blueprint"). Reads an answers
3
+ // file in the shape of harness.json, validates it, writes harness.json, renders
4
+ // the agreement from library/agreement.md between the markers in AGENTS.md and
5
+ // runs the generator. Everything outside the markers is left byte for byte.
6
+ const fs = require('node:fs');
7
+ const path = require('node:path');
8
+ const util = require('./util.cjs');
9
+ const harness = require('./harness.cjs');
10
+ const generate = require('./generate.cjs');
11
+ const models = require('./models.cjs');
12
+
13
+ const AGREEMENT_START = '<!-- pincer:agreement:start -->';
14
+ const AGREEMENT_END = '<!-- pincer:agreement:end -->';
15
+ const AGENTS_FILE = 'AGENTS.md';
16
+ const { LIBRARY, libraryDir } = generate;
17
+ const TEMPLATE_FILE = 'agreement.md';
18
+ const SKELETON_FILE = 'agents-skeleton.md';
19
+ const CODE_GATE = 'passing check per ticket';
20
+
21
+ // {{slot}} and {{#each list}}…{{/each}} with {{.}} for the item; nothing else.
22
+ function fill(template, view) {
23
+ const lookup = (key) => {
24
+ if (!(key in view)) throw new Error(`${TEMPLATE_FILE}: unknown slot {{${key}}}`);
25
+ return view[key];
26
+ };
27
+ const expanded = template.replace(/\{\{#each ([\w.]+)\}\}\n?([\s\S]*?)\{\{\/each\}\}\n?/g, (_, key, block) => {
28
+ const items = lookup(key);
29
+ if (!Array.isArray(items)) throw new Error(`${TEMPLATE_FILE}: {{#each ${key}}} needs a list`);
30
+ return items.map((item) => block.replace(/\{\{\.\}\}/g, String(item))).join('');
31
+ });
32
+ return expanded.replace(/\{\{([\w.]+)\}\}/g, (_, key) => {
33
+ const value = lookup(key);
34
+ if (Array.isArray(value)) throw new Error(`${TEMPLATE_FILE}: {{${key}}} is a list; use {{#each}}`);
35
+ return String(value);
36
+ });
37
+ }
38
+
39
+ const or = (value, fallback) => (typeof value === 'string' && value.trim() ? value.trim() : fallback);
40
+ const yesNo = (value) => (value ? 'yes' : 'no');
41
+
42
+ // The one-line pipeline: the stages that run, in order, each gate in bold.
43
+ function pipeline(h) {
44
+ const who = harness.acceptors(h);
45
+ const gates = {
46
+ plan: or(h.stages.plan.gate, 'spec approved'),
47
+ code: CODE_GATE,
48
+ evaluate: h.stages.evaluate.evidence === false ? 'review done' : 'evidence saved',
49
+ release: `accepted by ${who.length ? who.join(', ') : 'a person'}`,
50
+ };
51
+ return harness.STAGES.filter((name) => harness.stageOn(h, name))
52
+ .map((name) => (gates[name] ? `${name} (**${gates[name]}**)` : name))
53
+ .join(' → ');
54
+ }
55
+
56
+ // The slots of library/agreement.md from a normalized agreement; the library
57
+ // supplies the agents' default tiers for the Models line.
58
+ function view(h, lib) {
59
+ const homes = harness.homes(h);
60
+ const simplify = harness.OPTIONAL_STAGES.filter((name) => !harness.stageOn(h, name))
61
+ .map((name) => `${name} is off${or(h.stages[name].reason, '') ? ` — ${h.stages[name].reason.trim()}` : ''}.`);
62
+ const people = h.people.map((p) => `${p.name.trim()}${or(p.role, '') ? ` (${p.role.trim()})` : ''}${p.accepts === true ? ' — accepts results' : ''}`);
63
+ const forge = { github: 'GitHub', none: 'none', other: 'other' }[h.systems.forge] || h.systems.forge;
64
+ return {
65
+ name: or(h.project.name, 'unnamed project'),
66
+ kind: h.project.kind,
67
+ kindReason: or(h.project.reason, '') ? ` — ${h.project.reason.trim()}` : '',
68
+ forge,
69
+ remote: or(h.systems.remote, 'none'),
70
+ ci: yesNo(h.systems.ci),
71
+ deploy: or(h.systems.deploy, 'none'),
72
+ secrets: or(h.systems.secrets, 'not recorded'),
73
+ pipeline: pipeline(h),
74
+ spec: !harness.stageOn(h, 'plan') ? 'none (plan is off)' : h.stages.plan.spec === 'forge' ? 'forge' : `${homes.specs}/`,
75
+ tickets: h.stages.narrow.tickets === 'forge' ? 'forge' : `${homes.tickets}/`,
76
+ evidence: !harness.stageOn(h, 'evaluate') ? 'none (evaluate is off)' : h.stages.evaluate.evidence === false ? 'none' : `${homes.evidence}/`,
77
+ simplify: simplify.length ? simplify : ['Every stage runs; nothing switched off.'],
78
+ people: people.length ? people : ['nobody named yet'],
79
+ branches: h.repo.branches === 'per-ticket' ? 'one branch per ticket' : 'commit to main',
80
+ pr: yesNo(h.repo.pr),
81
+ merge: h.repo.merge,
82
+ protection: or(h.repo.protection, 'none'),
83
+ checks: h.repo.checks.length ? h.repo.checks.join(', ') : 'none',
84
+ open: h.open.length ? h.open : ['none'],
85
+ firstJob: or(h.firstJob, 'not named yet'),
86
+ models: models.agreementLine(models.effective(h, lib)),
87
+ };
88
+ }
89
+
90
+ // The agreement text, without the markers; ends with one newline.
91
+ function renderAgreement(h, { library } = {}) {
92
+ const lib = library || libraryDir(process.cwd());
93
+ const template = fs.readFileSync(path.join(lib, TEMPLATE_FILE), 'utf8');
94
+ return `${fill(template, view(harness.normalize(h), lib)).trimEnd()}\n`;
95
+ }
96
+
97
+ // AGENTS.md with the agreement between the markers. Markers present: the text
98
+ // between them is replaced. No markers: the block goes after a leading title,
99
+ // else at the top. One marker alone is a problem, nothing is changed.
100
+ function splice(text, agreement) {
101
+ const block = `${AGREEMENT_START}\n${agreement}${AGREEMENT_END}`;
102
+ const start = text.indexOf(AGREEMENT_START), end = text.indexOf(AGREEMENT_END);
103
+ if (start === -1 && end === -1) {
104
+ const title = /^# [^\n]*\n/.exec(text);
105
+ const head = title ? title[0] : '';
106
+ const rest = text.slice(head.length).replace(/^\n+/, '');
107
+ return { text: `${head}${head ? '\n' : ''}${block}\n${rest ? `\n${rest}` : ''}` };
108
+ }
109
+ if (start === -1 || end === -1 || end < start) return { problem: `${AGENTS_FILE}: found one agreement marker without the other (${AGREEMENT_START} … ${AGREEMENT_END})` };
110
+ return { text: `${text.slice(0, start)}${block}${text.slice(end + AGREEMENT_END.length)}` };
111
+ }
112
+
113
+ function write(root, { answers = harness.HARNESS_FILE, label, library } = {}) {
114
+ const name = label || answers;
115
+ const file = path.isAbsolute(answers) ? answers : path.join(root, answers);
116
+ let raw;
117
+ try { raw = fs.readFileSync(file, 'utf8'); } catch (error) {
118
+ return { ok: false, exit: 3, problems: [`${name}: cannot read (${error.code === 'ENOENT' ? 'no such file' : error.message})`] };
119
+ }
120
+ let doc;
121
+ try { doc = JSON.parse(raw); } catch (error) { return { ok: false, exit: 3, problems: [`${name}: invalid JSON (${error.message})`] }; }
122
+ const problems = harness.validate(doc);
123
+ if (problems.length) return { ok: false, exit: 3, problems: problems.map((p) => `${name}: ${p}`) };
124
+ const normalized = harness.normalize(doc);
125
+ const lib = libraryDir(root, library);
126
+ const agreement = renderAgreement(normalized, { library: lib });
127
+ const agentsFile = path.join(root, AGENTS_FILE);
128
+ const current = fs.existsSync(agentsFile) ? fs.readFileSync(agentsFile, 'utf8') : fs.readFileSync(path.join(lib, SKELETON_FILE), 'utf8');
129
+ const spliced = splice(current, agreement);
130
+ if (spliced.problem) return { ok: false, exit: 1, problems: [spliced.problem] };
131
+ util.atomicWrite(path.join(root, harness.HARNESS_FILE), `${JSON.stringify(normalized, null, 2)}\n`);
132
+ util.atomicWrite(agentsFile, spliced.text);
133
+ const generated = generate.generate(root, { harness: normalized, library: lib });
134
+ return { ok: true, harness: normalized, agreement, generated, written: [harness.HARNESS_FILE, AGENTS_FILE] };
135
+ }
136
+
137
+ module.exports = { AGREEMENT_START, AGREEMENT_END, AGENTS_FILE, LIBRARY, write, renderAgreement, splice, fill, libraryDir };