pincer-workflow 0.6.0 → 0.7.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.
- package/README.md +101 -14
- package/package.json +2 -2
- package/template/.agents/skills/pincer-code/SKILL.md +4 -1
- package/template/.agents/skills/pincer-narrow/SKILL.md +11 -1
- package/template/.agents/skills/pincer-status/SKILL.md +19 -5
- package/template/.claude/commands/pincer-code.md +4 -1
- package/template/.claude/commands/pincer-narrow.md +11 -1
- package/template/.claude/commands/pincer-status.md +19 -5
- package/template/.github/prompts/pincer-code.prompt.md +4 -1
- package/template/.github/prompts/pincer-narrow.prompt.md +11 -1
- package/template/.github/prompts/pincer-status.prompt.md +19 -5
- package/template/docs/runtime-contracts.md +88 -2
- package/template/scripts/pincer-runtime/readiness.cjs +6 -1
- package/template/scripts/pincer-runtime/resume.cjs +69 -1
- package/template/scripts/pincer-runtime/scaffold.cjs +254 -0
- package/template/scripts/pincer-runtime.cjs +30 -5
package/README.md
CHANGED
|
@@ -31,15 +31,80 @@ On Codex CLI the commands are skills, invoked by mention rather than slash:
|
|
|
31
31
|
`$pincer-plan <brief>` → `$pincer-narrow` → `$pincer-code` → `$pincer-evaluate`
|
|
32
32
|
→ `$pincer-release`, and `$pincer-status`.
|
|
33
33
|
|
|
34
|
+
## Your first change, end to end
|
|
35
|
+
|
|
36
|
+
The five commands above are the shape of the work. This is the whole journey for one
|
|
37
|
+
change on a project that wants strict requirement coverage — the path most of the
|
|
38
|
+
runtime exists to support. Every step is a real command; none of them is optional
|
|
39
|
+
ceremony, and the two read-only reports (`coverage scaffold`, `resume --brief`) write
|
|
40
|
+
nothing.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Install, then plan and break down the work.
|
|
44
|
+
npx pincer-workflow init
|
|
45
|
+
# /pincer-plan <brief> → .prd/prd-v1.md with R-NN requirements and S-NN scenarios
|
|
46
|
+
# /pincer-narrow → tickets/T-*.md, one per unit of work
|
|
47
|
+
|
|
48
|
+
# 2. Register the change and select it in this worktree.
|
|
49
|
+
node scripts/pincer-runtime.cjs register --prd .prd/prd-v1.md
|
|
50
|
+
node scripts/pincer-runtime.cjs change select prd-v1
|
|
51
|
+
|
|
52
|
+
# 3. Choose your coverage. Default is fine: tickets, checks and evidence work without
|
|
53
|
+
# this step. Strict coverage additionally binds every S-NN to a ticket and a check.
|
|
54
|
+
# Start the map from the draft — it lists every scenario and ticket for you:
|
|
55
|
+
node scripts/pincer-runtime.cjs coverage scaffold --change prd-v1
|
|
56
|
+
|
|
57
|
+
# 4. Author .prd/coverage/prd-v1.json yourself from that draft, then review the links.
|
|
58
|
+
# The draft never invents a link, a check command or a scope disposition, and it is
|
|
59
|
+
# not a map: `coverage adopt` refuses it. Working through its Unresolved list is the
|
|
60
|
+
# authoring step.
|
|
61
|
+
|
|
62
|
+
# 5. Adopt strict coverage explicitly, then record the user's actual approval.
|
|
63
|
+
node scripts/pincer-runtime.cjs coverage adopt --preview --change prd-v1
|
|
64
|
+
node scripts/pincer-runtime.cjs coverage adopt --apply --change prd-v1
|
|
65
|
+
node scripts/pincer-runtime.cjs change authorize prd-v1 --agreement <digest> \
|
|
66
|
+
--reference "<where the user said it>" --excerpt "<the user's approval, quoted>"
|
|
67
|
+
node scripts/pincer-runtime.cjs change activate prd-v1
|
|
68
|
+
|
|
69
|
+
# 6. Implement. One commit per ticket; `done` refuses without a current passing check.
|
|
70
|
+
# /pincer-code
|
|
71
|
+
|
|
72
|
+
# 7. Come back tomorrow, or in a fresh session with no memory of any of this:
|
|
73
|
+
node scripts/pincer-runtime.cjs resume --brief
|
|
74
|
+
# → one screen: the change, the authorization verdict, the coverage label, the
|
|
75
|
+
# blocker categories with counts, and the exact next command. `resume` (no
|
|
76
|
+
# --brief) prints every row; the brief names the command that shows them.
|
|
77
|
+
|
|
78
|
+
# 8. Evaluate and audit.
|
|
79
|
+
# /pincer-evaluate → validated evidence under .prd/evidence/
|
|
80
|
+
# /pincer-release → read-only pass/fail audit
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Adoption grants no approval, and approval is never inferred: `coverage adopt` records
|
|
84
|
+
the agreement, `change authorize` records what the user actually said about it, and a
|
|
85
|
+
generic "continue" authorizes no revised scope. Edit the PRD, the tickets or the map
|
|
86
|
+
afterwards and the agreement changes — the runtime reports `AGREEMENT_CHANGED` and
|
|
87
|
+
waits for a fresh decision rather than carrying the old approval forward.
|
|
88
|
+
|
|
89
|
+
### Pinning a management kit
|
|
90
|
+
|
|
91
|
+
The kit under `scripts/` is code that the workflow also runs *on* the project. When you
|
|
92
|
+
are changing that code, manage the work with a pinned released copy from outside the
|
|
93
|
+
tree rather than with the version you are editing — extract the scripts from a release
|
|
94
|
+
tag into a directory outside the repository, record its digest, and run the lifecycle
|
|
95
|
+
commands from there. A project that only uses PINCER never needs this; a project that
|
|
96
|
+
develops it does.
|
|
97
|
+
|
|
34
98
|
### Claude Code
|
|
35
99
|
|
|
36
|
-
|
|
100
|
+
Installs complete: the commands, the two subagents, the `.env` deny rules and
|
|
37
101
|
the destructive-command and ticket-guard hooks install to `.claude/`. Start
|
|
38
|
-
`claude` in the repo and run `/pincer-plan <brief>`.
|
|
102
|
+
`claude` in the repo and run `/pincer-plan <brief>`. See
|
|
103
|
+
[platform support](#platform-support) for what that install has been observed to do.
|
|
39
104
|
|
|
40
105
|
### Codex CLI
|
|
41
106
|
|
|
42
|
-
|
|
107
|
+
Installs complete as well: `AGENTS.md` loads natively, and the six commands
|
|
43
108
|
install as skills under `.agents/skills/`, which Codex discovers from the repo
|
|
44
109
|
(no copying into your home directory — Codex removed custom prompts and
|
|
45
110
|
`~/.codex/prompts/` in early 2026). Start `codex` in the repo, then type
|
|
@@ -61,6 +126,28 @@ Full notes in `.codex/README.md`.
|
|
|
61
126
|
Enable `"chat.promptFiles": true` in VS Code settings, then run `/pincer-plan`
|
|
62
127
|
in chat. `.github/copilot-instructions.md` is wired to `AGENTS.md`.
|
|
63
128
|
|
|
129
|
+
## Platform support
|
|
130
|
+
|
|
131
|
+
Two different claims, kept apart on purpose. **Installation-tested** means the packed
|
|
132
|
+
artifact installs and its files land where they should, proven by the suites in this
|
|
133
|
+
repository on every push. **Live-observed** means a real agent session carried a real
|
|
134
|
+
change through the workflow on that surface and the artifacts were reviewed. An
|
|
135
|
+
installation check is not a platform trial, and packaged parity is not live success.
|
|
136
|
+
|
|
137
|
+
| Surface | Installation-tested | Live-observed |
|
|
138
|
+
| --- | --- | --- |
|
|
139
|
+
| Claude Code (`npx pincer-workflow init`) | yes — packed install, layout and generated parity | not for this release |
|
|
140
|
+
| Codex CLI (`.agents/skills/`) | yes — packed install and skill layout | not for this release |
|
|
141
|
+
| GitHub Copilot (VS Code prompts) | yes — packed install and prompt layout | no |
|
|
142
|
+
| Claude Code plugin (marketplace) | yes — plugin build parity | no |
|
|
143
|
+
| Windows (native, no POSIX shell) | no | no |
|
|
144
|
+
|
|
145
|
+
No live platform trial has been observed for the current release. The work that would
|
|
146
|
+
produce one — a full strict journey on Claude Code and on Codex, plus a real cross-agent
|
|
147
|
+
handoff resumed from files — is open, and until it reports, every "live-observed" cell
|
|
148
|
+
above stays as it reads. Earlier releases have their own recorded trials under `docs/`;
|
|
149
|
+
those describe the versions they were run against, not this one.
|
|
150
|
+
|
|
64
151
|
## Claude Code plugin (alternative)
|
|
65
152
|
|
|
66
153
|
Claude users can install PINCER as a plugin instead — commands arrive
|
|
@@ -80,17 +167,6 @@ and Copilot). Installing both gives you duplicate commands. Plugin users who
|
|
|
80
167
|
want the repo-side rules too can copy `AGENTS.md` from the
|
|
81
168
|
[template](template/AGENTS.md).
|
|
82
169
|
|
|
83
|
-
## Update
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npx pincer-workflow@latest update
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Files you never touched are refreshed in place. (Installs older than v0.2.0 gain the ticket state machine, the status report and the ticket-guard hook on update; `.claude/settings.json` conflicts if you edited it — merge the new hook entry from the `.new` file. v0.2.2 replaces the Codex adapter: the commands are now skills in `.agents/skills/` invoked as `$pincer-*`, since Codex no longer loads `~/.codex/prompts/` — you can delete the copies there. v0.2.3 ships the playbooks, rubrics and templates under `.claude/` on every platform, which Codex- and Copilot-only installs were missing. v0.3.0 makes trust revocable: every verification attempt is recorded, `done` re-runs the check, tickets carry `prd:`, the release audit is read-only, and `.pincer.json` moves to schema 2 — an install from 0.2.x is treated as an untrusted baseline, so on the first update every changed file arrives as a `.new` proposal once; hooks now need Node 18+. v0.4.0 carries requirement IDs and `Proves:` checks from the PRD to evaluation, adds `profile: small|standard`, and saves candidate evidence under `.prd/evidence/`; the update is additive, but an existing `NOTES.md` without an `evidence:` manifest reads as stale until `/pincer-evaluate` is re-run. v0.4.1 is playbook wording only: the code playbook gains one recovery exception (a tree back at the evaluated candidate is restored by the user and nothing is committed), evaluate records one check per command, and plan asks only the open part of a partly answered question. v0.5.0 adds the runtime: `scripts/pincer-runtime.cjs` with its modules under `scripts/pincer-runtime/` now implements the ticket lifecycle, status, migration and evidence export, and `pincer-ticket.sh` / `pincer-status.sh` delegate to it, so every command needs Node.js 18+; unmigrated projects keep their receipts and behavior. Run `node scripts/pincer-runtime.cjs migrate --preview --prd .prd/prd-vN.md` to see what migration would change; after `--apply`, `verify` records attempts with captured logs under the ignored `.pincer/runtime/` and writes no receipt into the ticket, `done` consumes the current passing attempt against the current source, and `/pincer-evaluate` exports evidence schema 2 from runtime attempts. `docs/runtime-contracts.md` is the contract; `pincer doctor` tells you when a migration is available and never migrates on its own. The next version keeps several changes side by side: `register` writes a change record (schema 2) that retains every earlier change, the worktree selects the change it works on (`change select`), the user's approval is recorded against the reviewed agreement (`change authorize`), the lifecycle is explicit (`change activate|pause|resume|complete|reopen|cancel|supersede`), and `resume` prints where to continue from files alone. A v0.5.0 binding converts with the same `migrate --preview` / `--apply`; the converted change is planned with no authorization, its old attempts and evaluation are history until verified again, and the old free-text authorization never authorizes execution. Selection and local attempts are per worktree; records are tracked and shared through git; completed means ready for evaluation, not released. The next version also raises the floor to Node.js 22+, because 18 and 20 are past end of life. It separately fixes a defect that was never version-specific: a command whose output exceeded one pipe buffer (65,536 bytes) lost the tail when the process exited and still exited 0, on every version including 22 and 24. Every write is now synchronous, so piped `--json` is complete. Updating the kit inside a migrated project changes tracked runtime files, so every done ticket's attempt reads `SOURCE_CHANGED` until it is verified again; update between changes, not mid-ticket. An update from 0.4.x leaves the retired `scripts/pincer-ticket-lib.sh` on disk; `pincer doctor` names it and it is safe to delete.) Files you edited are left
|
|
90
|
-
alone — the new version lands next to them as `<file>.new` for a manual merge.
|
|
91
|
-
`npx pincer-workflow doctor` checks the health of an install (hook executable,
|
|
92
|
-
`.gitignore` covering `.env*`, no unmerged `*.new` files, version current).
|
|
93
|
-
|
|
94
170
|
## What you get
|
|
95
171
|
|
|
96
172
|
| Piece | Purpose |
|
|
@@ -110,6 +186,17 @@ alone — the new version lands next to them as `<file>.new` for a manual merge.
|
|
|
110
186
|
| `docs/release-checklist.md` | General, read-only candidate audit used by `/pincer-release` |
|
|
111
187
|
| `docs/dry-run-checklist.md` | Separate manual platform trial for the Pincer kit |
|
|
112
188
|
|
|
189
|
+
## Update
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npx pincer-workflow@latest update
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Files you never touched are refreshed in place. (Installs older than v0.2.0 gain the ticket state machine, the status report and the ticket-guard hook on update; `.claude/settings.json` conflicts if you edited it — merge the new hook entry from the `.new` file. v0.2.2 replaces the Codex adapter: the commands are now skills in `.agents/skills/` invoked as `$pincer-*`, since Codex no longer loads `~/.codex/prompts/` — you can delete the copies there. v0.2.3 ships the playbooks, rubrics and templates under `.claude/` on every platform, which Codex- and Copilot-only installs were missing. v0.3.0 makes trust revocable: every verification attempt is recorded, `done` re-runs the check, tickets carry `prd:`, the release audit is read-only, and `.pincer.json` moves to schema 2 — an install from 0.2.x is treated as an untrusted baseline, so on the first update every changed file arrives as a `.new` proposal once; hooks now need Node 18+. v0.4.0 carries requirement IDs and `Proves:` checks from the PRD to evaluation, adds `profile: small|standard`, and saves candidate evidence under `.prd/evidence/`; the update is additive, but an existing `NOTES.md` without an `evidence:` manifest reads as stale until `/pincer-evaluate` is re-run. v0.4.1 is playbook wording only: the code playbook gains one recovery exception (a tree back at the evaluated candidate is restored by the user and nothing is committed), evaluate records one check per command, and plan asks only the open part of a partly answered question. v0.5.0 adds the runtime: `scripts/pincer-runtime.cjs` with its modules under `scripts/pincer-runtime/` now implements the ticket lifecycle, status, migration and evidence export, and `pincer-ticket.sh` / `pincer-status.sh` delegate to it, so every command needs Node.js 18+; unmigrated projects keep their receipts and behavior. Run `node scripts/pincer-runtime.cjs migrate --preview --prd .prd/prd-vN.md` to see what migration would change; after `--apply`, `verify` records attempts with captured logs under the ignored `.pincer/runtime/` and writes no receipt into the ticket, `done` consumes the current passing attempt against the current source, and `/pincer-evaluate` exports evidence schema 2 from runtime attempts. `docs/runtime-contracts.md` is the contract; `pincer doctor` tells you when a migration is available and never migrates on its own. v0.6.0 keeps several changes side by side: `register` writes a change record (schema 2) that retains every earlier change, the worktree selects the change it works on (`change select`), the user's approval is recorded against the reviewed agreement (`change authorize`), the lifecycle is explicit (`change activate|pause|resume|complete|reopen|cancel|supersede`), and `resume` prints where to continue from files alone. A v0.5.0 binding converts with the same `migrate --preview` / `--apply`; the converted change is planned with no authorization, its old attempts and evaluation are history until verified again, and the old free-text authorization never authorizes execution. Selection and local attempts are per worktree; records are tracked and shared through git; completed means ready for evaluation, not released. v0.6.0 also raises the floor to Node.js 22+, because 18 and 20 are past end of life. It separately fixes a defect that was never version-specific: a command whose output exceeded one pipe buffer (65,536 bytes) lost the tail when the process exited and still exited 0, on every version including 22 and 24. Every write is now synchronous, so piped `--json` is complete. Updating the kit inside a migrated project changes tracked runtime files, so every done ticket's attempt reads `SOURCE_CHANGED` until it is verified again; update between changes, not mid-ticket. An update from 0.4.x leaves the retired `scripts/pincer-ticket-lib.sh` on disk; `pincer doctor` names it and it is safe to delete.) Files you edited are left
|
|
196
|
+
alone — the new version lands next to them as `<file>.new` for a manual merge.
|
|
197
|
+
`npx pincer-workflow doctor` checks the health of an install (hook executable,
|
|
198
|
+
`.gitignore` covering `.env*`, no unmerged `*.new` files, version current).
|
|
199
|
+
|
|
113
200
|
## Design principles
|
|
114
201
|
|
|
115
202
|
- **Approval gates scale with decision cost** — a human owns architecture, scope,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pincer-workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "PINCER — a PRD-driven agentic delivery workflow for Claude Code, Codex CLI, and GitHub Copilot. Plan · Investigate · Narrow · Code · Evaluate · Release.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pincer": "bin/pincer.js"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"node": ">=22"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "node test/smoke.test.js && node test/installer.test.js && node test/ticket.test.js && node test/validation.test.js && node test/verification.test.js && node test/behavioral-verification.test.js && node test/evidence.test.js && node test/candidate.test.js && node test/recovery.test.js && node test/hooks.test.js && node test/runtime-parse.test.js && node test/coverage-inventory.test.js && node test/coverage-map.test.js && node test/coverage-agreement.test.js && node test/coverage-adoption.test.js && node test/coverage-readiness.test.js && node test/coverage-impact.test.js && node test/coverage-checks.test.js && node test/coverage-evidence.test.js && node test/coverage-reports.test.js && node test/runtime-identity.test.js && node test/change-registry.test.js && node test/change-selection.test.js && node test/change-agreement.test.js && node test/change-authorization.test.js && node test/change-lifecycle.test.js && node test/change-command-gates.test.js && node test/change-evidence-context.test.js && node test/change-evaluations.test.js && node test/change-resume.test.js && node test/change-trial-record.test.js && node test/change-review-packet.test.js && node test/runtime-state.test.js && node test/change-transactions.test.js && node test/runtime-status.test.js && node test/runtime-output.test.js && node test/runtime-runner.test.js && node test/runtime-lifecycle.test.js && node test/runtime-migrate.test.js && node test/change-migration.test.js && node test/runtime-evidence.test.js && node test/workflow.test.js && node test/contracts.test.js && node test/change-contracts.test.js && node test/coverage-contracts.test.js && node test/distribution.test.js && node test/change-distribution.test.js && node test/coverage-distribution.test.js && node test/delivery-benchmark.test.js && node test/coverage-trial-record.test.js && node test/coverage-review-packet.test.js"
|
|
17
|
+
"test": "node test/smoke.test.js && node test/installer.test.js && node test/strict-onboarding.test.js && node test/ticket.test.js && node test/validation.test.js && node test/verification.test.js && node test/behavioral-verification.test.js && node test/evidence.test.js && node test/candidate.test.js && node test/recovery.test.js && node test/hooks.test.js && node test/runtime-parse.test.js && node test/coverage-inventory.test.js && node test/coverage-map.test.js && node test/coverage-scaffold.test.js && node test/coverage-agreement.test.js && node test/coverage-adoption.test.js && node test/coverage-readiness.test.js && node test/coverage-impact.test.js && node test/coverage-checks.test.js && node test/coverage-evidence.test.js && node test/coverage-reports.test.js && node test/runtime-identity.test.js && node test/change-registry.test.js && node test/change-selection.test.js && node test/change-agreement.test.js && node test/change-authorization.test.js && node test/change-lifecycle.test.js && node test/change-command-gates.test.js && node test/change-evidence-context.test.js && node test/change-evaluations.test.js && node test/change-resume.test.js && node test/resume-brief.test.js && node test/change-trial-record.test.js && node test/change-review-packet.test.js && node test/runtime-state.test.js && node test/change-transactions.test.js && node test/runtime-status.test.js && node test/runtime-output.test.js && node test/runtime-runner.test.js && node test/runtime-lifecycle.test.js && node test/runtime-migrate.test.js && node test/change-migration.test.js && node test/runtime-evidence.test.js && node test/workflow.test.js && node test/improvement-contracts.test.js && node test/contracts.test.js && node test/change-contracts.test.js && node test/coverage-contracts.test.js && node test/distribution.test.js && node test/change-distribution.test.js && node test/coverage-distribution.test.js && node test/effort-records.test.js && node test/execution-freeze.test.js && node test/delivery-benchmark.test.js && node test/delivery-benchmark-v7.test.js && node test/benchmark-orchestrator.test.js && node test/strict-pilot-records.test.js && node test/platform-trial-records.test.js && node test/improvement-trial-records.test.js && node test/coverage-trial-record.test.js && node test/coverage-review-packet.test.js && node test/improvement-review-packet.test.js && node test/readiness-contracts.test.js && node test/benchmark-effective-inputs.test.js && node test/release-preparation.test.js && node test/benchmark-run-claims.test.js && node test/benchmark-environment.test.js && node test/benchmark-browser.test.js && node test/benchmark-restart.test.js && node test/benchmark-usage-completeness.test.js && node test/benchmark-terminal-records.test.js && node test/study-readiness.test.js && node test/benchmark-allocation.test.js && node test/benchmark-prepared-bases.test.js && node test/benchmark-study-launch.test.js"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"claude-code",
|
|
@@ -40,7 +40,10 @@ previously authorized work. Read the `Runtime` line before the first ticket. `ch
|
|
|
40
40
|
`.prd/changes/`) → run `node scripts/pincer-runtime.cjs resume` and follow its `Next`
|
|
41
41
|
line: it names the selected change, its lifecycle state, the agreement and the
|
|
42
42
|
authorization verdict, the blockers in order and the exact next command, all from the
|
|
43
|
-
files on disk.
|
|
43
|
+
files on disk. `resume --brief` is the same report projected to counts and one next
|
|
44
|
+
action — the same verdict and the same `Next` line, with every blocker category kept
|
|
45
|
+
and its `Detail` line naming the command that prints the rows it grouped. Prefer it
|
|
46
|
+
when you are orienting, and read the full report when a blocker needs its detail. Select the change to work on (`node scripts/pincer-runtime.cjs change
|
|
44
47
|
select <id>`; selection is local metadata and touches no source), activate it
|
|
45
48
|
(`change activate <id>`; refused until the user's authorization is recorded with
|
|
46
49
|
`change authorize` and no consequential decision is open) and resume a paused change
|
|
@@ -39,7 +39,17 @@ discovered consequential choice is surfaced before implementation.
|
|
|
39
39
|
in the ticket Objective. Resolve missing coverage and conflicting criteria with
|
|
40
40
|
the user before implementation; do not start with an unmapped required scenario. On a change with change records, author the map once as
|
|
41
41
|
`.prd/coverage/<change id>.json` (coverage map schema 1, "Coverage map" in
|
|
42
|
-
`docs/runtime-contracts.md`)
|
|
42
|
+
`docs/runtime-contracts.md`). Start from
|
|
43
|
+
`node scripts/pincer-runtime.cjs coverage scaffold --change <change id>`: it prints
|
|
44
|
+
a read-only draft listing every live scenario exactly once with its requirement,
|
|
45
|
+
every ticket with its objective, its own `Implements:` claim and its Verification
|
|
46
|
+
text, and an `Unresolved` list of what is still unauthored. It writes nothing,
|
|
47
|
+
adopts nothing and decides nothing — it removes the transcription, not the
|
|
48
|
+
judgment, so a ticket's claim is material to check rather than a link, and no check
|
|
49
|
+
command, ticket role or scope disposition is ever invented. The draft is not a map:
|
|
50
|
+
it carries no `schema` key and `coverage adopt` refuses it. You author the real map
|
|
51
|
+
yourself, working through the draft's unresolved entries:
|
|
52
|
+
one `scenarios` row per `S-NN` naming its
|
|
43
53
|
implementing tickets and declared checks, every ticket of the change in
|
|
44
54
|
`tickets` as `implements` or `enables` (with a rationale), each check declared
|
|
45
55
|
once in `checks` with its kind, `required` flag and, for a command, the exact
|
|
@@ -12,7 +12,19 @@ start of a session. Read-only: change nothing.
|
|
|
12
12
|
|
|
13
13
|
## Steps
|
|
14
14
|
|
|
15
|
-
1.
|
|
15
|
+
1. On a project with change records, start with
|
|
16
|
+
`node scripts/pincer-runtime.cjs resume --brief`. It is one read that answers the
|
|
17
|
+
question this command exists for: the selected change and its lifecycle, the
|
|
18
|
+
authorization verdict, the coverage label, ticket and attempt counts, every blocking
|
|
19
|
+
reason category with its count, and the exact next command — the full report's own,
|
|
20
|
+
copied, never recomputed. Reading the full `resume`, `status` and `coverage` reports
|
|
21
|
+
in sequence to find one next action costs several kilobytes to answer in a line, and
|
|
22
|
+
on a large change the full report grows with the work while the answer does not.
|
|
23
|
+
Read further only when the brief gives you a reason to: it ends with the `Detail`
|
|
24
|
+
line naming the command that prints every row it grouped.
|
|
25
|
+
2. When you need the detail — a blocker you must act on, a ticket list, an attempt
|
|
26
|
+
history — run `scripts/pincer-status.sh`. It reads the artifacts on disk (`.prd/`,
|
|
27
|
+
`tickets/`,
|
|
16
28
|
`NOTES.md`) and prints the PRD state and profile, every ticket with its state and
|
|
17
29
|
clock-based elapsed time, what is blocked, wall-clock build time while a ticket is in
|
|
18
30
|
progress or against an explicit user budget, the `Runtime` line (legacy receipts or
|
|
@@ -20,8 +32,8 @@ start of a session. Read-only: change nothing.
|
|
|
20
32
|
candidate, any warnings (each readiness problem once), and the next command to run.
|
|
21
33
|
`scripts/pincer-status.sh --json` prints one status object with reason codes and the
|
|
22
34
|
next action for tooling; `node scripts/pincer-runtime.cjs ready [T-NN]` is the
|
|
23
|
-
read-only gate. On a project with change records (`Runtime changes …`)
|
|
24
|
-
`node scripts/pincer-runtime.cjs resume`
|
|
35
|
+
read-only gate. On a project with change records (`Runtime changes …`)
|
|
36
|
+
`node scripts/pincer-runtime.cjs resume` is the full report: it reports the selected change, its
|
|
25
37
|
lifecycle, agreement and authorization, decisions, references, tickets, attempts,
|
|
26
38
|
candidate, the authored handoff note and the next command, and never writes;
|
|
27
39
|
`change list` shows every retained change, `status --change <id>` and
|
|
@@ -30,12 +42,14 @@ start of a session. Read-only: change nothing.
|
|
|
30
42
|
`strict` or `unverified`; on a strict change `node scripts/pincer-runtime.cjs coverage`
|
|
31
43
|
(and `impact` after an edit) name the exact scenario, ticket, check or decision
|
|
32
44
|
that is next — quote them rather than inferring coverage from the ticket list.
|
|
33
|
-
|
|
45
|
+
On a legacy project there is no change record to summarize, so `pincer-status.sh`
|
|
46
|
+
is the first read.
|
|
47
|
+
3. Report in three lines: where the workflow is, what is in progress or blocked, and the
|
|
34
48
|
next command. Quote the `Next` line as-is. When the `Runtime` line says `legacy`,
|
|
35
49
|
add the register or migrate command it names as the step that precedes the next
|
|
36
50
|
ticket (fresh project → `register`, legacy receipts → `migrate --preview`, a v0.5.0
|
|
37
51
|
binding → `migrate --preview`, no selection → `change select <id>`).
|
|
38
|
-
|
|
52
|
+
4. If a ticket is `in_progress`, read it and `git status`, then offer to resume it with
|
|
39
53
|
`$pincer-code T-{NN}`. If the script printed a warning, surface it — a done ticket
|
|
40
54
|
without a receipt was marked by hand and needs `scripts/pincer-ticket.sh verify T-{NN}`.
|
|
41
55
|
Never restore a ticket file from git to clear a warning; a failed attempt is a record.
|
|
@@ -38,7 +38,10 @@ previously authorized work. Read the `Runtime` line before the first ticket. `ch
|
|
|
38
38
|
`.prd/changes/`) → run `node scripts/pincer-runtime.cjs resume` and follow its `Next`
|
|
39
39
|
line: it names the selected change, its lifecycle state, the agreement and the
|
|
40
40
|
authorization verdict, the blockers in order and the exact next command, all from the
|
|
41
|
-
files on disk.
|
|
41
|
+
files on disk. `resume --brief` is the same report projected to counts and one next
|
|
42
|
+
action — the same verdict and the same `Next` line, with every blocker category kept
|
|
43
|
+
and its `Detail` line naming the command that prints the rows it grouped. Prefer it
|
|
44
|
+
when you are orienting, and read the full report when a blocker needs its detail. Select the change to work on (`node scripts/pincer-runtime.cjs change
|
|
42
45
|
select <id>`; selection is local metadata and touches no source), activate it
|
|
43
46
|
(`change activate <id>`; refused until the user's authorization is recorded with
|
|
44
47
|
`change authorize` and no consequential decision is open) and resume a paused change
|
|
@@ -37,7 +37,17 @@ discovered consequential choice is surfaced before implementation.
|
|
|
37
37
|
in the ticket Objective. Resolve missing coverage and conflicting criteria with
|
|
38
38
|
the user before implementation; do not start with an unmapped required scenario. On a change with change records, author the map once as
|
|
39
39
|
`.prd/coverage/<change id>.json` (coverage map schema 1, "Coverage map" in
|
|
40
|
-
`docs/runtime-contracts.md`)
|
|
40
|
+
`docs/runtime-contracts.md`). Start from
|
|
41
|
+
`node scripts/pincer-runtime.cjs coverage scaffold --change <change id>`: it prints
|
|
42
|
+
a read-only draft listing every live scenario exactly once with its requirement,
|
|
43
|
+
every ticket with its objective, its own `Implements:` claim and its Verification
|
|
44
|
+
text, and an `Unresolved` list of what is still unauthored. It writes nothing,
|
|
45
|
+
adopts nothing and decides nothing — it removes the transcription, not the
|
|
46
|
+
judgment, so a ticket's claim is material to check rather than a link, and no check
|
|
47
|
+
command, ticket role or scope disposition is ever invented. The draft is not a map:
|
|
48
|
+
it carries no `schema` key and `coverage adopt` refuses it. You author the real map
|
|
49
|
+
yourself, working through the draft's unresolved entries:
|
|
50
|
+
one `scenarios` row per `S-NN` naming its
|
|
41
51
|
implementing tickets and declared checks, every ticket of the change in
|
|
42
52
|
`tickets` as `implements` or `enables` (with a rationale), each check declared
|
|
43
53
|
once in `checks` with its kind, `required` flag and, for a command, the exact
|
|
@@ -10,7 +10,19 @@ start of a session. Read-only: change nothing.
|
|
|
10
10
|
|
|
11
11
|
## Steps
|
|
12
12
|
|
|
13
|
-
1.
|
|
13
|
+
1. On a project with change records, start with
|
|
14
|
+
`node scripts/pincer-runtime.cjs resume --brief`. It is one read that answers the
|
|
15
|
+
question this command exists for: the selected change and its lifecycle, the
|
|
16
|
+
authorization verdict, the coverage label, ticket and attempt counts, every blocking
|
|
17
|
+
reason category with its count, and the exact next command — the full report's own,
|
|
18
|
+
copied, never recomputed. Reading the full `resume`, `status` and `coverage` reports
|
|
19
|
+
in sequence to find one next action costs several kilobytes to answer in a line, and
|
|
20
|
+
on a large change the full report grows with the work while the answer does not.
|
|
21
|
+
Read further only when the brief gives you a reason to: it ends with the `Detail`
|
|
22
|
+
line naming the command that prints every row it grouped.
|
|
23
|
+
2. When you need the detail — a blocker you must act on, a ticket list, an attempt
|
|
24
|
+
history — run `scripts/pincer-status.sh`. It reads the artifacts on disk (`.prd/`,
|
|
25
|
+
`tickets/`,
|
|
14
26
|
`NOTES.md`) and prints the PRD state and profile, every ticket with its state and
|
|
15
27
|
clock-based elapsed time, what is blocked, wall-clock build time while a ticket is in
|
|
16
28
|
progress or against an explicit user budget, the `Runtime` line (legacy receipts or
|
|
@@ -18,8 +30,8 @@ start of a session. Read-only: change nothing.
|
|
|
18
30
|
candidate, any warnings (each readiness problem once), and the next command to run.
|
|
19
31
|
`scripts/pincer-status.sh --json` prints one status object with reason codes and the
|
|
20
32
|
next action for tooling; `node scripts/pincer-runtime.cjs ready [T-NN]` is the
|
|
21
|
-
read-only gate. On a project with change records (`Runtime changes …`)
|
|
22
|
-
`node scripts/pincer-runtime.cjs resume`
|
|
33
|
+
read-only gate. On a project with change records (`Runtime changes …`)
|
|
34
|
+
`node scripts/pincer-runtime.cjs resume` is the full report: it reports the selected change, its
|
|
23
35
|
lifecycle, agreement and authorization, decisions, references, tickets, attempts,
|
|
24
36
|
candidate, the authored handoff note and the next command, and never writes;
|
|
25
37
|
`change list` shows every retained change, `status --change <id>` and
|
|
@@ -28,12 +40,14 @@ start of a session. Read-only: change nothing.
|
|
|
28
40
|
`strict` or `unverified`; on a strict change `node scripts/pincer-runtime.cjs coverage`
|
|
29
41
|
(and `impact` after an edit) name the exact scenario, ticket, check or decision
|
|
30
42
|
that is next — quote them rather than inferring coverage from the ticket list.
|
|
31
|
-
|
|
43
|
+
On a legacy project there is no change record to summarize, so `pincer-status.sh`
|
|
44
|
+
is the first read.
|
|
45
|
+
3. Report in three lines: where the workflow is, what is in progress or blocked, and the
|
|
32
46
|
next command. Quote the `Next` line as-is. When the `Runtime` line says `legacy`,
|
|
33
47
|
add the register or migrate command it names as the step that precedes the next
|
|
34
48
|
ticket (fresh project → `register`, legacy receipts → `migrate --preview`, a v0.5.0
|
|
35
49
|
binding → `migrate --preview`, no selection → `change select <id>`).
|
|
36
|
-
|
|
50
|
+
4. If a ticket is `in_progress`, read it and `git status`, then offer to resume it with
|
|
37
51
|
`/pincer-code T-{NN}`. If the script printed a warning, surface it — a done ticket
|
|
38
52
|
without a receipt was marked by hand and needs `scripts/pincer-ticket.sh verify T-{NN}`.
|
|
39
53
|
Never restore a ticket file from git to clear a warning; a failed attempt is a record.
|
|
@@ -40,7 +40,10 @@ previously authorized work. Read the `Runtime` line before the first ticket. `ch
|
|
|
40
40
|
`.prd/changes/`) → run `node scripts/pincer-runtime.cjs resume` and follow its `Next`
|
|
41
41
|
line: it names the selected change, its lifecycle state, the agreement and the
|
|
42
42
|
authorization verdict, the blockers in order and the exact next command, all from the
|
|
43
|
-
files on disk.
|
|
43
|
+
files on disk. `resume --brief` is the same report projected to counts and one next
|
|
44
|
+
action — the same verdict and the same `Next` line, with every blocker category kept
|
|
45
|
+
and its `Detail` line naming the command that prints the rows it grouped. Prefer it
|
|
46
|
+
when you are orienting, and read the full report when a blocker needs its detail. Select the change to work on (`node scripts/pincer-runtime.cjs change
|
|
44
47
|
select <id>`; selection is local metadata and touches no source), activate it
|
|
45
48
|
(`change activate <id>`; refused until the user's authorization is recorded with
|
|
46
49
|
`change authorize` and no consequential decision is open) and resume a paused change
|
|
@@ -39,7 +39,17 @@ discovered consequential choice is surfaced before implementation.
|
|
|
39
39
|
in the ticket Objective. Resolve missing coverage and conflicting criteria with
|
|
40
40
|
the user before implementation; do not start with an unmapped required scenario. On a change with change records, author the map once as
|
|
41
41
|
`.prd/coverage/<change id>.json` (coverage map schema 1, "Coverage map" in
|
|
42
|
-
`docs/runtime-contracts.md`)
|
|
42
|
+
`docs/runtime-contracts.md`). Start from
|
|
43
|
+
`node scripts/pincer-runtime.cjs coverage scaffold --change <change id>`: it prints
|
|
44
|
+
a read-only draft listing every live scenario exactly once with its requirement,
|
|
45
|
+
every ticket with its objective, its own `Implements:` claim and its Verification
|
|
46
|
+
text, and an `Unresolved` list of what is still unauthored. It writes nothing,
|
|
47
|
+
adopts nothing and decides nothing — it removes the transcription, not the
|
|
48
|
+
judgment, so a ticket's claim is material to check rather than a link, and no check
|
|
49
|
+
command, ticket role or scope disposition is ever invented. The draft is not a map:
|
|
50
|
+
it carries no `schema` key and `coverage adopt` refuses it. You author the real map
|
|
51
|
+
yourself, working through the draft's unresolved entries:
|
|
52
|
+
one `scenarios` row per `S-NN` naming its
|
|
43
53
|
implementing tickets and declared checks, every ticket of the change in
|
|
44
54
|
`tickets` as `implements` or `enables` (with a rationale), each check declared
|
|
45
55
|
once in `checks` with its kind, `required` flag and, for a command, the exact
|
|
@@ -12,7 +12,19 @@ start of a session. Read-only: change nothing.
|
|
|
12
12
|
|
|
13
13
|
## Steps
|
|
14
14
|
|
|
15
|
-
1.
|
|
15
|
+
1. On a project with change records, start with
|
|
16
|
+
`node scripts/pincer-runtime.cjs resume --brief`. It is one read that answers the
|
|
17
|
+
question this command exists for: the selected change and its lifecycle, the
|
|
18
|
+
authorization verdict, the coverage label, ticket and attempt counts, every blocking
|
|
19
|
+
reason category with its count, and the exact next command — the full report's own,
|
|
20
|
+
copied, never recomputed. Reading the full `resume`, `status` and `coverage` reports
|
|
21
|
+
in sequence to find one next action costs several kilobytes to answer in a line, and
|
|
22
|
+
on a large change the full report grows with the work while the answer does not.
|
|
23
|
+
Read further only when the brief gives you a reason to: it ends with the `Detail`
|
|
24
|
+
line naming the command that prints every row it grouped.
|
|
25
|
+
2. When you need the detail — a blocker you must act on, a ticket list, an attempt
|
|
26
|
+
history — run `scripts/pincer-status.sh`. It reads the artifacts on disk (`.prd/`,
|
|
27
|
+
`tickets/`,
|
|
16
28
|
`NOTES.md`) and prints the PRD state and profile, every ticket with its state and
|
|
17
29
|
clock-based elapsed time, what is blocked, wall-clock build time while a ticket is in
|
|
18
30
|
progress or against an explicit user budget, the `Runtime` line (legacy receipts or
|
|
@@ -20,8 +32,8 @@ start of a session. Read-only: change nothing.
|
|
|
20
32
|
candidate, any warnings (each readiness problem once), and the next command to run.
|
|
21
33
|
`scripts/pincer-status.sh --json` prints one status object with reason codes and the
|
|
22
34
|
next action for tooling; `node scripts/pincer-runtime.cjs ready [T-NN]` is the
|
|
23
|
-
read-only gate. On a project with change records (`Runtime changes …`)
|
|
24
|
-
`node scripts/pincer-runtime.cjs resume`
|
|
35
|
+
read-only gate. On a project with change records (`Runtime changes …`)
|
|
36
|
+
`node scripts/pincer-runtime.cjs resume` is the full report: it reports the selected change, its
|
|
25
37
|
lifecycle, agreement and authorization, decisions, references, tickets, attempts,
|
|
26
38
|
candidate, the authored handoff note and the next command, and never writes;
|
|
27
39
|
`change list` shows every retained change, `status --change <id>` and
|
|
@@ -30,12 +42,14 @@ start of a session. Read-only: change nothing.
|
|
|
30
42
|
`strict` or `unverified`; on a strict change `node scripts/pincer-runtime.cjs coverage`
|
|
31
43
|
(and `impact` after an edit) name the exact scenario, ticket, check or decision
|
|
32
44
|
that is next — quote them rather than inferring coverage from the ticket list.
|
|
33
|
-
|
|
45
|
+
On a legacy project there is no change record to summarize, so `pincer-status.sh`
|
|
46
|
+
is the first read.
|
|
47
|
+
3. Report in three lines: where the workflow is, what is in progress or blocked, and the
|
|
34
48
|
next command. Quote the `Next` line as-is. When the `Runtime` line says `legacy`,
|
|
35
49
|
add the register or migrate command it names as the step that precedes the next
|
|
36
50
|
ticket (fresh project → `register`, legacy receipts → `migrate --preview`, a v0.5.0
|
|
37
51
|
binding → `migrate --preview`, no selection → `change select <id>`).
|
|
38
|
-
|
|
52
|
+
4. If a ticket is `in_progress`, read it and `git status`, then offer to resume it with
|
|
39
53
|
`/pincer-code T-{NN}`. If the script printed a warning, surface it — a done ticket
|
|
40
54
|
without a receipt was marked by hand and needs `scripts/pincer-ticket.sh verify T-{NN}`.
|
|
41
55
|
Never restore a ticket file from git to clear a warning; a failed attempt is a record.
|
|
@@ -893,8 +893,8 @@ it selects nothing, completes nothing, runs nothing and writes nothing.
|
|
|
893
893
|
|
|
894
894
|
## Resume report
|
|
895
895
|
|
|
896
|
-
`resume [--change <id>] [--json]` is read-only inspection of the selected
|
|
897
|
-
change for a fresh session; it is distinct from `change resume`, the lifecycle
|
|
896
|
+
`resume [--change <id>] [--brief] [--json]` is read-only inspection of the selected
|
|
897
|
+
(or named) change for a fresh session; it is distinct from `change resume`, the lifecycle
|
|
898
898
|
operation. It launches no check, records no approval, changes no selection and writes
|
|
899
899
|
no file; repeated runs are byte-identical apart from `generated`. Resume JSON schema 1:
|
|
900
900
|
|
|
@@ -938,6 +938,40 @@ command to run):
|
|
|
938
938
|
7. `completed` without a current evaluation → `/pincer-evaluate`;
|
|
939
939
|
8. evaluated and current → `/pincer-release` (read-only audit).
|
|
940
940
|
|
|
941
|
+
### Brief resume
|
|
942
|
+
|
|
943
|
+
`resume --brief [--change <id>] [--json]` is a **projection of the report `resume`
|
|
944
|
+
already computes**, for a fresh session that needs the next action without paying for
|
|
945
|
+
every row to find it. It is not a second policy engine: `next` is the full report's
|
|
946
|
+
own object, copied, and no verdict, readiness value or precedence rule is recomputed.
|
|
947
|
+
It composes with `--change` and `--json`, keeps the full report's exit code, and
|
|
948
|
+
writes nothing.
|
|
949
|
+
|
|
950
|
+
Brief JSON envelope — `brief: 1` with `of` naming the schema it projects, so nothing
|
|
951
|
+
reading resume JSON ever sees a new shape:
|
|
952
|
+
|
|
953
|
+
```
|
|
954
|
+
{ brief: 1, kind: "resume-brief", of: 2, generated, root, mode,
|
|
955
|
+
selection: { change | null, problem | null },
|
|
956
|
+
change: { id, prd, base, lifecycle } | null,
|
|
957
|
+
agreement: { current, verdict, authorized: { id, disposition } | null } | null,
|
|
958
|
+
coverage: { label, strict, structure, implementation } | null,
|
|
959
|
+
tickets: { total, by_status: { open, in_progress, done }, not_ready },
|
|
960
|
+
attempts: { total, running, current_failed },
|
|
961
|
+
candidate: { notes, candidate, evidence } | null,
|
|
962
|
+
blockers: { total, categories: [ { code, count } ] },
|
|
963
|
+
next: <the full report's next, verbatim>,
|
|
964
|
+
detail: { command, prd, tickets: [ paths ], omitted } }
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
Grouping may collapse **repetition**; it may never collapse a **category**. Every
|
|
968
|
+
distinct blocker code of the full report appears in `blockers.categories` with its
|
|
969
|
+
exact count, and `blockers.total`, `tickets.total` and `attempts.total` equal the full
|
|
970
|
+
report's own lengths. `detail.omitted` states how many rows the brief did not print
|
|
971
|
+
and `detail.command` is the exact command that prints them, so nothing is hidden —
|
|
972
|
+
only deferred. The default `resume` human output and resume JSON schema 2 are
|
|
973
|
+
unchanged.
|
|
974
|
+
|
|
941
975
|
## Migration and rollback
|
|
942
976
|
|
|
943
977
|
`migrate --preview --prd .prd/prd-vN.md` prints the plan and writes nothing. It exits 0
|
|
@@ -1556,6 +1590,7 @@ are unchanged.
|
|
|
1556
1590
|
| --- | --- | --- | --- |
|
|
1557
1591
|
| `coverage` | `[--change <id>] [--json]` | nothing | the phase-specific coverage report of the selected (or named) change; exit 0 when the report was computed (complete or not), 4 when the inputs cannot be read |
|
|
1558
1592
|
| `impact` | `[--change <id>] [--from G-NN \| A-NN] [--json]` | nothing | structural differences between the current authored inputs and a retained agreement; exit 0 when computed (`unchanged`, `changed` or `unavailable`), 4 on invalid input |
|
|
1593
|
+
| `coverage scaffold` | `--change <id> [--json]` | nothing | the read-only coverage draft ("Coverage draft"); exit 0 when a draft was produced, 4 when the inputs cannot be read |
|
|
1559
1594
|
| `coverage adopt` | `--preview \| --apply --change <id> [--agreement <digest>]` | apply: the backup, the schema 3 record, the adoption snapshot | preview writes nothing; exit 0 / 1 (conflict) / 4 (invalid state) |
|
|
1560
1595
|
| `check` | `C-NN --candidate <sha>` (strict) | an attempt | the declared command; `--timeout` and `-- <command>` are `CHECK_UNDECLARED` in a strict change |
|
|
1561
1596
|
|
|
@@ -1563,6 +1598,57 @@ are unchanged.
|
|
|
1563
1598
|
write no file; repeated runs are byte-identical apart from `generated`. Their human
|
|
1564
1599
|
output and their JSON name the same IDs, codes and next action.
|
|
1565
1600
|
|
|
1601
|
+
### Coverage draft
|
|
1602
|
+
|
|
1603
|
+
`coverage scaffold --change <id> [--json]` projects the validated inventory, the
|
|
1604
|
+
change's tickets and any authored map into one reviewable **draft**. It exists to
|
|
1605
|
+
remove transcription, not judgment: authoring `.prd/coverage/<id>.json` means copying
|
|
1606
|
+
every live scenario, every ticket role and every check declaration out of documents
|
|
1607
|
+
the runtime has already parsed, and that copying is all this removes.
|
|
1608
|
+
|
|
1609
|
+
The draft **decides nothing**. It never invents a link, a check command, a ticket role
|
|
1610
|
+
or a scope disposition; an entry it cannot resolve from authored content stays
|
|
1611
|
+
`unresolved`. A ticket's own `Implements:`/`Scenarios:` claim and its Verification
|
|
1612
|
+
text are reported under `candidates` with the ticket's file, as material to read —
|
|
1613
|
+
never promoted into `scenarios` or `checks`. IDs on those lines that the inventory
|
|
1614
|
+
does not define are not reported at all, because a ticket naming other tickets is not
|
|
1615
|
+
a scenario claim.
|
|
1616
|
+
|
|
1617
|
+
Draft envelope — `draft: 1`, and deliberately **no `schema` key**, so `readMap`
|
|
1618
|
+
refuses it with `COVERAGE_INVALID: unsupported coverage map schema undefined`:
|
|
1619
|
+
|
|
1620
|
+
```
|
|
1621
|
+
{ draft: 1, kind: "coverage-draft", change, prd,
|
|
1622
|
+
inventory: { digest, requirements, scenarios },
|
|
1623
|
+
authored: { map: <path> | null, digest | null },
|
|
1624
|
+
scenarios: { "S-NN": { requirement, state, tickets: [], checks: [] } },
|
|
1625
|
+
scope: { "S-NN": { disposition, decision, prior, note, state } },
|
|
1626
|
+
tickets: { "T-NN": { role, rationale, state } },
|
|
1627
|
+
checks: { "C-NN": { kind, required, command, timeout, cwd, obligation, note, state } },
|
|
1628
|
+
candidates: { tickets: { "T-NN": { file, objective, implements: [], scenarios: [], verification } } },
|
|
1629
|
+
unresolved: [ { code, id, detail } ],
|
|
1630
|
+
next: { action, command } }
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
`state` is `authored` for content read from the existing map and `unresolved`
|
|
1634
|
+
otherwise. Every live inventory scenario appears exactly once across `scenarios` and
|
|
1635
|
+
`scope`. Authored rows the inventory no longer defines are **preserved and flagged**
|
|
1636
|
+
(`SCENARIO_STALE`), never dropped: removing an obligation is a decision with its own
|
|
1637
|
+
disposition and authorization. Unresolved codes are `SCENARIO_UNLINKED`,
|
|
1638
|
+
`CHECK_UNDECLARED`, `TICKET_UNCLASSIFIED`, `SCENARIO_STALE` and `TICKET_FOREIGN`.
|
|
1639
|
+
|
|
1640
|
+
The draft body carries no timestamp, so two calls on identical authored inputs are
|
|
1641
|
+
byte-identical. Scaffolding writes no file, launches no check, changes no selection,
|
|
1642
|
+
adopts nothing and records no approval; a malformed, unsupported, symlinked or
|
|
1643
|
+
out-of-root map is refused with exit 4 before any draft is printed, because silently
|
|
1644
|
+
dropping authored content is the one failure this command must not have.
|
|
1645
|
+
|
|
1646
|
+
A draft is **not a coverage map and confers no readiness**. The route is unchanged:
|
|
1647
|
+
author `.prd/coverage/<id>.json` by hand, review the links, `coverage` validates it,
|
|
1648
|
+
`coverage adopt --preview|--apply` adopts it, and `change authorize` records the
|
|
1649
|
+
user's instruction covering the new agreement. Editing the inputs afterwards still
|
|
1650
|
+
yields `AGREEMENT_CHANGED`.
|
|
1651
|
+
|
|
1566
1652
|
Coverage JSON schema 1:
|
|
1567
1653
|
|
|
1568
1654
|
```
|
|
@@ -70,7 +70,12 @@ function migratedTicketReadiness({ text, fields, timeout, attempt, legacyReceipt
|
|
|
70
70
|
default: return { ready: false, reasons: [reason('ATTEMPT_ERROR', `attempt ${id} has unknown outcome ${JSON.stringify(attempt.outcome)}`, 'verify')] };
|
|
71
71
|
}
|
|
72
72
|
if (current.prdRevision && attempt.context && attempt.context.prd_revision !== current.prdRevision) {
|
|
73
|
-
|
|
73
|
+
// The remedy is mode-specific. `register --rebind` is the v0.4.1/v0.5.0 repair and
|
|
74
|
+
// changes mode refuses it outright (AGREEMENT_CHANGED: "--rebind is not supported
|
|
75
|
+
// for change records"), so naming it there sends the reader to a command the
|
|
76
|
+
// runtime will not run — the defect class T-80 closed for the coverage report.
|
|
77
|
+
reasons.push(reason('REVISION_CHANGED', 'the PRD revision changed since the passing attempt',
|
|
78
|
+
mode === 'changes' ? 'change revise, record its authorization, then verify' : 'register --rebind, then verify'));
|
|
74
79
|
}
|
|
75
80
|
if (attempt.check && attempt.check.digest !== parse.checkDigest(text, timeout)) {
|
|
76
81
|
reasons.push(reason('CHECK_CHANGED', 'the Verification block or timeout changed since the passing attempt', 'verify'));
|
|
@@ -202,4 +202,72 @@ function render(r) {
|
|
|
202
202
|
return `${lines.join('\n')}\n`;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
// --- The brief projection (docs/runtime-contracts.md, "Brief resume") ----------------
|
|
206
|
+
// A fresh session reads the full report to answer one question — what do I do next —
|
|
207
|
+
// and pays for every ticket row, attempt row and repeated blocker detail to get it.
|
|
208
|
+
// The brief keeps the answer and the counts and drops the repetition.
|
|
209
|
+
//
|
|
210
|
+
// It is a projection, not a second report: `next` is the full report's own object,
|
|
211
|
+
// copied, and every distinct blocker code survives with its exact count. Grouping may
|
|
212
|
+
// collapse repetition; it may never collapse a category, because a category is the
|
|
213
|
+
// reason a gate will refuse. Nothing here recomputes a verdict, caches readiness or
|
|
214
|
+
// decides an action, and `detail` names the command that prints every omitted row.
|
|
215
|
+
const BRIEF = 1;
|
|
216
|
+
const BRIEF_KIND = 'resume-brief';
|
|
217
|
+
|
|
218
|
+
function brief(r) {
|
|
219
|
+
const counts = {};
|
|
220
|
+
for (const b of r.blockers) counts[b.code] = (counts[b.code] || 0) + 1;
|
|
221
|
+
const byStatus = { open: 0, in_progress: 0, done: 0 };
|
|
222
|
+
let notReady = 0;
|
|
223
|
+
for (const t of r.tickets) {
|
|
224
|
+
if (t.status in byStatus) byStatus[t.status]++;
|
|
225
|
+
if (!t.readiness.ready) notReady++;
|
|
226
|
+
}
|
|
227
|
+
const running = r.attempts.filter(a => a.outcome === 'running').length;
|
|
228
|
+
const currentFailed = r.attempts.filter(a => a.current && a.outcome !== 'passed').length;
|
|
229
|
+
const cov = r.coverage;
|
|
230
|
+
return {
|
|
231
|
+
brief: BRIEF, kind: BRIEF_KIND, of: SCHEMA,
|
|
232
|
+
generated: r.generated, root: r.root, mode: r.mode,
|
|
233
|
+
selection: r.selection,
|
|
234
|
+
change: r.change ? { id: r.change.id, prd: r.change.prd, base: r.change.base, lifecycle: r.change.lifecycle.state } : null,
|
|
235
|
+
agreement: r.agreement ? { current: r.agreement.current, verdict: r.agreement.verdict, authorized: r.agreement.authorized ? { id: r.agreement.authorized.id, disposition: r.agreement.authorized.disposition } : null } : null,
|
|
236
|
+
coverage: cov ? { label: cov.label, strict: cov.strict, structure: cov.strict && cov.structure ? cov.structure.complete : null, implementation: cov.strict && cov.implementation ? cov.implementation.scenarios : null } : null,
|
|
237
|
+
tickets: { total: r.tickets.length, by_status: byStatus, not_ready: notReady },
|
|
238
|
+
attempts: { total: r.attempts.length, running, current_failed: currentFailed },
|
|
239
|
+
candidate: r.candidate ? { notes: r.candidate.notes, candidate: r.candidate.candidate, evidence: r.candidate.evidence ? r.candidate.evidence.verdict : null } : null,
|
|
240
|
+
blockers: { total: r.blockers.length, categories: Object.keys(counts).map(code => ({ code, count: counts[code] })) },
|
|
241
|
+
next: r.next,
|
|
242
|
+
detail: {
|
|
243
|
+
command: `${RUNTIME} resume${r.change ? ` --change ${r.change.id}` : ''}`,
|
|
244
|
+
prd: r.references ? r.references.prd.path : null,
|
|
245
|
+
tickets: r.references ? r.references.tickets.map(t => t.file) : [],
|
|
246
|
+
omitted: r.tickets.length + r.attempts.length + Math.max(0, r.blockers.length - Object.keys(counts).length),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function renderBrief(b) {
|
|
252
|
+
const lines = [];
|
|
253
|
+
const short = s => (typeof s === 'string' ? s.slice(0, 12) : '—');
|
|
254
|
+
lines.push(`PINCER resume --brief · ${b.generated} · ${b.root}`);
|
|
255
|
+
if (!b.change) {
|
|
256
|
+
lines.push(`Selection ${b.selection && b.selection.change ? b.selection.change : 'none'}${b.selection && b.selection.problem ? ` · ${b.selection.problem.code}: ${b.selection.problem.detail}` : ''}`);
|
|
257
|
+
} else {
|
|
258
|
+
const c = b.change, a = b.agreement;
|
|
259
|
+
lines.push(`Change ${c.id} · ${c.prd} · base ${c.base.slice(0, 7)} · ${c.lifecycle}`);
|
|
260
|
+
lines.push(`Agreement ${a.current ? short(a.current) : 'unavailable'} · ${a.verdict}${a.authorized ? ` · authorized ${a.authorized.id} (${a.authorized.disposition})` : ' · not authorized'}`);
|
|
261
|
+
if (b.coverage) lines.push(`Coverage ${b.coverage.label}${b.coverage.strict ? ` · structure ${b.coverage.structure ? 'complete' : 'incomplete'} · implementation ${b.coverage.implementation.complete}/${b.coverage.implementation.total - b.coverage.implementation.dispositioned} scenarios` : ''}`);
|
|
262
|
+
const t = b.tickets;
|
|
263
|
+
lines.push(`Tickets ${t.total} · ${t.by_status.done} done · ${t.by_status.in_progress} in progress · ${t.by_status.open} open · ${t.not_ready} not ready`);
|
|
264
|
+
lines.push(`Attempts ${b.attempts.total}${b.attempts.running ? ` · ${b.attempts.running} running` : ''}${b.attempts.current_failed ? ` · ${b.attempts.current_failed} current not passed` : ''}`);
|
|
265
|
+
if (b.candidate) lines.push(`Candidate ${b.candidate.notes === 'current' ? `current (${String(b.candidate.candidate).slice(0, 7)})` : b.candidate.notes}${b.candidate.evidence ? ` · evidence ${b.candidate.evidence}` : ''}`);
|
|
266
|
+
}
|
|
267
|
+
lines.push(`Blockers ${b.blockers.total ? b.blockers.categories.map(x => `${x.code}${x.count > 1 ? ` ×${x.count}` : ''}`).join(' · ') : 'none'}`);
|
|
268
|
+
lines.push(`Next ${b.next.action}: ${b.next.command}`);
|
|
269
|
+
lines.push(`Detail ${b.detail.command}${b.detail.omitted ? ` (${b.detail.omitted} row(s) not shown)` : ''}${b.detail.prd ? ` · ${b.detail.prd}` : ''}`);
|
|
270
|
+
return `${lines.join('\n')}\n`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = { SCHEMA, BRIEF, BRIEF_KIND, build, decide, render, brief, renderBrief };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// PINCER runtime — the coverage draft (docs/runtime-contracts.md, "Coverage draft").
|
|
3
|
+
// `coverage scaffold --change <id>` projects the validated PRD inventory, the change's
|
|
4
|
+
// tickets and any authored map into one reviewable draft. It exists because authoring
|
|
5
|
+
// `.prd/coverage/<id>.json` means transcribing every live scenario, every ticket role
|
|
6
|
+
// and every check declaration out of documents the runtime has already parsed; that
|
|
7
|
+
// transcription is the measured cost this removes (docs/prd-v7-pilots.md).
|
|
8
|
+
//
|
|
9
|
+
// What it must never do is decide. The draft carries no semantic judgment: an
|
|
10
|
+
// unresolved scenario stays unresolved, a check command is never invented, a ticket
|
|
11
|
+
// role is never guessed, and a scope disposition is never chosen. Candidate tickets
|
|
12
|
+
// and their existing verification text are listed as *material to read*, with their
|
|
13
|
+
// file provenance, never promoted into a link or a declaration.
|
|
14
|
+
//
|
|
15
|
+
// The envelope is `draft: 1` with no `schema` key, so `coverage.validateMap` refuses
|
|
16
|
+
// it: a draft written to disk is not a coverage map and cannot become one by being
|
|
17
|
+
// saved. The real map is authored by hand, validated by `coverage`, adopted by
|
|
18
|
+
// `coverage adopt` and authorized by `change authorize`, exactly as before.
|
|
19
|
+
//
|
|
20
|
+
// Pure and read-only: nothing here writes a file, launches a check, changes a
|
|
21
|
+
// selection or records an approval. The draft body carries no timestamp, so two calls
|
|
22
|
+
// on identical authored inputs produce byte-identical output.
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
const parse = require('./parse.cjs');
|
|
26
|
+
const requirements = require('./requirements.cjs');
|
|
27
|
+
const coverage = require('./coverage.cjs');
|
|
28
|
+
|
|
29
|
+
const DRAFT = 1;
|
|
30
|
+
const KIND = 'coverage-draft';
|
|
31
|
+
const RUNTIME = 'node scripts/pincer-runtime.cjs';
|
|
32
|
+
// Unresolved codes, in report order. These describe the draft, not the runtime state:
|
|
33
|
+
// they say what a human still has to author, never what a gate will refuse.
|
|
34
|
+
const UNRESOLVED_ORDER = ['SCENARIO_UNLINKED', 'CHECK_UNDECLARED', 'TICKET_UNCLASSIFIED', 'SCENARIO_STALE', 'TICKET_FOREIGN'];
|
|
35
|
+
|
|
36
|
+
const AUTHORED = 'authored';
|
|
37
|
+
const UNRESOLVED = 'unresolved';
|
|
38
|
+
|
|
39
|
+
// The first line under a ticket heading, used as the objective a reviewer reads.
|
|
40
|
+
function firstLine(text, heading) {
|
|
41
|
+
const rows = parse.lines(text);
|
|
42
|
+
const i = rows.findIndex(l => l.trim() === heading);
|
|
43
|
+
if (i === -1) return null;
|
|
44
|
+
const next = rows.slice(i + 1).find(l => l.trim() !== '');
|
|
45
|
+
return next ? next.trim() : null;
|
|
46
|
+
}
|
|
47
|
+
// The IDs a ticket's own Context section claims ("- Implements: R-04." and
|
|
48
|
+
// "- Scenarios: S-10, S-11."). Classified against the live inventory rather than by
|
|
49
|
+
// prefix, because tickets name other tickets on the same lines — T-03 of the strict
|
|
50
|
+
// fixture says "Implements: none (enables T-01, T-02)", and reading that as two
|
|
51
|
+
// scenario claims would be the draft inventing a link out of prose. An ID the
|
|
52
|
+
// inventory does not define is not reported at all.
|
|
53
|
+
const CLAIM_LINE = /^[ \t]*[-+*][ \t]+(?:Implements|Scenarios|Requirements):/;
|
|
54
|
+
function claimsOf(text, inventory) {
|
|
55
|
+
const ids = new Set();
|
|
56
|
+
for (const row of parse.lines(text)) {
|
|
57
|
+
if (!CLAIM_LINE.test(row)) continue;
|
|
58
|
+
for (const id of row.match(new RegExp(requirements.ID, 'g')) || []) ids.add(id);
|
|
59
|
+
}
|
|
60
|
+
const claimed = [...ids];
|
|
61
|
+
return {
|
|
62
|
+
requirements: requirements.sortIds(claimed.filter(id => inventory.requirements[id])),
|
|
63
|
+
scenarios: requirements.sortIds(claimed.filter(id => inventory.scenarios[id])),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// The ticket's own Verification fence, verbatim, as provenance for a reviewer. It is
|
|
67
|
+
// quoted, never turned into a check declaration: whether it proves the scenario is a
|
|
68
|
+
// judgment the draft does not make.
|
|
69
|
+
function verificationOf(text) {
|
|
70
|
+
const commands = parse.verificationCommands(text);
|
|
71
|
+
return commands && commands.length ? commands.join('\n') : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// build(root, record) → { ok, code, problems, draft }
|
|
75
|
+
// `code` is set only when the inputs cannot be read; a missing map is the normal case
|
|
76
|
+
// this command exists for, not an error.
|
|
77
|
+
function build(root, record) {
|
|
78
|
+
const fail = (code, problems) => ({ ok: false, code, problems, draft: null });
|
|
79
|
+
const inv = requirements.readInventory(root, record.prd);
|
|
80
|
+
if (!inv.ok) return fail(inv.code, inv.problems);
|
|
81
|
+
const inventory = inv.inventory;
|
|
82
|
+
|
|
83
|
+
// An absent map is expected; an unreadable one is not — silently dropping authored
|
|
84
|
+
// content would be the one way this command could destroy work.
|
|
85
|
+
const rel = coverage.file(record.change);
|
|
86
|
+
const real = coverage.realFile(root, rel);
|
|
87
|
+
let authored = null;
|
|
88
|
+
if (!real) {
|
|
89
|
+
const m = coverage.readMap(root, record);
|
|
90
|
+
if (!m.ok) return fail(m.code, m.problems);
|
|
91
|
+
authored = m;
|
|
92
|
+
} else if (!real.endsWith(': missing')) {
|
|
93
|
+
return fail('COVERAGE_INVALID', [`${rel}: ${real.slice(rel.length + 2)}`]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const t = coverage.ticketsOf(root, record);
|
|
97
|
+
if (!t.ok) return fail(t.code, t.problems);
|
|
98
|
+
|
|
99
|
+
const map = authored ? authored.map : null;
|
|
100
|
+
const unresolved = [];
|
|
101
|
+
const note = (code, id, detail) => unresolved.push({ code, id, detail });
|
|
102
|
+
|
|
103
|
+
// --- scenarios: every live scenario exactly once, authored rows preserved ----------
|
|
104
|
+
const scenarios = {};
|
|
105
|
+
const scope = {};
|
|
106
|
+
for (const id of requirements.sortIds(Object.keys(inventory.scenarios))) {
|
|
107
|
+
const live = inventory.scenarios[id];
|
|
108
|
+
const authoredScope = map && map.scope ? map.scope[id] : null;
|
|
109
|
+
if (authoredScope) {
|
|
110
|
+
scope[id] = { ...authoredScope, state: AUTHORED };
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const row = map && map.scenarios ? map.scenarios[id] : null;
|
|
114
|
+
const tickets = row ? [...row.tickets] : [];
|
|
115
|
+
const checks = row ? [...row.checks] : [];
|
|
116
|
+
const resolved = Boolean(row) && tickets.length > 0 && checks.length > 0;
|
|
117
|
+
scenarios[id] = { requirement: live.requirement, state: resolved ? AUTHORED : UNRESOLVED, tickets, checks };
|
|
118
|
+
if (!row) note('SCENARIO_UNLINKED', id, `${id} (${live.requirement}) has no row in scenarios or scope`);
|
|
119
|
+
else if (!tickets.length) note('SCENARIO_UNLINKED', id, `${id} is linked to no ticket`);
|
|
120
|
+
else if (!checks.length) note('CHECK_UNDECLARED', id, `${id} is linked to no check`);
|
|
121
|
+
}
|
|
122
|
+
// A map row naming a scenario the inventory no longer defines is authored content:
|
|
123
|
+
// it is preserved and flagged, never deleted, because removing an obligation is a
|
|
124
|
+
// decision with its own disposition and authorization.
|
|
125
|
+
if (map) {
|
|
126
|
+
for (const id of requirements.sortIds(Object.keys(map.scenarios))) {
|
|
127
|
+
if (scenarios[id] || scope[id]) continue;
|
|
128
|
+
scenarios[id] = { requirement: null, state: UNRESOLVED, tickets: [...map.scenarios[id].tickets], checks: [...map.scenarios[id].checks] };
|
|
129
|
+
note('SCENARIO_STALE', id, `${id} has a row but is not a scenario of ${record.prd}`);
|
|
130
|
+
}
|
|
131
|
+
for (const id of requirements.sortIds(Object.keys(map.scope))) {
|
|
132
|
+
if (scope[id] || inventory.scenarios[id]) continue;
|
|
133
|
+
scope[id] = { ...map.scope[id], state: UNRESOLVED };
|
|
134
|
+
note('SCENARIO_STALE', id, `${id} has a scope row but is not a scenario of ${record.prd}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- tickets: authored roles preserved; unclassified tickets named, not classified --
|
|
139
|
+
const tickets = {};
|
|
140
|
+
for (const id of requirements.sortIds(Object.keys(t.tickets))) {
|
|
141
|
+
const row = map && map.tickets ? map.tickets[id] : null;
|
|
142
|
+
if (row) tickets[id] = { role: row.role, rationale: row.rationale, state: AUTHORED };
|
|
143
|
+
else {
|
|
144
|
+
tickets[id] = { role: null, rationale: null, state: UNRESOLVED };
|
|
145
|
+
note('TICKET_UNCLASSIFIED', id, `${id} has no role; classify it as implements or enables (an enabling ticket needs a rationale)`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (map) {
|
|
149
|
+
for (const id of requirements.sortIds(Object.keys(map.tickets))) {
|
|
150
|
+
if (tickets[id]) continue;
|
|
151
|
+
tickets[id] = { role: map.tickets[id].role, rationale: map.tickets[id].rationale, state: UNRESOLVED };
|
|
152
|
+
note(t.others[id] ? 'TICKET_FOREIGN' : 'SCENARIO_STALE', id,
|
|
153
|
+
t.others[id] ? `${id} is a ticket of ${t.others[id]}, not of ${record.prd}` : `${id} has a row but no ticket file is associated with ${record.prd}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- checks: authored declarations preserved verbatim; none invented ---------------
|
|
158
|
+
const checks = {};
|
|
159
|
+
if (map) for (const id of Object.keys(map.checks).sort()) checks[id] = { ...map.checks[id], state: AUTHORED };
|
|
160
|
+
// Every check a scenario links to must be declared; an undeclared one is named.
|
|
161
|
+
for (const [sid, row] of Object.entries(scenarios)) {
|
|
162
|
+
for (const c of row.checks) {
|
|
163
|
+
if (checks[c]) continue;
|
|
164
|
+
note('CHECK_UNDECLARED', c, `${c} is linked from ${sid} but has no declaration`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- candidates: material to read, with provenance. Never a link. -----------------
|
|
169
|
+
const candidates = { tickets: {} };
|
|
170
|
+
for (const id of requirements.sortIds(Object.keys(t.tickets))) {
|
|
171
|
+
const entry = t.tickets[id];
|
|
172
|
+
const claims = claimsOf(entry.text, inventory);
|
|
173
|
+
candidates.tickets[id] = {
|
|
174
|
+
file: entry.file,
|
|
175
|
+
objective: firstLine(entry.text, '## Objective'),
|
|
176
|
+
implements: claims.requirements,
|
|
177
|
+
scenarios: claims.scenarios,
|
|
178
|
+
verification: verificationOf(entry.text),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
unresolved.sort((a, b) => (UNRESOLVED_ORDER.indexOf(a.code) - UNRESOLVED_ORDER.indexOf(b.code)) || requirements.compareIds(a.id, b.id));
|
|
183
|
+
const draft = {
|
|
184
|
+
draft: DRAFT,
|
|
185
|
+
kind: KIND,
|
|
186
|
+
change: record.change,
|
|
187
|
+
prd: record.prd,
|
|
188
|
+
inventory: { digest: inventory.digest, requirements: Object.keys(inventory.requirements).length, scenarios: Object.keys(inventory.scenarios).length },
|
|
189
|
+
authored: { map: authored ? authored.file : null, digest: authored ? authored.digest : null },
|
|
190
|
+
scenarios, scope, tickets, checks, candidates, unresolved,
|
|
191
|
+
next: nextAction(record, unresolved, authored),
|
|
192
|
+
};
|
|
193
|
+
return { ok: true, code: null, problems: [], draft };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// What the reader does next. A complete draft does not adopt anything: it says the map
|
|
197
|
+
// is ready to be reviewed and validated, which is a different claim from "correct".
|
|
198
|
+
function nextAction(record, unresolved, authored) {
|
|
199
|
+
if (unresolved.length) {
|
|
200
|
+
const first = unresolved[0];
|
|
201
|
+
return { action: `author the unresolved entries (${unresolved.length}), starting with ${first.id}`, command: `edit ${coverage.file(record.change)} — ${first.detail}` };
|
|
202
|
+
}
|
|
203
|
+
if (!authored) return { action: 'author the map from this draft, then validate it', command: `edit ${coverage.file(record.change)}, then ${RUNTIME} coverage --change ${record.change}` };
|
|
204
|
+
return { action: 'review the authored map, then preview adoption', command: `${RUNTIME} coverage adopt --preview --change ${record.change}` };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function render(d) {
|
|
208
|
+
const lines = [];
|
|
209
|
+
const count = o => Object.keys(o).length;
|
|
210
|
+
lines.push(`PINCER coverage draft · change ${d.change} · ${d.prd}`);
|
|
211
|
+
lines.push(`Inventory ${d.inventory.requirements} requirement(s), ${d.inventory.scenarios} scenario(s) · digest ${d.inventory.digest.slice(0, 12)}`);
|
|
212
|
+
lines.push(`Authored ${d.authored.map ? `${d.authored.map} · digest ${d.authored.digest.slice(0, 12)}` : 'no map yet'}`);
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push(`Scenarios ${count(d.scenarios)} linked row(s), ${count(d.scope)} scope row(s)`);
|
|
215
|
+
for (const [id, s] of Object.entries(d.scenarios)) {
|
|
216
|
+
const marker = s.state === AUTHORED ? ' ' : '?';
|
|
217
|
+
lines.push(` ${marker} ${id} ${s.requirement || '(not in the inventory)'} tickets ${s.tickets.length ? s.tickets.join(', ') : '—'} checks ${s.checks.length ? s.checks.join(', ') : '—'}`);
|
|
218
|
+
}
|
|
219
|
+
for (const [id, s] of Object.entries(d.scope)) {
|
|
220
|
+
lines.push(` ${s.state === AUTHORED ? ' ' : '?'} ${id} scope: ${s.disposition}${s.decision ? ` (${s.decision})` : ''}${s.note ? ` — ${s.note}` : ''}`);
|
|
221
|
+
}
|
|
222
|
+
lines.push('');
|
|
223
|
+
lines.push(`Tickets ${count(d.tickets)}`);
|
|
224
|
+
for (const [id, t] of Object.entries(d.tickets)) {
|
|
225
|
+
const c = d.candidates.tickets[id];
|
|
226
|
+
lines.push(` ${t.state === AUTHORED ? ' ' : '?'} ${id} role ${t.role || '—'}${t.rationale ? ` (${t.rationale})` : ''}${c && c.objective ? ` · ${c.objective}` : ''}`);
|
|
227
|
+
if (t.state !== AUTHORED && c) {
|
|
228
|
+
const claimed = [...c.implements, ...c.scenarios];
|
|
229
|
+
if (claimed.length) lines.push(` the ticket says it implements ${claimed.join(', ')} (${c.file}) — a statement to check, not a link`);
|
|
230
|
+
if (c.verification) lines.push(` its Verification block runs: ${c.verification.split('\n')[0]}${c.verification.includes('\n') ? ' …' : ''}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
lines.push('');
|
|
234
|
+
lines.push(`Checks ${count(d.checks)}`);
|
|
235
|
+
for (const [id, c] of Object.entries(d.checks)) {
|
|
236
|
+
lines.push(` ${c.state === AUTHORED ? ' ' : '?'} ${id} ${c.kind}${c.required ? ', required' : ''} ${c.kind === 'command' ? c.command : c.obligation}`);
|
|
237
|
+
}
|
|
238
|
+
if (!count(d.checks)) lines.push(' none declared — a check is authored, never derived from a ticket');
|
|
239
|
+
lines.push('');
|
|
240
|
+
if (d.unresolved.length) {
|
|
241
|
+
lines.push(`Unresolved ${d.unresolved.length}`);
|
|
242
|
+
for (const u of d.unresolved) lines.push(` ${u.code} ${u.detail}`);
|
|
243
|
+
} else {
|
|
244
|
+
lines.push('Unresolved none — every scenario has a row, every ticket a role, every linked check a declaration');
|
|
245
|
+
}
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push('This is a draft, not a coverage map: it carries no schema, and `coverage`,');
|
|
248
|
+
lines.push('`coverage adopt` and the agreement digest do not accept it. Author');
|
|
249
|
+
lines.push(`${coverage.file(d.change)} yourself, review the links, then validate and adopt it.`);
|
|
250
|
+
lines.push(`Next ${d.next.action}: ${d.next.command}`);
|
|
251
|
+
return `${lines.join('\n')}\n`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { DRAFT, KIND, UNRESOLVED_ORDER, AUTHORED, UNRESOLVED, build, render, nextAction };
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// node scripts/pincer-runtime.cjs change authorize <id> --agreement <digest> (--reference <text> --excerpt <text> | --delegated --basis A-NN --explanation <text>) [--decision D-NN]...
|
|
17
17
|
// node scripts/pincer-runtime.cjs change decide <id> --summary <text> [--id D-NN] | --resolve D-NN --reference <text> --excerpt <text>
|
|
18
18
|
// node scripts/pincer-runtime.cjs change activate|pause|resume|complete|reopen|cancel|supersede <id> [--reason <text>] [--note <text>] [--decision D-NN] [--with <id>]
|
|
19
|
-
// node scripts/pincer-runtime.cjs resume [--change <id>] [--json]
|
|
20
|
-
// node scripts/pincer-runtime.cjs coverage [--change <id>] [--json] · coverage adopt --preview|--apply --change <id> [--agreement <digest>]
|
|
19
|
+
// node scripts/pincer-runtime.cjs resume [--change <id>] [--brief] [--json]
|
|
20
|
+
// node scripts/pincer-runtime.cjs coverage [--change <id>] [--json] · coverage scaffold --change <id> [--json] · coverage adopt --preview|--apply --change <id> [--agreement <digest>]
|
|
21
21
|
// node scripts/pincer-runtime.cjs impact [--change <id>] [--from G-NN|A-NN] [--json]
|
|
22
22
|
//
|
|
23
23
|
// Exit codes: 0 ok · 1 failed/not ready/refused · 2 usage · 3 state busy ·
|
|
@@ -77,8 +77,8 @@ function usage(message) {
|
|
|
77
77
|
' pincer-runtime.cjs change decide <id> --summary <text> [--id D-NN] | --resolve D-NN --reference <text> --excerpt <text>\n' +
|
|
78
78
|
' pincer-runtime.cjs change activate|resume|complete <id> · change pause <id> --reason <text> [--note <text>] · change reopen <id> --reason <text>\n' +
|
|
79
79
|
' pincer-runtime.cjs change cancel <id> --decision D-NN --reason <text> · change supersede <id> --with <id> --decision D-NN\n' +
|
|
80
|
-
' pincer-runtime.cjs resume [--change <id>] [--json] (the read-only report; `change resume` is the lifecycle operation)\n' +
|
|
81
|
-
' pincer-runtime.cjs coverage [--change <id>] [--json] · coverage adopt --preview|--apply --change <id> [--agreement <digest>]\n' +
|
|
80
|
+
' pincer-runtime.cjs resume [--change <id>] [--brief] [--json] (the read-only report; `change resume` is the lifecycle operation)\n' +
|
|
81
|
+
' pincer-runtime.cjs coverage [--change <id>] [--json] · coverage scaffold --change <id> [--json] · coverage adopt --preview|--apply --change <id> [--agreement <digest>]\n' +
|
|
82
82
|
' pincer-runtime.cjs impact [--change <id>] [--from G-NN|A-NN] [--json]\n');
|
|
83
83
|
process.exit(EXIT.USAGE);
|
|
84
84
|
}
|
|
@@ -422,6 +422,22 @@ function cmdRegister(root, args) {
|
|
|
422
422
|
// entry into strict coverage (docs/runtime-contracts.md, "Adoption and rollback").
|
|
423
423
|
function cmdCoverage(root, args) {
|
|
424
424
|
const [sub, ...rest] = args;
|
|
425
|
+
// coverage scaffold --change <id> [--json] — the read-only coverage draft
|
|
426
|
+
// (docs/runtime-contracts.md, "Coverage draft"). Writes nothing and launches nothing;
|
|
427
|
+
// the draft it prints is not a map and `coverage adopt` does not accept it.
|
|
428
|
+
if (sub === 'scaffold') {
|
|
429
|
+
const o = parseOptions(rest, { switches: ['--json'], valued: ['--change'] });
|
|
430
|
+
if (o.positional.length) usage(`unexpected argument ${o.positional[0]} (coverage scaffold takes --change <id> [--json])`);
|
|
431
|
+
if (!o.change) usage('coverage scaffold requires --change <id>');
|
|
432
|
+
const scaffold = require('./pincer-runtime/scaffold.cjs');
|
|
433
|
+
const resolved = changes.resolveSelected(root, { change: o.change });
|
|
434
|
+
if (resolved.code) fail('pincer', `${resolved.code}: ${resolved.problem}`, exitForCode(resolved.code));
|
|
435
|
+
const result = scaffold.build(root, resolved.record);
|
|
436
|
+
if (!result.ok) fail('pincer', `${result.code}: ${result.problems[0]}`, exitForCode(result.code));
|
|
437
|
+
if (o.json) io.out(`${JSON.stringify(result.draft, null, 2)}\n`);
|
|
438
|
+
else io.out(scaffold.render(result.draft));
|
|
439
|
+
process.exit(EXIT.OK);
|
|
440
|
+
}
|
|
425
441
|
if (sub !== 'adopt') {
|
|
426
442
|
// The read-only coverage report (docs/runtime-contracts.md, "Coverage and impact commands").
|
|
427
443
|
const o = parseOptions(args, { switches: ['--json'], valued: ['--change'] });
|
|
@@ -476,9 +492,18 @@ function cmdImpact(root, args) {
|
|
|
476
492
|
|
|
477
493
|
// resume [--change <id>] [--json] — the read-only resume report (never the lifecycle operation).
|
|
478
494
|
function cmdResume(root, args) {
|
|
479
|
-
const o = parseOptions(args, { switches: ['--json'], valued: ['--change'] });
|
|
495
|
+
const o = parseOptions(args, { switches: ['--json', '--brief'], valued: ['--change'] });
|
|
480
496
|
if (o.positional.length) usage(`unexpected argument ${o.positional[0]}`);
|
|
481
497
|
const result = resume.build(root, { change: o.change || null });
|
|
498
|
+
// --brief is a projection of the same computed report: same verdict, same next
|
|
499
|
+
// action, same blocker categories, fewer bytes (docs/runtime-contracts.md, "Brief
|
|
500
|
+
// resume"). It never recomputes a decision and never changes the exit code.
|
|
501
|
+
if (o.brief) {
|
|
502
|
+
const brief = resume.brief(result.json);
|
|
503
|
+
if (o.json) io.out(`${JSON.stringify(brief, null, 2)}\n`);
|
|
504
|
+
else io.out(resume.renderBrief(brief));
|
|
505
|
+
process.exit(result.exit);
|
|
506
|
+
}
|
|
482
507
|
if (o.json) io.out(`${JSON.stringify(result.json, null, 2)}\n`);
|
|
483
508
|
else io.out(result.text);
|
|
484
509
|
process.exit(result.exit);
|