pincer-workflow 0.7.0 → 1.0.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 (139) hide show
  1. package/README.md +157 -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 +48 -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 +236 -0
  72. package/template/docs/release-checklist.md +22 -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 +373 -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 +161 -0
  82. package/template/scripts/pincer/ticket.cjs +203 -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,236 @@
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
+ The floor has two halves: a ticket is `done` only on a receipt, and accepted only when
133
+ a named person said so. `accept` writes `accepted`, `accepted_by` and `accepted_words`
134
+ together, and the validator refuses a ticket carrying one or two of the three. The name
135
+ must be in `people` with `accepts: true`; the words are the person's, quoted as they
136
+ said them, never a paraphrase. Acceptance gates nothing in the lifecycle (the next
137
+ ticket may start), but status warns about every done ticket without it and names the
138
+ next step: `/pincer-release` when that stage is on, else the `accept` command itself.
139
+ Release passes only when every done ticket of the change carries it. The agent runs
140
+ `accept` only after the person said so.
141
+
142
+ ## Evidence manifest schema 1
143
+
144
+ `<homes.evidence>/<candidate>/manifest.json`, where the directory's basename is the
145
+ candidate's full commit id. Top-level keys, all required and no others: `schema` (1),
146
+ `candidate` and `base` (40 hex characters), `created` (UTC timestamp), `spec` (a path
147
+ that exists), `environment` (`tools` and `limitations`, each a list of short
148
+ strings), `checks`, `visual_review`, `artifacts`. Each check has `id` (`C-NN`), `kind` (`command`, `review`, `visual`),
149
+ `required`, `result` (`passed`, `failed`, `unverified`), `command` (required for a
150
+ command check), `log` (an artifact path) and an optional `note`; a required check
151
+ must be `passed`. `visual_review` has `applicable` and, when false, `reason`.
152
+ `artifacts` maps paths inside the manifest's directory to their sha256, and the
153
+ digests must match the files. The validator checks that the record is consistent, not
154
+ that the commands ran. Status reports `Evidence:` per manifest and release reads the
155
+ same validator.
156
+
157
+ ## Generation and .new sidecars
158
+
159
+ `generate` reads the library (`.claude/pincer/library/`, the project's own copy) and
160
+ the agreement. For every stage that is on it copies `library/stages/<stage>/` to
161
+ `.claude/skills/pincer-<stage>/` (the project copy: edit this one), writes the thin
162
+ `.claude/commands/pincer-<stage>.md`, and per installed platform the Codex skill
163
+ `.agents/skills/pincer-<stage>/SKILL.md` (`/pincer-x` mentions become `$pincer-x`,
164
+ `$ARGUMENTS` becomes a sentence) and the Copilot prompt
165
+ `.github/prompts/pincer-<stage>.prompt.md`, both derived from the project copy as it
166
+ stands. Agents named by the rosters of the stages that are on are rendered to
167
+ `.claude/agents/<name>.md`: the library copy with its `tier:` line dropped and the
168
+ model and effort of its tier appended; with Codex installed each also gets
169
+ `.codex/agents/<name>.toml` (see Models). The model and effort lines of an agent file
170
+ and of a project copy belong to the generator and the tier belongs in the agreement: a
171
+ file that differs from the library copy only in those lines is the generator's to
172
+ overwrite or remove when no baseline records it; with a baseline in `.pincer.json`, a
173
+ hand-written model line conflicts like any other edit and gets a sidecar.
174
+
175
+ Baselines live in `.pincer.json` under `generated` when the installer wrote it. A
176
+ target whose hash matches its baseline or the fresh content is overwritten; one that
177
+ matches neither was edited by hand and is kept, with the fresh content beside it as
178
+ `<file>.new`. Derived files carry the comment
179
+ `<!-- Generated by scripts/pincer/cli.cjs generate from <source> ... -->`; a file that
180
+ still carries it is the generator's to overwrite or remove. The files of a stage
181
+ switched off, or of an agent dropped from every active roster, are removed when
182
+ unedited and reported when not. `--overwrite` treats every target as unedited.
183
+ Platforms come from `.pincer.json`, all three without one; `--platform` overrides them
184
+ for one run. Kit maintainers regenerate the shipped copies the same way, with
185
+ `--root template`, `--platform all` and a `--harness` file in which every optional
186
+ stage is off; a test fails when the shipped copies are stale.
187
+
188
+ ## Models
189
+
190
+ The agreement names a tier per role under `models.stages.<stage>` (the five optional
191
+ stages) and `models.agents.<name>`: `strong`, `standard`, `fast`, `inherit` for the
192
+ session's model, or `""` for not set, each with an optional effort `low`, `medium` or
193
+ `high`. The library's `models.json` maps a tier to each platform's model name: the
194
+ Claude row holds the aliases `opus`, `sonnet`, `haiku`; the Codex and Copilot rows name
195
+ the vendors' current models and change when a vendor ships. `models.map.<platform>.<tier>`
196
+ in the agreement overrides one entry. A library agent file may carry `tier:` as the
197
+ kit's default for that role (`codebase-explorer`: `standard`). Resolution, in
198
+ `scripts/pincer/models.cjs`: the agreement's tier, else the library's, else not set.
199
+ `inherit` and not set write no model field, so the host's own order stands; an effort
200
+ is written whenever set, with or without a model. The always-on stages take no tier:
201
+ their copies are installer-owned and the validator rejects an entry for them.
202
+
203
+ What the generator writes. Claude Code: `model:` and `effort:` in the agent files and,
204
+ for a stage with a tier, in its project copy and thin command; the model applies to the
205
+ stage's first turn and the session's model resumes on the next prompt. Codex:
206
+ `.codex/agents/<name>.toml` per roster agent, with `model` and
207
+ `model_reasoning_effort`, and one hint line at the top of the stage skill, which has no
208
+ model field. Copilot: `model:` in the prompt file; the agents are the Claude files,
209
+ which VS Code reads and maps itself. A tier the table cannot translate, or a missing
210
+ table, is reported once and the field is left out; doctor prints the resolved mapping
211
+ per installed platform. The host decides last: Claude Code's model allowlist and
212
+ subagent env vars, Copilot org policy and Codex project trust override any file.
213
+
214
+ | Role | Claude Code | Codex | Copilot (VS Code) |
215
+ |---|---|---|---|
216
+ | agent model | frontmatter, under the host's resolution order | agent file | the Claude file, mapped by VS Code |
217
+ | agent effort | frontmatter | agent file | not available |
218
+ | stage model | first turn of the stage | a hint in the skill | the prompt file |
219
+ | stage effort | frontmatter | the same hint | not available |
220
+
221
+ ## Recovery
222
+
223
+ - 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.
224
+ - An interrupted check (a signal, the timeout, exit 130): run `verify` again.
225
+ - 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.
226
+ - A check that changed after it passed: `done` refuses with both hashes; run `verify` again.
227
+ - 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`.
228
+ - `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`.
229
+ - 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.
230
+
231
+ ## Platform notes
232
+
233
+ - 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.
234
+ - 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.
235
+ - 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.
236
+ - 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,32 @@
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
+ What `/pincer-release` audits before the person accepts. Read-only: every item is
4
+ checked from `scripts/pincer-status.sh` output or from git, and a failure goes back to
5
+ its stage through a new ticket. Pass or fail per item, one sentence of evidence each.
6
6
 
7
- ## Change identity and state
7
+ ## Tickets
8
8
 
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
9
+ - [ ] `scripts/pincer-status.sh` prints the agreement line and no `Warnings:` block
10
+ - [ ] every ticket of the change is `done` with `verified` on its status line
11
+ - [ ] no ticket line reads `invalid`
12
+ - [ ] `git log --oneline` shows one `T-NN: <title>` commit per ticket, in dependency order
16
13
 
17
- ## Scope and evidence
14
+ ## Evidence
18
15
 
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
16
+ - [ ] `git status --short` is empty before the audit and after it
17
+ - [ ] every command in `repo.checks` passes when run directly, output quoted
18
+ - [ ] with evidence on: `node scripts/pincer-evidence.cjs validate <manifest> --candidate <sha>` prints `ok <sha>` and every artifact it lists is tracked
19
+ - [ ] with `stages.release.audit` = `pr`: the pull request is merged, its checks are green, and the deploy named in `systems.deploy` was observed
23
20
 
24
21
  ## Review
25
22
 
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
23
+ - [ ] the review report exists (a file under the evidence home, or the PR comment) and every finding has a file, a line and a disposition
24
+ - [ ] every fix after evaluate went through its own ticket and `T-NN:` commit, never an ad-hoc commit
25
+ - [ ] no secret environment file is tracked: `git ls-files | grep -i '^\.env'` shows only `.env.example`
31
26
 
32
- ## Handover and verdict
27
+ ## Acceptance
33
28
 
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
29
+ - [ ] the person saw the result and every ticket's `Done when` lines before being asked
30
+ - [ ] every done ticket shows `accepted by <name>` in status, the name from `people` with `accepts: true`
31
+ - [ ] `accepted_words` holds the person's own words, not a paraphrase
32
+ - [ ] the audit wrote nothing but the acceptances: no `verify`, no `done`, no ticket edits, and `git status --short` is still empty
@@ -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 };