From dc85b9bdca46977f6ca1ac5113e87198415ef022 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Thu, 17 Sep 2026 09:54:46 +0200 Subject: [PATCH 001/143] docs: spec and plan the technical-design step, with its glossary terms and ADR --- .../adr/0004-ceremonies-follow-the-reader.md | 41 + docs/domain/glossary.md | 76 +- .../plans/2026-09-17-technical-design-step.md | 1646 +++++++++++++++++ .../specs/2026-09-16-technical-design-step.md | 900 +++++++++ 4 files changed, 2653 insertions(+), 10 deletions(-) create mode 100644 docs/domain/adr/0004-ceremonies-follow-the-reader.md create mode 100644 docs/plans/2026-09-17-technical-design-step.md create mode 100644 docs/specs/2026-09-16-technical-design-step.md diff --git a/docs/domain/adr/0004-ceremonies-follow-the-reader.md b/docs/domain/adr/0004-ceremonies-follow-the-reader.md new file mode 100644 index 0000000..6f2d8f5 --- /dev/null +++ b/docs/domain/adr/0004-ceremonies-follow-the-reader.md @@ -0,0 +1,41 @@ +--- +ticket: none +--- + +# A document's ceremonies follow its next reader + +The lifecycle rule branches between specs and plans in several places — +when a re-review offer comes due, what a stale `integrity:` stamp means +at a gate, who owns an unresolved verdict, how chain debt is discharged. +Every one of those branches is binary, and none of them is really about +the file's name. They divide documents by who reads them next. A spec is +read next by a reader who still judges it: the plan-writer who must turn +it into tasks, and the plan-adversary who names the origin of every +finding it makes. A plan is read next by whoever executes it — who may +well notice a mistake, but is promised to no reading of the plan as a +whole. + +So the ceremonies a class owes follow not from the existence of a later +reader but from what that reader is guaranteed to read. A judged +document has one: plan-writing reads it whole to turn it into tasks, and +the plan-adversary reads it far enough to name the origin of what it +finds. So that class may close its review chain by an offered pair and +may discharge the debt by declining — the risk accepted is bounded, +since a scheduled reader still faces the part no round re-read. Nothing +after a plan is guaranteed to read the plan whole, so that class owes a +mandatory confirming round instead. A new document class answers these +questions by naming its next reader rather than by negotiating each +branch separately: the technical design is read next by the plan-writer +and the plan-adversary, so it takes the spec's side of every one of +them. What the class settles is what a document owes, never how many +dispatches discharge it. A technical design owes the integrity check +like any judged document; one audit reading it together with its design +spec discharges that debt for both, and the reader is why — the +plan-writer takes the two as one basis, so the question of whether that +basis suffices is one question about a pair. The two sides are named in +the glossary — a **judged document** against an **implementation plan** +— and the rule's branches say which class they mean rather than which +filename. The class is not called a design document, though three agent +cards and the plugin README use that phrase today: a design +spec and a technical design both carry "design" in their names, so the +phrase reads as one of them rather than as both. diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index 7ee9faf..faa0ae4 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -135,12 +135,12 @@ model-capability ladder. _Avoid_: severity tier **Origin**: -The document a review finding traces to — `plan`, `spec`, or `both` — -named by the reviewer that found it rather than derived at triage. A -finding originating in the spec is held unless a written decision -licenses the edit, since editing a spec from inside a plan review is -design work. -_Avoid_: owner (for a document), source +The documents a review finding traces to, as a list naming one or more +of the process's document classes — named by the reviewer that found it +rather than derived at triage. A finding originating in the design spec +is held unless a written decision licenses the edit, since editing one +from inside a plan review is design work. +_Avoid_: owner (for a document), source, `both` **Rule tag**: The inline parenthesized annotation at a rule's (or sub-rule's) @@ -183,6 +183,60 @@ invented rule id; the `rule: none` citation is the report's only candidate-gap marker. _Avoid_: uncited observation, unmatched finding +**Design spec**: +The judged document saying what a change must do and why — its +behaviour, its decisions and its boundaries — written before any +technical design and read next by whoever turns it into structure. Short +form: spec, which every existing sentence and the `spec:` frontmatter +field still use. A project's own document plays this part only if it +carries the decisions and boundaries the later steps read; one that +states behaviour alone is an input to a design spec rather than one. +_Avoid_: requirements document, functional spec + +**Judged document**: +A document a reader who still judges it will read next — a design spec +or a technical design, each read next by the plan-writer and the +plan-adversary — as against an implementation plan, whose next reader +only executes it. The class decides which ceremonies a document owes +(ADR 0004), so the lifecycle rule branches on it rather than on a +filename. +_Avoid_: design document + +**Part**: +A unit of a design with a resolvable identity, an explicit +responsibility and its exclusions, and a contract that lets its +implementation change without changing its consumers. It earns a row of +its own when leaving it out would make an implementer decide a +responsibility boundary, a contract between parts, or the ownership of +state; details that only carry out decisions already made stay inside +the part that owns them. A public method is usually a row in its part's +contracts rather than a part of its own — the construct decides nothing, +the boundary does. + +**Contract**: +What a consumer of a part may rely on without reading the producer's +body — a name it resolves, a grammar it parses, a field it reads, a +pattern it discovers. Where the consumer must read that body there is no +contract but a coupling, and the technical design says so or cuts it. +Part and contract are two questions about one thing rather than +competing labels: a file may be a part, its signature a contract, and +the record it writes state. + +**Technical design**: +The judged document saying what a system is made of — its parts, the +contracts between them, and the state they keep — written between the +design spec and the plan, and read next by the plan-writer. +_Avoid_: detailed design + +**Vocabulary gap**: +A kind of part, contract, or home of state that no domain skill +available to the author names, recorded in the technical design where it +had to be invented. Its producer is the author rather than a reviewer, +so it carries no severity and enters no report; accumulated gaps are +what decide whether a technology earns a vocabulary skill of its own. +_Avoid_: candidate gap (a review finding, graded and counted), missing +term + **Sub-rule**: A dot-suffixed, tagged refinement of a rule whose violations grade differently from the rule's default — its own entry under the rule's @@ -256,11 +310,13 @@ _Avoid_: sub-tier record **Consumption gate**: The workflow step at which a document's review verdict is about to be relied on as the basis of further work — plan-writing for a spec, -implementation for a plan. Four things fire or come due there: the +implementation for a plan. Five things fire or come due there: the re-review offer on a fallback-recorded verdict, the integrity audit -offered when a spec's body hash no longer matches its stamp, the pair -question a diff-scoped chain earns, and the chain debt itself. Nothing -orders them against each other yet. +offered when a document's body, or that of the document audited with it, +no longer matches its stamp, the pair question a diff-scoped chain earns, the chain debt +itself, and the offer of a technical design. Only one ordering is fixed: +everything a technical design owes settles before anything its design +spec owes, and the questions reach the developer in one batch. _Avoid_: usage point **Unfinished-work list**: diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md new file mode 100644 index 0000000..c523976 --- /dev/null +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -0,0 +1,1646 @@ +--- +ticket: none +date: 2026-09-17 +status: draft +spec: ../specs/2026-09-16-technical-design-step.md +branch: feature/technical-design-step +base: develop +--- + +# Technical design step — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Ship the contract for a third document class — the technical +design — across the `working-process` plugin: one new path-scoped rule, +six changed rules and skills, five changed agent cards and the plugin +README, with the glossary and ADR 0004 already landed during spec +authoring. + +**Architecture:** Every deliverable is prose in a shipped rule, agent +card, skill description or README. There is no code and no test suite; a +task's test is the grep pair it publishes, run before and after the +edit. Tasks are drawn one per site, so no two touch the same text — +`spec-plan-lifecycle.md` takes four tasks and `workflow.md` three, +because the change edits sections of each that share no line. The +plugin ships this contract and does not execute it: no +`docs/technical-designs/` directory is created in this repo. + +**Tech Stack:** Markdown rule, agent and skill files distributed as a +Claude Code Rules payload; `grep`, `tr` and `claude plugin validate` for +verification. + +## Global Constraints + +- **The spec is the source.** Where this plan and the spec disagree, the + spec wins and the plan is wrong — except where *Deviations from the + spec* below records a departure and its reason. +- **Prose wraps at 72 characters** in every rule, agent card, skill and + README file. Match the surrounding paragraph; never reflow a paragraph + this plan does not change. +- **A check over prose normalizes whitespace first** + (`tr -s '[:space:]' ' '`), so a phrase matches wherever a line wraps. + Only a check anchoring something that cannot wrap — a path, a filename + shape, a heading, a frontmatter key — is written plain. +- **Every step states its before value and its after value.** The before + values in this plan were measured against the files on 2026-09-17, not + predicted. +- **`design document` appears five times in four shipped files** before + this plan runs — twice in `agents/plan-adversary.md`, once each in + `agents/architect.md`, `agents/integrity-auditor.md` and `README.md`. + When the plan is done it appears zero times in those files. No task + may introduce a sixth. +- **The glossary binds.** `docs/domain/glossary.md` terms and `_Avoid_` + bans govern this text. `Design spec`, `Judged document`, `Technical + design`, `Part`, `Contract`, `Vocabulary gap`, `Origin` and + `Consumption gate` are canonical here. `design document` is banned by + **Judged document**'s `_Avoid_` list and appears in no replacement + block. `session` never names a skill — write + `` `system-designer-session` skill``. +- **Public repo hygiene**: no machine-specific paths, no company or + client names, all committed text in English. +- **Commit messages** are one conventional-commit line, no body, no + trailers. +- **Paths in this plan are repo-relative.** Rule and agent files live + under `plugins/working-process/`. + +--- + +### Task 1: Verify the two documents already on disk + +The spec's Parts table lists the glossary and ADR 0004 as parts of this +change. Both landed while the spec was authored and are uncommitted in +the working tree. This task verifies them rather than writing them, so a +later task never re-mints a term that already exists. + +**Files:** +- Verify: `docs/domain/glossary.md` +- Verify: `docs/domain/adr/0004-ceremonies-follow-the-reader.md` + +**Interfaces:** +- Produces: the seven glossary terms every later task's prose leans on — + `Design spec`, `Judged document`, `Technical design`, `Part`, + `Contract`, `Vocabulary gap`, and `Origin` in its list form — plus ADR + 0004 as the citable decision behind the class. + +- [ ] **Step 1: Confirm every term the spec mints exists** + +Run: + +```bash +for t in "Design spec" "Judged document" "Technical design" "Part" \ + "Contract" "Vocabulary gap" "Origin" "Consumption gate"; do + printf '%-18s %s\n' "$t" \ + "$(grep -c "^\*\*${t}\*\*:" docs/domain/glossary.md)" +done +``` + +Expected: `1` for every term. A `0` means the glossary edit was lost — +stop and restore it from the spec's Parts table before continuing. + +- [ ] **Step 2: Confirm `Origin` is a list and bans `both`** + +Run: + +```bash +grep -A8 '^\*\*Origin\*\*:' docs/domain/glossary.md | \ + tr -s '[:space:]' ' ' | grep -c 'Avoid.*`both`' +``` + +Expected: `1`. The list form is what Task 12 writes the adversary's +`origin` field against. + +- [ ] **Step 3: Confirm ADR 0004 exists and names the class** + +Run: + +```bash +tr -s '[:space:]' ' ' < docs/domain/adr/0004-ceremonies-follow-the-reader.md | \ + grep -c 'judged document' +``` + +Expected: `1` or more. + +- [ ] **Step 4: Confirm the Consumption gate entry names five items** + +Run: + +```bash +grep -A12 '^\*\*Consumption gate\*\*:' docs/domain/glossary.md | \ + tr -s '[:space:]' ' ' +``` + +Expected: five items listed, with the ordering of one pair fixed and the +rest unordered. This is the entry Task 10 points the gate offer at. + +- [ ] **Step 5: Commit** + +```bash +git add docs/domain/glossary.md docs/domain/adr/0004-ceremonies-follow-the-reader.md \ + docs/specs/2026-09-16-technical-design-step.md docs/plans/2026-09-17-technical-design-step.md +git commit -m "docs: spec and plan the technical-design step, with its glossary terms and ADR" +``` + +--- + +### Task 2: The new path-scoped rule + +**Files:** +- Create: `plugins/working-process/rules/technical-design.md` + +**Interfaces:** +- Produces: the section skeleton every later task refers to, the three + definitions (part, contract, state), the closed `change` value set + `new | changed | retired | unchanged`, and the term **vocabulary + gap**. Task 12 reads the `change` set; Task 5 reads the skeleton's + section 7–8 delegation to the lifecycle rule. + +- [ ] **Step 1: Confirm the file does not exist** + +Run: `ls plugins/working-process/rules/technical-design.md` +Expected: `No such file or directory`. + +- [ ] **Step 2: Write the rule** + +Create `plugins/working-process/rules/technical-design.md`: + +```markdown +--- +paths: + - "docs/technical-designs/**" +--- + +# Technical design — what the thing is made of + +A technical design answers what a thing is made of, as its design spec +answers what and why and the implementation plan answers in what order +and verified how. It lives in `docs/technical-designs/`, one file per +design spec, named `-technical-design.md`. + +The boundary against the design spec runs between identity and +placement. The design spec names parts by the name their consumers use +and gives each one a responsibility. The technical design decides what +naming a part leaves open: where it sits, what shape the contract on +its border takes, who writes it and who reads it, what is state and +where that state is authoritative. Where naming a part fixes its +location, the name is the path and the design spec carries it. + +## Section skeleton + +| # | section | status | content | +|---|---|---|---| +| 1 | Scope | required | which design spec and which of its behaviours this structure realizes; the system boundary | +| 2 | Parts | required | `part \| kind \| change \| placement \| owns \| deliberately excludes`; a retirement is a `change: retired` row | +| 3 | Contracts | required | `producer → consumer \| crosses \| guarantee \| breaks when \| check`; a flow crossing three or more parts in fixed order becomes a numbered sequence | +| 4 | State | conditional | `record \| home \| written by \| read by \| lifecycle \| visible through` | +| 5 | Failure and repetition | conditional | what happens when a step does not finish, runs twice, runs beside another, or finds the other side absent | +| 6 | Cuts not taken | conditional | one line per rejected division and its reason | +| 7–8 | Open questions, Review rounds | from the spec-plan-lifecycle rule | as a design spec and a plan carry them today | + +Three sections are required because only those three are never empty, +on prose or on code. A conditional section that is absent is named in +one line — "not applicable, because…" — never silently dropped. + +`kind` answers what a part is; `placement` answers where it belongs — +its layer, module, package, service, or path, at the level the design +decision needs. The two coincide only where a domain convention derives +one from the other, and the cell is filled even then: a cell reading +`rules/` says the convention was applied deliberately, while an empty +one cannot be told apart from an omission. + +`change` takes one of `new`, `changed`, `retired` or `unchanged`. The +set is closed and a domain adds no value to it: the column steers the +process, so its meaning belongs to the core, and a value outside the +set is an error the author corrects rather than a part that quietly +escapes the plan's coverage check. + +Tests are not a section. What verifies a contract is the contract's +`check` column; when that check runs is the plan's business. + +## The three definitions + +A **part** has a resolvable identity, an explicit responsibility with +its exclusions, and a contract that lets its implementation change +without changing its consumers. + +A **contract** is what a consumer may rely on without reading the +producer's body. + +**State** is a record that outlives one interaction and is read by a +later one, with a home, a writer, a reader and an end. + +Part and contract are two questions about one thing rather than +competing labels: a file may be a part, its signature a contract, and +the record it writes state. + +## What earns a part its own row + +Listing a part separately is a design decision. List it when leaving it +out would make an implementer decide a responsibility boundary, a +contract between parts, or the ownership of state. Keep inside a part +the details that merely carry out decisions already made — a public +method is usually a row in its part's contracts rather than a part of +its own. + +Task boundaries never define part boundaries: the plan is organised +around the structure, not the structure around the plan. The grain +comes from the decisions the document exists to settle — the same +question an integrity audit asks of a design spec, which is what this +text would leave an implementer to decide. + +## Domain vocabulary + +The author takes the part vocabulary — kinds of part, placement rules, +kinds of contract, homes of state — from the Domain expertise duty the +personas already carry, which has them scan and load the domain's own +skills. No discovery convention of its own ships here. + +A kind of part that no domain skill names is recorded in the document +as a **vocabulary gap**: the author names the kind, writes the gap +down, and the architect round judges the boundary, which is what it +judges in any case. Accumulated vocabulary gaps are either the +specification of a `-technical-design` skill or the evidence +that none is needed. + +Where no skill covers the technology at all, the document is written +from generic knowledge, best effort. That degradation path is what +makes this contract self-sufficient. + +## What the core closes + +The core closes the three definitions above, the table columns, the +contract entry's fields, and the four questions of the failure section. +A domain adds rows and values — outside the closed `change` column — +never columns, and never redefines what qualifies: a kind of part it +names must still have a resolvable identity, an explicit +responsibility, and a contract its implementation can change behind. +``` + +- [ ] **Step 3: Verify the rule's own claims** + +Run: + +```bash +F=plugins/working-process/rules/technical-design.md +grep -c '^| [0-9]' $F # skeleton rows +grep -c 'new | changed | retired | unchanged' $F +awk 'length > 72' $F | grep -v '^|' | wc -l # over-wide non-table lines +``` + +Expected: `7` skeleton rows (1–6 plus the `7–8` row), `1` for the closed +set, `0` over-wide prose lines. + +- [ ] **Step 4: Validate the plugin** + +Run: `claude plugin validate plugins/working-process` +Expected: passes. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/technical-design.md +git commit -m "feat(working-process): add the technical-design rule" +``` + +--- + +### Task 3: The new Process directory + +**Files:** +- Modify: `plugins/working-process/rules/process-artifacts.md:1-8` and + the Process-directory sentence at `:12-16` + +**Interfaces:** +- Produces: `docs/technical-designs/` as a named Process directory, so + the first-create question fires for it and Task 4 can assign it a + field set. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/rules/process-artifacts.md +grep -c 'technical-designs' $F +sed -n '2,8p' $F +``` + +Expected: `0`, and a `paths:` block of five globs, the last +`.superpowers/**`. + +- [ ] **Step 2: Add the glob** + +In the frontmatter, after the `"docs/plans/**"` line, insert: + +```yaml + - "docs/technical-designs/**" +``` + +- [ ] **Step 3: Add the directory to the prose list** + +Replace: + +``` +project repo to hold work artifacts: `docs/specs/`, `docs/plans/`, +`docs/domain/`, `docs/code-review/`, `docs/memory/` (Team memory — when the +``` + +with: + +``` +project repo to hold work artifacts: `docs/specs/`, +`docs/technical-designs/`, `docs/plans/`, `docs/domain/`, +`docs/code-review/`, `docs/memory/` (Team memory — when the +``` + +- [ ] **Step 4: Verify both sites** + +Run: + +```bash +F=plugins/working-process/rules/process-artifacts.md +grep -c '"docs/technical-designs/\*\*"' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c '`docs/specs/`, `docs/technical-designs/`, `docs/plans/`' # expect 1 +``` + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/process-artifacts.md +git commit -m "feat(working-process): count docs/technical-designs among Process directories" +``` + +--- + +### Task 4: The new directory's field set + +**Files:** +- Modify: `plugins/working-process/rules/ticket-frontmatter.md:13-15` + +**Interfaces:** +- Consumes: the directory named in Task 3. +- Produces: the guarantee that a technical design carries the full + process field set rather than the bare `ticket` + `date` its + per-directory list would otherwise give it. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +tr -s '[:space:]' ' ' < plugins/working-process/rules/ticket-frontmatter.md | \ + grep -o 'Specs and plans (`docs/specs/`, `docs/plans/`)' +``` + +Expected: one match. + +- [ ] **Step 2: Widen the first field-set bullet** + +Replace: + +``` +- Specs and plans (`docs/specs/`, `docs/plans/`): `ticket` + `date` + + `status` + the process and branch fields — details in the + spec-plan-lifecycle rule. +``` + +with: + +``` +- Design specs, technical designs and plans (`docs/specs/`, + `docs/technical-designs/`, `docs/plans/`): `ticket` + `date` + + `status` + the process and branch fields — details in the + spec-plan-lifecycle rule. +``` + +- [ ] **Step 3: Verify the catch-all no longer claims the directory** + +Run: + +```bash +F=plugins/working-process/rules/ticket-frontmatter.md +tr -s '[:space:]' ' ' < $F | grep -c 'technical designs and plans' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'Every other document under `docs/`' # expect 1 +``` + +The second still stands: it is a catch-all, and the first bullet now +takes the new directory out of its reach. + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/rules/ticket-frontmatter.md +git commit -m "feat(working-process): give technical designs the process field set" +``` + +--- + +### Task 5: The lifecycle rule — scope and the class at each branch + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md:1-6` + (`paths:`), `:481-495` (Lifecycle offers), `:504-509` (the + consumption-gate backstop) + +**Interfaces:** +- Consumes: `Judged document` from Task 1. +- Produces: the rule loading for `docs/technical-designs/**`, and every + branch reading the class rather than a filename. Tasks 6, 7 and 8 edit + other sections of this file and must not touch these lines. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +sed -n '2,5p' $F +grep -c 'judged document' $F # expect 0 +grep -c 'technical-design' $F # expect 0 +``` + +- [ ] **Step 2: Widen the rule's scope** + +In the frontmatter, after ` - "docs/specs/**"`, insert: + +```yaml + - "docs/technical-designs/**" +``` + +- [ ] **Step 3: Name the class in the offers paragraph** + +Replace: + +``` +architect-review a grilled spec (architect agent dispatch); +``` + +with: + +``` +architect-review a grilled design spec, and a technical design, which +is never grilled (architect agent dispatch); +``` + +Then replace: + +``` +spec or plan moves to `implemented` and the memory-review-session skill +``` + +with: + +``` +judged document or plan moves to `implemented` and the +memory-review-session skill +``` + +- [ ] **Step 4: Widen the consumption-gate backstop** + +Replace: + +``` +A document's consumption gate is the backstop for its ledger: a spec +does not pass to plan-writing, nor a plan to implementation, while +`held` lines stay open — an LGTM can leave the frontmatter clean while a +``` + +with: + +``` +A document's consumption gate is the backstop for its ledger: no judged +document passes to plan-writing, nor a plan to implementation, while +`held` lines stay open — an LGTM can leave the frontmatter clean while a +``` + +- [ ] **Step 5: State that a technical design is not grilled** + +After the offers paragraph, add: + +``` +A technical design is a judged document and takes the design spec's side +of every branch in this rule: the same fields, the same +consumption-gate semantics for a stale stamp, the same owner for an +unresolved verdict, and the same pair offered against chain debt. It is +not grilled — grilling stress-tests terminology against the project +glossary, and a technical design mints none: its vocabulary comes from +its design spec, which was grilled, and from the domain's own skills. +The names it does mint are part and component names, checked against +those skills where one exists and recorded as a vocabulary gap where +none does. +``` + +- [ ] **Step 6: Verify** + +Run: + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +grep -c '"docs/technical-designs/\*\*"' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'no judged document passes to plan-writing' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'It is not grilled' # expect 1 +``` + +- [ ] **Step 7: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): branch the lifecycle on the document class" +``` + +--- + +### Task 6: The lifecycle rule — the two pointers + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md:16-26` + (the frontmatter example) and the field notes below it + +**Interfaces:** +- Consumes: the class branches from Task 5. +- Produces: `technical-design:` and the widened `spec:`, which Task 9 + makes the propagation auditor check and Task 11 makes the authoring + step write. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +grep -n 'spec: \.\./specs/' plugins/working-process/rules/spec-plan-lifecycle.md +``` + +Expected: one line, commented `plans only`. + +- [ ] **Step 2: Widen `spec:` and add `technical-design:`** + +Replace: + +``` +spec: ../specs/.md # plans only: the spec this plan implements; inline list when several +``` + +with: + +``` +spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several +technical-design: ../technical-designs/.md # optional, on a design spec and on a plan: the technical design that develops this design spec +``` + +- [ ] **Step 3: Write the five rules that keep the pair honest** + +After the field notes, add: + +``` +- `technical-design:` is optional and appears once the technical design + exists, so its absence means there is none rather than one nobody + linked. It is new in kind: every other pointer records where a + document came from, while this one names a document written later, + and it gives a reader holding the design spec the structure that + develops it. The author who creates the design writes both ends in + the same turn — the design's `spec:` and the design spec's + `technical-design:`. A plan keeps its own `spec:` and + `technical-design:`, and where it carries both they must agree with + the pair. One technical design per design spec. +``` + +- [ ] **Step 4: Verify** + +Run: + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +grep -c '^technical-design: ' $F # expect 1 +grep -c 'plans and technical designs' $F # expect 1 +grep -c 'plans only' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'One technical design per design spec' # expect 1 +``` + +The third check is the deletion assertion: the old `plans only` comment +must be gone, not merely contradicted further down. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): add the technical-design pointer pair" +``` + +--- + +### Task 7: The lifecycle rule — the pair stamp + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md:21` + (the frontmatter example) and `:81-105` (the `integrity:` notes) + +**Interfaces:** +- Consumes: the pointers from Task 6. +- Produces: the `with:` value shape, which Task 14 makes the auditor + emit and Task 18 documents in the README's field table. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +grep -c 'with: ' $F # expect 0 +grep -n 'sha: ' $F +``` + +Expected: `0`, and two lines carrying the bare value shape. + +- [ ] **Step 2: Extend the frontmatter example** + +Replace: + +``` +integrity: (sha: ) # optional: date of the last integrity audit, plus the body hash it certifies +``` + +with: + +``` +integrity: (sha: [; with: @]) # optional: date of the last integrity audit, the body hash it certifies, and — where a technical design was audited with it — that document and its hash +``` + +- [ ] **Step 3: Write the pair clause** + +After the paragraph ending `so writing the stamp never invalidates what +it stamps.`, add: + +``` + A design spec that names a technical design is audited with it as one + target, and one stamp records the pair. The stamp lives on the design + spec, names both documents and both body hashes, and the technical + design carries no `integrity:` of its own — it owes the check like any + judged document and discharges it jointly. The value takes the form + `integrity: (sha: ; with: @)`, + where `` is the technical design's path relative to the design + spec. Changing either document unsettles the pair, and the gate + recomputes both hashes to see it; because the two pointers are what + identify the pair, an added, removed or repointed `technical-design:` + unsettles it too. +``` + +- [ ] **Step 4: Verify both directions** + +Run: + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +grep -c 'with: @' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'carries no `integrity:` of its own' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'recomputes both hashes' # expect 1 +``` + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): record a pair audit in one integrity stamp" +``` + +--- + +### Task 8: The lifecycle rule — the gate's passes and the status move + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md`, + Lifecycle offers section, after the consumption-gate backstop + paragraph + +**Interfaces:** +- Consumes: the class branches from Task 5. +- Produces: the ordering and the pass structure Task 10's gate offer + fires inside. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +tr -s '[:space:]' ' ' < plugins/working-process/rules/spec-plan-lifecycle.md | \ + grep -c 'runs in passes' +``` + +Expected: `0`. + +- [ ] **Step 2: Write the ordering and the passes** + +After the paragraph ending `the held spec questions are asked before the +plan is written, whoever writes it.`, add: + +``` +A technical design's own consumption gate is plan-writing, the same +moment as its design spec's, and one ordering binds the two: writing +the design and applying its review dispositions precedes the joint +integrity audit, because the design is the last producer of changes to +the design spec and certifying that body before the design's questions +are answered stamps a body about to change. The dependency reaches no +further — every other item of the two gates stays unordered. + +The gate therefore runs in passes. The first pass decides whether a +technical design is written at all. Where the offer is accepted, the +gate suspends until the design exists and its review dispositions are +applied, then resumes and collects the questions the documents' current +state raises. Answers already given stay binding unless the basis they +rested on changed. Each pass asks its questions in one batch, as a +review round does — a batch being every question answerable at that +pass, never every question the gate will ever ask. + +A technical design's `status` reaches `implemented` with its plan's, in +the same turn and by the same hand, since otherwise the rule freezing an +implemented document's body never reaches it. +``` + +- [ ] **Step 3: Verify** + +Run: + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +tr -s '[:space:]' ' ' < $F | grep -c 'The gate therefore runs in passes' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'precedes the joint integrity audit' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'in the same turn and by the same hand' # expect 1 +``` + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): run the consumption gate in passes" +``` + +--- + +### Task 9: The propagation auditor's author-facing duty + +**Files:** +- Modify: `plugins/working-process/rules/propagation-duties.md:1-6` + (`paths:`) and its duty list + +**Interfaces:** +- Consumes: the pointers from Task 6. +- Produces: the author's duty to check both pointers resolve and name + each other — the mechanical half of the pair's honesty. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/rules/propagation-duties.md +sed -n '2,5p' $F +grep -c 'technical-design' $F # expect 0 +grep -c '^- \|^## ' $F +``` + +- [ ] **Step 2: Widen the rule's scope** + +In the frontmatter, after ` - "docs/specs/**"`, insert: + +```yaml + - "docs/technical-designs/**" +``` + +- [ ] **Step 3: Add the pointer-pair duty** + +Append to the duty list: + +``` +- **The pointer pair resolves both ways.** Where a design spec carries + `technical-design:` or a technical design carries `spec:`, both target + files exist and each pointer names the document that names it. A + pointer resolving to a missing file, or to a document pointing + somewhere else, is a hit: the pair is what identifies the audit's + target, so a broken half silently narrows what the next audit reads. +``` + +- [ ] **Step 4: Verify** + +Run: + +```bash +F=plugins/working-process/rules/propagation-duties.md +grep -c '"docs/technical-designs/\*\*"' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'The pointer pair resolves both ways' # expect 1 +``` + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/propagation-duties.md +git commit -m "feat(working-process): make the author check the pointer pair" +``` + +--- + +### Task 10: The workflow rule — the step and its gate offer + +**Files:** +- Modify: `plugins/working-process/rules/workflow.md:36-56` (step 4, + *Spec → plan*) + +**Interfaces:** +- Consumes: the passes from Task 8. +- Produces: the offer that decides a technical design gets written, and + the marker test that fires it. The offer lives in the always-on rule + because it fires before any technical design exists, with the + path-scoped rule of Task 2 therefore unloaded. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/rules/workflow.md +grep -c 'technical design' $F # expect 0 +sed -n '36,38p' $F +``` + +Expected: `0`, and step 4 opening `4. **Spec → plan.** At the spec's +consumption gate, before the plan is`. + +- [ ] **Step 2: Insert the new step before the gate's other business** + +Immediately after the line + +``` +4. **Spec → plan.** At the spec's consumption gate, before the plan is +``` + +the step continues as it does today. Insert, as the step's first +paragraph after its existing offer sentence, a new bullet under it: + +``` + At the same gate, and before the integrity audit above, offer the + technical design when the repository is one that has code. A + declaration the project records binds and is never re-asked, in + either direction. Without a declaration, a repository carrying a + toolchain manifest — a file a language or platform toolchain reads + to build, test or deploy it — gets the offer at every consumption + gate and nothing is written; a repository carrying neither gets no + offer. A marker proves the repository holds code, never that this + change needs decomposing, so the declaration is the signal and the + marker only raises the question. The offer reads *open the + `system-designer-session` skill and write the technical design?* — + never *dispatch*, which names a background agent here. A session may + propose writing the declaration and never writes it unasked. A + change may skip the document when it sits inside boundaries and + contracts already settled and leaves the implementer no new + responsibility split, placement, or ownership of state. +``` + +- [ ] **Step 3: Order the audit behind it** + +Replace, inside step 4: + +``` + The brief + confirms the auditor's two preconditions: every edit from the +``` + +with: + +``` + Where the offer is accepted, the audit waits: the design is the last + producer of changes to its design spec, and the two are then audited + as one target. The brief + confirms the auditor's two preconditions: every edit from the +``` + +- [ ] **Step 4: Verify** + +Run: + +```bash +F=plugins/working-process/rules/workflow.md +tr -s '[:space:]' ' ' < $F | grep -c 'system-designer-session` skill and write the technical design' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'the declaration is the signal and the marker only raises the question' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'audited as one target' # expect 1 +grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill +``` + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/workflow.md +git commit -m "feat(working-process): offer the technical design at the consumption gate" +``` + +--- + +### Task 11: The workflow rule — the authoring step writes both pointers + +**Files:** +- Modify: `plugins/working-process/rules/workflow.md`, step 4, after the + offer added in Task 10 + +**Interfaces:** +- Consumes: the offer from Task 10 and the pointers from Task 6. +- Produces: the one turn in which both ends of the pair are written, so + Task 9's duty never finds a half-written pair. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +tr -s '[:space:]' ' ' < plugins/working-process/rules/workflow.md | \ + grep -c 'writes both ends' +``` + +Expected: `0`. + +- [ ] **Step 2: Write the authoring step** + +After the offer paragraph, add: + +``` + Accepted, the session opens the `system-designer-session` skill in + the main thread and writes the document to + `docs/technical-designs/`, following the technical-design rule that + loads there. It writes both ends of the pair in the same turn — the + design's `spec:` and the design spec's `technical-design:` — so no + audit ever meets a half-written pair. The reviewer is the + `architect` agent, whose card admits any judged document dispatched + standalone, with the propagation audit gating that dispatch as it + gates every verdict dispatch. The plan-adversary stays on plans. +``` + +- [ ] **Step 3: Verify** + +Run: + +```bash +F=plugins/working-process/rules/workflow.md +tr -s '[:space:]' ' ' < $F | grep -c 'writes both ends of the pair in the same turn' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'The plan-adversary stays on plans' # expect 1 +``` + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/rules/workflow.md +git commit -m "feat(working-process): name the technical design's author and reviewer" +``` + +--- + +### Task 12: The workflow rule — the triage clause reads a list + +**Files:** +- Modify: `plugins/working-process/rules/workflow.md:333-339` + +**Interfaces:** +- Consumes: `Origin` in its list form from Task 1. +- Produces: the widened hold, which Task 15 makes the adversary's report + schema able to express. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +sed -n '333,339p' plugins/working-process/rules/workflow.md +``` + +Expected: the clause opening `- A finding whose `origin` names the spec +is held unless a written` and carrying ``both` holds the same way`. + +- [ ] **Step 2: Widen the hold to the class** + +Replace: + +``` +- A finding whose `origin` names the spec is held unless a written + decision licenses the edit, since editing a spec from inside a plan + review is design work; `both` holds the same way, and its `held` + line names in `options:` which half is fixable at once. A licensed + spec-origin fix lands in the spec's own ledger and the plan's line + points at it, by the cross-document clause the spec-plan-lifecycle + rule defines +``` + +with: + +``` +- A finding whose `origin` names a judged document — a design spec or a + technical design — is held unless a written decision licenses the + edit, since changing either from inside a plan review is design work. + `origin` is a list, so a finding naming more than one holds the same + way, and its `held` line names in `options:` which part is fixable at + once. A licensed fix lands in the ledger of the document that + changed, and the plan's line points at it, by the cross-document + clause the spec-plan-lifecycle rule defines +``` + +- [ ] **Step 3: Verify the retired value is gone** + +Run: + +```bash +F=plugins/working-process/rules/workflow.md +tr -s '[:space:]' ' ' < $F | grep -c 'names a judged document' # expect 1 +grep -c '`both`' $F # expect 0 +``` + +The second check is the deletion assertion: `both` retires with the +value, and a surviving mention would send triage down a branch the +adversary can no longer emit. + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/rules/workflow.md +git commit -m "feat(working-process): hold a finding from either judged document" +``` + +--- + +### Task 13: The architect card takes the class name + +**Files:** +- Modify: `plugins/working-process/agents/architect.md:3` + +**Interfaces:** +- Consumes: `Judged document` from Task 1. +- Produces: the card sentence Task 11's authoring step cites when it + says the architect admits any judged document standalone. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +grep -c 'design document' plugins/working-process/agents/architect.md +``` + +Expected: `1`. + +- [ ] **Step 2: Rename the class in the description** + +Replace, in the `description:` field: + +``` +a grilled spec (primary target) or any design document dispatched standalone +``` + +with: + +``` +a grilled design spec (primary target) or any judged document dispatched standalone +``` + +- [ ] **Step 3: Verify** + +Run: + +```bash +F=plugins/working-process/agents/architect.md +grep -c 'design document' $F # expect 0 +grep -c 'any judged document' $F # expect 1 +grep -c 'a grilled design spec' $F # expect 1 +``` + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/agents/architect.md +git commit -m "feat(working-process): let the architect card name the judged-document class" +``` + +--- + +### Task 14: The integrity auditor reads a pair + +The spec requires these three edits to land together: any one alone +leaves the card contradicting itself, which is the defect class this +auditor hunts. + +**Files:** +- Modify: `plugins/working-process/agents/integrity-auditor.md:3` + (description), `:76-81` (Target and moment), `:101-103` (the second + lens), `:133-137` (the coverage tell) + +**Interfaces:** +- Consumes: the pointers from Task 6 and the stamp from Task 7. +- Produces: the four cases, the narrowed second lens, and the + coverage tell's per-document shape. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/agents/integrity-auditor.md +grep -c 'design document' $F # expect 1 +grep -c 'coverage: ' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'Your primary target is a spec' # expect 1 +``` + +- [ ] **Step 2: Rename the class in the description** + +Replace `Judgment audit of a churned design document` with +`Judgment audit of a churned judged document`. + +- [ ] **Step 3: Write the four cases** + +Replace: + +``` +Your primary target is a spec, audited at the consumption gate before +plan-writing: after the architect round and after its dispositions are +applied, which is where the churn accumulates. A plan is a permitted +target on explicit request, never a gated one. Read the whole target, +first line to last. +``` + +with: + +``` +Your primary target is a design spec, audited at the consumption gate +before plan-writing: after the architect round and after its +dispositions are applied, which is where the churn accumulates. Four +cases fix what you read: + +A design spec with no technical design is a single target. A design +spec that names one is audited together with it, both documents the +target and neither the other's context. A plan, a permitted target on +explicit request, never a gated one, is the target alone and reads the +documents its `spec:` and `technical-design:` name as context. A +technical design is never a target alone: its audit is its design +spec's. + +No other field carries context. `revises:` names a document whose +design no longer matches what shipped, and reading an archive as +authority is how a stale claim re-enters a live one. + +Read the whole target, first line to last — every document of it. +``` + +- [ ] **Step 4: Narrow the second lens** + +Replace: + +``` +Read the document again as a careful implementer who must build from this +text and has no other context: no conversation, no author to ask. +``` + +with: + +``` +Read the document again as a careful implementer who must build from +this text, the documents audited with it, and the documents its `spec:` +and `technical-design:` name, and has no other context: no +conversation, no author to ask. +``` + +- [ ] **Step 5: Give the coverage tell a pair of numbers** + +Replace: + +``` + coverage: lines; highest line cited +``` + +with: + +``` + coverage: lines, highest line cited [; …] +``` + +and extend the sentence below it: + +``` +The tell is how a partial read exposes itself, so report both numbers +even when they embarrass the run. A joint target reports a pair per +document: one document read whole while the other was skimmed is the +one risk peculiar to a pair, and a single pair of numbers cannot show +it. +``` + +- [ ] **Step 6: Verify all three edits landed and the old text is gone** + +Run: + +```bash +F=plugins/working-process/agents/integrity-auditor.md +grep -c 'design document' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'Four cases fix what you read' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'never a target alone' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'the documents audited with it' # expect 1 +grep -c 'coverage: ' $F # expect 1 +grep -c 'coverage: ' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'Your primary target is a spec,' # expect 0 +``` + +The last two are deletion assertions. + +- [ ] **Step 7: Commit** + +```bash +git add plugins/working-process/agents/integrity-auditor.md +git commit -m "feat(working-process): audit a design spec and its technical design as one target" +``` + +--- + +### Task 15: The plan adversary reads a list of origins + +**Files:** +- Modify: `plugins/working-process/agents/plan-adversary.md:46`, `:49`, + `:102`, and the generic dimensions list at `:57-70` + +**Interfaces:** +- Consumes: the triage clause from Task 12 and the `change` set from + Task 2. +- Produces: the `origin` list in the report schema and the coverage + read of the `change` column. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/agents/plan-adversary.md +grep -c 'design document' $F # expect 2 +grep -c '"origin": "plan" | "spec" | "both"' $F # expect 1 +grep -c '^### [0-9]' $F +``` + +- [ ] **Step 2: Rename the class in both sentences** + +Replace: + +``` +Handed a spec (a design document, not an implementation plan)? Decline +``` + +with: + +``` +Handed a judged document (a design spec or a technical design, not an +implementation plan)? Decline +``` + +Replace `to a design document and would misfire as findings.` with +`to a judged document and would misfire as findings.` + +- [ ] **Step 3: Make `origin` a list** + +Replace: + +``` + "origin": "plan" | "spec" | "both", +``` + +with: + +``` + "origin": ["design-spec" | "technical-design" | "implementation-plan", …], +``` + +- [ ] **Step 4: Add the coverage read of the `change` column** + +Append to the generic dimensions: + +``` +### 6. Coverage of the technical design's parts + +Where the plan's `technical-design:` names a document, read its Parts +table. The plan must cover every part marked `new`, `changed` or +`retired`; one part may span several tasks and one task several parts, +and a part carried as `unchanged` context needs none. An uncovered part +is an Important finding whose `origin` is the plan. A `change` value +outside `new | changed | retired | unchanged` is an Important finding +whose `origin` is the technical design: the set is closed, and an +unknown value would otherwise slip past this dimension unchecked. +``` + +- [ ] **Step 5: Verify** + +Run: + +```bash +F=plugins/working-process/agents/plan-adversary.md +grep -c 'design document' $F # expect 0 +grep -c '"origin": \["design-spec"' $F # expect 1 +grep -c '"origin": "plan"' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'Coverage of the technical design' # expect 1 +``` + +- [ ] **Step 6: Commit** + +```bash +git add plugins/working-process/agents/plan-adversary.md +git commit -m "feat(working-process): read origin as a list and cover the design's parts" +``` + +--- + +### Task 16: The propagation auditor's class and the pointer pair + +**Files:** +- Modify: `plugins/working-process/agents/propagation-auditor.md:3` + (description) and its duty list + +**Interfaces:** +- Consumes: the pointers from Task 6. +- Produces: the pointer-pair check in the agent that gates every + expensive dispatch. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/agents/propagation-auditor.md +grep -c 'a spec or plan' $F # expect 1 +grep -c 'technical-design' $F # expect 0 +``` + +- [ ] **Step 2: Widen the class in the description** + +Replace `Mechanical propagation audit of a spec or plan before an +expensive dispatch` with `Mechanical propagation audit of a design spec, +a technical design or a plan before an expensive dispatch`. + +- [ ] **Step 3: Add the pointer-pair duty** + +Append to the duty list: + +``` +- **The pointer pair.** Where the document carries `technical-design:` + or `spec:`, check that both targets exist and that the two pointers + name each other. A pointer resolving to a missing file, or to a + document pointing elsewhere, is a hit — the pair identifies the + integrity audit's target, so a broken half silently narrows what the + next audit reads. +``` + +- [ ] **Step 4: Verify** + +Run: + +```bash +F=plugins/working-process/agents/propagation-auditor.md +grep -c 'a design spec, a technical design or a plan' $F # expect 1 +grep -c 'a spec or plan' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'The pointer pair' # expect 1 +``` + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/agents/propagation-auditor.md +git commit -m "feat(working-process): check the pointer pair in the propagation audit" +``` + +--- + +### Task 17: The process-status skill covers the new directory + +**Files:** +- Modify: `plugins/working-process/skills/process-status/SKILL.md:3` + +The skill's body owns nothing but running the published commands, so the +directory it covers lives in its `description:` alone. + +**Interfaces:** +- Consumes: the directory from Task 3. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +grep -c 'docs/specs and docs/plans' plugins/working-process/skills/process-status/SKILL.md +``` + +Expected: `1`. + +- [ ] **Step 2: Widen the description** + +Replace `a status pass over docs/specs and docs/plans` with +`a status pass over docs/specs, docs/technical-designs and docs/plans`. + +- [ ] **Step 3: Verify** + +Run: + +```bash +F=plugins/working-process/skills/process-status/SKILL.md +grep -c 'docs/specs, docs/technical-designs and docs/plans' $F # expect 1 +grep -c 'docs/specs and docs/plans' $F # expect 0 +``` + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/skills/process-status/SKILL.md +git commit -m "feat(working-process): cover technical designs in the status pass" +``` + +--- + +### Task 18: The plugin README + +**Files:** +- Modify: `plugins/working-process/README.md:6-9` (the flow line), `:18` + (the architect paraphrase), `:133` (the `integrity` field row) + +**Interfaces:** +- Consumes: every earlier task. This is the reader-facing summary and it + is edited last, so it describes what shipped rather than what was + planned. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +F=plugins/working-process/README.md +grep -c 'design document' $F # expect 1 +grep -c 'sha: ' $F # expect 1 +grep -n 'idea → brainstorming' $F +``` + +- [ ] **Step 2: Put the step in the flow line** + +Replace: + +``` +idea → brainstorming (spec) → grilling-session → architect review → +integrity audit → writing-plans (plan) → plan-adversary → +``` + +with: + +``` +idea → brainstorming (design spec) → grilling-session → architect +review → technical design (when the repository has code) → integrity +audit → writing-plans (plan) → plan-adversary → +``` + +- [ ] **Step 3: Rename the class in the architect paraphrase** + +Replace `or any design document dispatched standalone; verdict` with +`or any judged document dispatched standalone; verdict`. + +- [ ] **Step 4: Extend the `integrity` field row** + +Replace the `Values` cell `` ` (sha: )` `` with + +``` +` (sha: [; with: @])` +``` + +and append to its `Meaning` cell: + +``` +; where a design spec names a technical design the two are audited as one target and one stamp records the pair, on the design spec +``` + +- [ ] **Step 5: Verify, and sweep the plugin for the retired phrase** + +Run: + +```bash +grep -rc 'design document' plugins/working-process/ | grep -v ':0$' +``` + +Expected: no output — zero occurrences anywhere in the plugin. This is +the Global Constraint's closing check. + +- [ ] **Step 6: Commit** + +```bash +git add plugins/working-process/README.md +git commit -m "docs(working-process): put the technical design in the README flow" +``` + +--- + +### Task 19: Changelog and the dogfooding version + +**Files:** +- Modify: `plugins/working-process/CHANGELOG.md` +- Modify: `plugins/working-process/.claude-plugin/plugin.json` + +**Interfaces:** +- Consumes: every earlier task. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +head -8 plugins/working-process/CHANGELOG.md +grep '"version"' plugins/working-process/.claude-plugin/plugin.json +``` + +- [ ] **Step 2: Write the `## Unreleased` entry** + +Add at the top of the changelog, under its one-line preamble: + +```markdown +## Unreleased + +- New `technical-design` rule: a third document class between the design + spec and the plan, saying what a thing is made of — section skeleton, + the definitions of part, contract and state, and the closed `change` + value set. +- The consumption gate offers the document where the repository has + code, and runs in passes when the offer is accepted. +- A design spec and its technical design are audited as one target, with + one `integrity:` stamp on the design spec naming both. +- `origin` on a plan-adversary finding is a list of named documents; + `both` retires. +- The glossary mints `design spec`, `judged document` and `technical + design`; `design document` retires from every card and the README. +``` + +- [ ] **Step 3: Set the dogfooding version** + +The topic branch dogfoods this plugin, so it takes a `-dev` suffix +hanging off a version above what `develop` carries. Read the current +`develop` version first and bump the minor, then suffix: + +```bash +git show develop:plugins/working-process/.claude-plugin/plugin.json | grep version +``` + +Set `version` to `0.18.0-dev.1.technical-design-step`. Measured +2026-09-17: `develop` carries `0.17.0`, and no `-dev..` has ever been +minted in this repository, so this topic is the counter's first use and +takes `1`. Re-read `develop` before committing — if it moved, the +version hangs off whatever it carries now and the counter takes the next +integer above the highest one develop holds. + +The counter is its own dot-separated identifier, so semver compares it +numerically and successive dogfooding builds of one topic order +correctly: `dev.1.x` < `dev.2.x` < `dev.10.x`. Measured 2026-09-17 +against the `semver` reference implementation, alongside the two +spellings that fail — `dev.-` puts the number inside an +alphanumeric identifier and sorts `1 < 10 < 2`, while a bare +`dev.` sorts alphabetically by topic and carries no order at +all. This topic is the counter's first use, so it takes `1`. + +- [ ] **Step 4: Verify** + +Run: + +```bash +claude plugin validate plugins/working-process +grep -c '^## Unreleased$' plugins/working-process/CHANGELOG.md # expect 1 +``` + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/CHANGELOG.md plugins/working-process/.claude-plugin/plugin.json +git commit -m "chore(working-process): changelog and dogfooding version for the technical-design step" +``` + +--- + +## Deviations from the spec + +- **`process-artifacts.md` is path-scoped, not always-on.** The spec's + Parts table calls it an always-on rule. The file carries a `paths:` + block of five globs, so Task 3 adds the new directory to that block as + well as to the prose list. Without the glob the rule would not load + for a technical design, and the first-create question it owns would + never fire for the new directory. The spec's row is wrong about the + kind; it is right about what the part owns. + +- **The dogfooding version uses a counter the versioning rule does not + yet carry.** `.claude/rules/plugin-versioning.md` describes + `X.Y.Z-dev.` with no counter, while Task 19 mints + `0.18.0-dev.1.technical-design-step`. The counter is the developer's + decision of 2026-09-17 — `n` separates the successive dogfood releases + of successive topics and increments on `develop`, which is the owner + the counter lacked when an earlier attempt at it was declined. The + rule's amendment is deliberately not part of this branch, so the + divergence stands until that separate change lands. Task 19 follows + the decision, not the current rule text. + +## What this plan does not do + +- It creates no `docs/technical-designs/` directory in this repository. + The plugin ships the contract; this repo's product is prose and it + declines the offer. The first project to accept the offer creates the + directory, and the first-create question fires there. +- It writes no declaration surface. Where a project records the + declaration is deferred by the spec, so this repo keeps receiving the + offer at every consumption gate until that question is settled. That + is a known cost, recorded in the spec. +- It renames no existing spec to `-spec.md`, and adds no + `-technical-design` skill. Both are out of scope in the spec. diff --git a/docs/specs/2026-09-16-technical-design-step.md b/docs/specs/2026-09-16-technical-design-step.md new file mode 100644 index 0000000..c9be22f --- /dev/null +++ b/docs/specs/2026-09-16-technical-design-step.md @@ -0,0 +1,900 @@ +--- +ticket: none +date: 2026-09-16 +status: draft +grilled: 2026-09-16 +architect: concerns (resolved 2026-09-16) +integrity: 2026-09-17 (sha: 171b93f) +branch: feature/technical-design-step +base: develop +--- + +# Technical design — a third document between spec and plan + +A plan that designs the code and orders the work designs it badly. This +spec adds a third document class between the spec and the plan: a +**technical design**, answering what the thing is made of. The plugin +ships the contract; projects with code consume it. + +## Why + +Where no document names the structure, the plan becomes the place it is +decided. A plan that designs the code and orders the work at once locks +decomposition into task order: tasks arrive as feature slices, each one +appended to whatever file the previous slice touched, and nothing states +where a new responsibility belongs. Implementers working from a fresh +context cannot see their siblings, so they reproduce each other's local +rules. The structure that results is nobody's decision — it is the +residue of the order in which work happened to be scheduled, and it is +paid for after implementation, by whoever reads the code next. + +Neither existing document holds that decision whole. The architect +persona already reviews boundaries, interfaces and alternatives, so +structure is reviewed — but it is reviewed scattered through a body +written about behaviour, where no reader sees the division at once and +the spec's stamp quietly covers more than it was written to certify. A +plan that carries the decision instead becomes a codebase without a +compiler: prose prescribing names and interfaces that nothing checks +until implementation contradicts them. + +The failure is observable before it is expensive. Its tell is a project +whose specs name code identifiers scattered through prose while no +document lists them in one place, and whose plans map tasks to features +rather than to parts. + +## The document + +A technical design answers **what the thing is made of**, as the spec +answers what and why, and the plan answers in what order and verified +how. It lives in `docs/technical-designs/`, suffix +`-technical-design.md`. + +The boundary that separates it from the spec is not files against +non-files. It runs between **identity** and **placement**: the spec +names parts by the name their consumers use and gives each one a +responsibility; the technical design decides what naming a part leaves +open — where it sits, what shape the contract on its border takes, who +writes it and who reads it, what is state and where it is +authoritative. Where naming a part fixes its location, the name is the +path and the spec carries it. + +This repo ships that contract and does not execute it. Its own product +is prose, where a part's name is its path and behaviour is already the +shape of the contract, so a technical design here would duplicate two +existing documents. The consumers are projects with code. Their +vocabulary comes from any skill available in the session, whatever +published it — a standards plugin, or the project's own skills +directory. Where no such skill covers the technology, the document is +written from generic knowledge, best effort; that degradation path is +what makes the contract self-sufficient. + +## When it fires + +Repository markers govern by default; a declaration overrides them in +both directions. + +| state | behaviour | +|---|---| +| a declaration the project records | binds, no question asked | +| no declaration, but the repository carries a toolchain manifest | the offer fires at every consumption gate; nothing is written | +| no declaration, no marker | no offer | + +A marker is a file a language or platform toolchain reads to build, test +or deploy the repository — the file a developer would point at to say +what kind of project this is. The core defines that test and names no +closed list; a domain's own skill may name the markers of its +technology. + +A marker proves the repository holds code, never that a given change +needs decomposing, so narrowing the test to exclude a repository that +finds the offer unhelpful would buy the exclusion with a worse test. +The declaration is the signal and it outranks the marker; the marker only +raises the question. This repository is a case in point: its product is +prose, and `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` +and its CI workflows are files a platform reads to deploy it, so the +offer reaches it and it declines by declaration. Until the declaration +has a surface, that decline has nowhere to live and the offer stands — +which is the deferral below, made visible rather than argued away. + +Where the declaration lives is deferred. This spec fixes only what it +must do: a project states the answer once, the statement binds without +being re-asked, and a session can read it without being told where to +look. The surface itself belongs to the wider question of where a +project's standing process answers live — a `CLAUDE.md` note, or a file +it points at, is the shape available today and the one an implementation +may assume until that question is settled. + +The offer reads *open the `system-designer-session` skill and write the +technical design?* — never *dispatch*, which in this plugin names a +background agent — rather than *does the Parts section suffice?*: a +project that +has not been writing structural sections has none to compute from, so a +trigger derived from one would first have to force the section it then +measures. A session may **propose** writing the declaration and never +writes it unasked. + +When the offer is accepted, the **integrity audit waits** for the +document's dispositions to reach the spec: an audit is a fresh read of a +settled spec, and decomposition is the last step that can unsettle one. +This is the only ordering the gate has: the glossary's Consumption gate +entry names five things due there and fixes the order of this one pair +alone, leaving the rest unordered as they were. + +## Author and reviewer + +The author is the **`system-designer-session` skill**, in the main +thread. The +reviewer is the `architect` agent, whose card already admits "any design +document dispatched standalone", with the propagation audit gating the +dispatch as it gates every verdict dispatch. The plan-adversary stays on +plans. + +## Section skeleton + +| # | section | status | content | +|---|---|---|---| +| 1 | Scope | required | which spec and which of its behaviours this structure realizes; the system boundary | +| 2 | Parts | required | `part \| kind \| change \| placement \| owns \| deliberately excludes`; a retirement is a `change: retired` row | +| 3 | Contracts | required | `producer → consumer \| crosses \| guarantee \| breaks when \| check`; a flow crossing three or more parts in fixed order becomes a numbered sequence | +| 4 | State | conditional | `record \| home \| written by \| read by \| lifecycle \| visible through` | +| 5 | Failure and repetition | conditional | what happens when a step does not finish, runs twice, runs beside another, or finds the other side absent | +| 6 | Cuts not taken | conditional | one line per rejected division and its reason — the material the architect grades | +| 7–8 | Open questions, Review rounds | from the lifecycle rule | as a spec and a plan carry them today | + +Three sections are required because only those three are never empty, +on prose or on code. A conditional section that is absent is named in +one line — "not applicable, because…" — which the system-designer +persona already requires of itself. + +`placement` names where a part belongs in the system's structure — its +layer, module, package, service, or path, at the level the design +decision needs. `kind` answers what a part is; `placement` answers where +it belongs, and the two coincide only where a domain convention derives +one from the other. Record the location even then: a cell reading +`rules/` says the convention was applied deliberately, while an empty +one cannot be told apart from an omission. The table in this spec keeps +five columns — a design spec names identity, and placement is the +technical design's business. + +`tests` is not a section: it returns as the contract's `check` column. +The document says **what** verifies a contract, the plan says **when** +that runs. What leaves the plan is the interface prescription its tasks +carry today in their `**Interfaces:**` blocks. + +The plan-adversary's coverage dimension reads the `change` column: the +plan must cover every part marked `new`, `changed` or `retired`; one +part may span several tasks and one task several parts. A part carried +as `unchanged` context needs none. The column's values are +`new | changed | retired | unchanged` and the set is closed to domains — +it steers the process, so its meaning belongs to the core. A value +outside the set is an error the author corrects, never a part that +quietly escapes the coverage check. + +Its `origin` field becomes a list of named documents — `design-spec`, +`technical-design`, `implementation-plan` — and `both` retires with it. +The triage clause in `workflow.md` that reads the field retires `both` +with it, and its hold widens: a finding originating in *either* judged +document is held unless a written decision licenses the edit, since +changing a technical design from inside a plan review is as much a +design decision as changing a design spec. Each holds against its own +document's ledger, by the cross-document clause the lifecycle rule +already defines. +Today `both` means the plan and the spec, unambiguously because there +are two documents; with three it stops saying which two, and triage +branches on that value. A list answers the question triage actually +asks — is any origin the design spec, since editing one from inside a +plan review is design work — and it takes a new document class without a +new decision, where explicit pairs would grow by the square. + +A part has a resolvable identity, an explicit responsibility with its +exclusions, and a contract that lets its implementation change without +changing its consumers. What earns a part its own row is a design +decision: list it separately when leaving it out would make an +implementer decide a responsibility boundary, a contract between parts, +or the ownership of state, and keep inside a part the details that +merely carry out decisions already made. A public method is usually a +row in its part's contracts rather than a part of its own. + +Task boundaries never define part boundaries — the plan is organised +around the structure, not the structure around the plan. The grain comes +from the decisions the document exists to settle, which is the same +question the integrity audit asks of a spec: what would this text leave +an implementer to decide? It comes neither from the plan nor from a +domain vocabulary, which a project may not have. + +A contract is what a consumer may rely on without reading the producer's +body. Part and contract are two questions about one thing rather than +competing labels: a file may be a part, its signature a contract, and +the record it writes state. State is a record that outlives one +interaction and is read by a later one, with a home, a writer, a reader +and an end. + +## Domain extension + +The author takes the part vocabulary — kinds of part, placement rules, +kinds of contract, homes of state — from the **Domain expertise** duty +the personas already carry, which has them scan and load the domain's +skills. No new discovery convention ships with this change. + +A kind of part that no domain skill names is recorded in the document as +a **vocabulary gap** — a term this change mints, kept apart from the +review cascade's candidate gap, which is a graded and counted finding +with a reviewer for its producer. Accumulated vocabulary gaps are either +the specification of a `-technical-design` skill or the evidence +that none is needed; the decision is then made on evidence rather than +remembered. + +The core closes the three definitions, the table columns, the contract +entry's fields, and the four questions of the failure section. A domain +adds rows and values — outside the closed `change` column — never +columns, and never redefines what qualifies: +a kind of part it names must still have a resolvable identity, an +explicit responsibility, and a contract its implementation can change +behind. + +## The class and its three cards + +The lifecycle rule branches between documents in several places, and +every branch is really about the class rather than the filename. The +glossary mints **judged document** for the class a design spec and a +technical design share, against an implementation plan, and ADR 0004 +records why the class decides what a document owes. + +The class is deliberately not called a design document, the phrase three +agent cards use today, because both members carry "design" in their +names and the phrase would read as one of them rather than as both. All +three cards move to the minted term in the same change: + +> `plan-adversary`, today "Handed a spec (a design document, not an +> implementation plan)?" — reads "Handed a judged document (a design +> spec or a technical design, not an implementation plan)?", and the +> phrase later in the same section, "to a design document and would +> misfire as findings", takes the same term. + +> `architect`, today "a grilled spec (primary target) or any design +> document dispatched standalone" — reads "a grilled design spec +> (primary target) or any judged document dispatched standalone" + +> `integrity-auditor`, whose description opens "Judgment audit of a +> churned design document" — takes the minted term there too. + +The plugin README paraphrases the architect's card and carries the same +phrase, so it moves with them: five occurrences in four files, which is +the whole of the shipped text using the retired name. + +## What the integrity audit reads + +The auditor reads a spec as the whole basis an implementer builds from. +Once a spec defers structure to a technical design, that basis is two +documents, and a check of one against a remembered copy of the other has +no place to stop: an audit's dispositions edit both, each edit unsettles +the other's record, and the document that produced the change is the one +the spec calls its last producer of changes. + +So the pair is audited as one target. Before plan-writing, a single +integrity audit reads the design spec and its technical design whole, +and judges three things: each document against itself, the two against +each other, and whether the pair together suffices for an implementer. +It may report defects in either. Its card carries the four cases +explicitly: + +> A design spec with no technical design is a single target. A design +> spec that names one is audited together with it, both documents the +> target and neither the other's context. A plan, a permitted target on +> request, is the target alone and reads the documents its `spec:` and +> `technical-design:` name as context. A technical design is never a +> target alone: its audit is its design spec's. No other field carries +> context; +> `revises:` names a document whose design no longer matches what +> shipped, and reading an archive as authority is how a stale claim +> re-enters a live one. + +Its second lens, which today reads "has no other context: no +conversation, no author to ask", is narrowed to match: + +> Read the document again as a careful implementer who must build from +> this text, the documents audited with it, and the documents its +> `spec:` and `technical-design:` name, and has no other context: no +> conversation, no author to ask. + +A third edit follows from the first: the card's coverage tell reports +one line count and one highest line cited, which is how a partial read +exposes itself. A pair has two documents to read partially, so the tell +reports a pair of numbers per document. Without it the one risk peculiar +to a joint target — one document read whole, the other skimmed — is the +one thing the tell cannot show. + +These edits land together — any one alone leaves the card contradicting +itself, which is the defect class this auditor hunts. + +One stamp records the pair. It lives on the design spec, names both +documents and both body hashes, and the technical design carries no +`integrity:` of its own. That is not the design shedding a ceremony: it +owes the check like any judged document and discharges it jointly, which +ADR 0004's principle allows, since the class decides what a document +owes and not how many dispatches discharge it. + + integrity: 2026-09-16 (sha: abc1234; with: x-technical-design.md@def5678) + +Changing either document unsettles the pair, and the gate recomputes +both hashes to see it. The two pointers are what identify the pair, so +an added, removed or repointed `technical-design:` unsettles it too. + +**Ending the check.** The stamp lands as it does today, once the audit's +dispositions are applied, and this change asks for no confirming read +before it. Merging the pair into one target already removed the loop +that made one seem necessary: with a single stamp there is no second one +to invalidate, and the risk that dispositions leave text nobody re-read +is the risk every audited spec already carries. A declined offer behaves +as a declined offer does today, and this spec changes nothing about it. + +**One offer, and what it discharges.** The pair earns one offer at the +gate rather than one per document. Both documents keep their own chain +debt from a diff-scoped round, and the pair audit may discharge both at +once; the record says which debts it discharged, so the design's missing +stamp never reads as an outstanding one and never draws a second offer +for a check already done. The other arm stays per-document: a +full-document architect round discharges the debt of the document it +read. + +## The term + +With two judged documents in the process, the bare word *spec* stops +saying which one a sentence means, and the `origin` values are where the +three documents first stand side by side. The glossary mints **design +spec** as the name, with *spec* recorded as its short form, so the new +surfaces this change writes — the `origin` values, the technical-design +rule, ADR 0004 — carry the full name from their first line, and existing +sentences take it as they are touched. + +The frontmatter field keeps its name. `spec:` is read by plans, by the +lifecycle rule and by the Unfinished-work commands, which grep the +literal string; renaming a field others rely on is a propagation event +with its own audit, and it buys nothing this change needs. + +## Parts and boundaries + +| part | kind | change | owns | deliberately excludes | +|---|---|---|---|---| +| `technical-design.md` rule | path-scoped rule | new | the skeleton, the three definitions, what a technical design must contain | every lifecycle sentence, and the gate that decides the document is written at all | +| `spec-plan-lifecycle.md` | path-scoped rule | changed | `paths:` covering the new directory; the class name at each branch point; the new pointer's meaning, optionality and cardinality; `spec:` widening from plans to technical designs; the stamp's grammar and when it is stale against the pair it names; the gate's one fixed ordering and its single batch; the design's `status` moving with the plan's | the skeleton | +| `propagation-duties.md` | path-scoped rule | changed | `paths:` covering the new directory; the author's duty to check both pointers resolve and name each other | — | +| `process-artifacts.md` | always-on rule | changed | `docs/technical-designs/` among the Process directories | — | +| `ticket-frontmatter.md` | path-scoped rule | changed | the new directory's field set, which its per-directory list would otherwise cut to `ticket` + `date` | — | +| `process-status` skill | skill | changed | the directories its status pass covers | the commands themselves, which the lifecycle rule publishes | +| `workflow.md` | always-on rule | changed | the step's position, the gate's offer and what fires it, the authoring step that writes both pointers, and the triage clause that reads `origin` | the document's contents | +| `propagation-auditor` card | agent card | changed | the document class it audits, today "a spec or plan"; the pointer pair among what it checks | — | +| `integrity-auditor` card | agent card | changed | which documents are its target in each case, what it may read as context, the second lens's premise, and the coverage tell's shape for a joint target | which gaps are structural | +| `plan-adversary` card | agent card | changed | `origin` as a list of named documents; the coverage dimension | the document's contents | +| glossary | reference document | changed | `Part`, `Contract`, `Technical design`, `Judged document`, `Design spec`, `Vocabulary gap`, `Origin` rewritten as a list of named documents, and the `Consumption gate` entry's count and ordering | — | +| `architect` card | agent card | changed | the class name in what it accepts | its duties | +| ADR 0004 | reference document | new | why a class owes the ceremonies it owes | the ceremonies themselves | +| plugin README | reference document | changed | the class name where it paraphrases the architect's card; the `integrity:` value shape in the field table | the two pointer fields, which are not stamped | + +The new rule is path-scoped so the skeleton loads only while a technical +design is open — which is why it cannot own the offer that decides a +design gets written. That offer fires at a consumption gate, before any +such document exists and with the new rule therefore unloaded, so the +always-on `workflow.md` owns the trigger, the offer and the step; the +session reads the path-scoped rule once the offer is accepted and it +sits down to write. The new rule carries no lifecycle sentence either: +the lifecycle rule loads beside it through its own `paths:`, and a +second home for one fact is how the two drift. + +Frontmatter carries the chain: `spec:` on the technical design and +`technical-design:` on the plan, both pointing back at the document they +descend from, as every pointer in this process does today. + +The design spec gains a `technical-design:` pointer of its own, and that +one is new in kind: it names a document written later. The existing +pointers record where a document came from; this one gives a reader +holding the spec the structure that develops it, which is the move +anyone makes who sits down to plan, to review, or to change the thing a +month later. Without it the only answer is to search the designs for the +one naming this spec. + +Five rules keep the pair honest. The field is optional and appears once +the technical design exists, so its absence means there is none rather +than one nobody linked. The author who creates the design writes both +ends in the same turn — the design's `spec:` and the spec's +`technical-design:`. The propagation audit checks that both targets +exist and that the two pointers name each other. A plan keeps its own +`spec:` and `technical-design:`, and where it carries both, they must +agree with the pair. One technical design per design spec, until +something measured asks for more. + +Neither pointer joins the README's field table, which holds stamped +fields only — but `integrity:` is in that table, so its entry carries +the extended value shape. + +The pointer also opens a hole the body hash cannot see. `integrity:` +certifies the body below the frontmatter, so neither adding this field +nor editing the technical design moves the design spec's hash — yet both +change the basis its audit was given. The pair audit closes that hole; +the section on what the integrity audit reads carries the mechanism. + +## Lifecycle + +A technical design is a **judged document**, the class a design spec +already belongs to, against an implementation plan. It is not grilled: +grilling stress-tests a document's terminology against the project +glossary, and a technical design mints no terminology — its vocabulary +comes from the design spec, which was grilled, and from the domain's +own skills. The names it does mint are part and component names, and +those are checked against the domain's own skills where such a skill is +available — and where none is, the author names the kind, records a +vocabulary gap, and the architect round judges the boundary, which is +what it judges in any case. The lifecycle rule branches +between the two in several places, and the technical design takes the +spec's side of every one: the same fields, the same consumption-gate +semantics for a stale stamp, the same owner for an unresolved verdict, +and the same pair offered against chain debt, which declining +discharges. One mechanism it shares rather than owns: the integrity +check is due on it like any judged document, and one audit of the pair +discharges it, so the `integrity:` stamp sits on the design spec and +names both. ADR 0004 records why the class earns those +ceremonies — its next reader still judges it — so the next document +class answers by naming its reader rather than by renegotiating each +branch. + +Two consequences the rule must state rather than imply. The design's own +consumption gate is plan-writing, the same moment as the design spec's, +and one ordering binds the two: writing the design and applying its +review dispositions precedes the joint integrity audit, because the +design is the last producer of changes to the spec and certifying the +spec's body before the design's questions are answered stamps a body +about to change. The dependency reaches no further — the broader reading, +that every item the design owes precedes every item the spec owes, is +too wide for a pair the audit reads as one. + +The gate therefore runs in passes. The first pass decides whether a +technical design is written at all; where the offer is accepted, the +gate suspends until the design exists and its review dispositions are +applied, then resumes and collects the questions the documents' current +state raises. Answers already given stay binding unless the basis they +rested on changed. The gate asks its questions in one batch, as a review +round does, rather than firing each offer in turn — a batch being every +question answerable at that pass, never every question the gate will +ever ask. And the +design's `status` reaches `implemented` with the plan's, in the same +turn and by the same hand, since otherwise the rule freezing an +implemented document's body never reaches it. + +## What it costs + +Accepting a technical design raises the cost of authoring and of review, +and the raise is not spread across the process: it lands on the offer. +Declined, the step costs one question at a gate. Accepted, it adds an +architect loop over the new document; the integrity check stays one +audit, widened to read the pair rather than doubled. Verdicts stay +separate and the integrity check merges — the two operations move +differently, and saying so is what keeps a reader from counting +dispatches and concluding the process doubled. + +The tiers matter more than the count. Propagation gates run on the +cheapest available family, so the extra dispatches they add are not a +proportional cost; the architect rounds and the integrity audit run at +the most capable tier, and beside them sits the author's time and the +developer's, one batch of questions per round. What moves the total is +the number of rounds, the size of the documents, and how much a design +sends back to its design spec. + +The justification is that structure is settled before implementation +rather than during it, and that the rework implementation would +otherwise force does not happen. That is a hypothesis, not a measured +result: what this change has observed is the symptom of *not* having the +document, never the cost of keeping one. The balance is for validation +to settle. + +A change may skip the technical design when it sits inside boundaries +and contracts already settled, and the implementer is left to decide no +new responsibility split, no placement, and no ownership of state — the +same test that decides whether a part earns a row. + +Validation on a code project measures both sides: elapsed time, tokens +by model tier, rounds per document, and the interruptions that needed a +developer decision, against the structural decisions still discovered +during implementation and the rework they caused. One project carrying +the document is not a comparison, so the arm it is read against is named +before the measurement starts — a change in the same project that met +the skip criterion above, or that project's own history before the +document existed. + +Reviewing the design spec and the technical design as one dispatch would +cut some of this, and is deliberately not proposed here: the two +documents have different subjects, and merging their reviews would +change what a verdict certifies. + +## Out of scope + +- Renaming existing specs to `-spec.md`: 28 files plus every `spec:` and + `revises:` pointer, for a disambiguation the new directory already + provides. +- A background agent authoring the document; a `-technical-design` + skill; and the domain half — the kinds of part a technology recognizes + and where each one belongs — which the domain's own skills carry. + +## Known limitations + +1. **This repo cannot dogfood the document.** It ships a contract it + will not execute, which is the same condition under which the + system-designer persona went a year without a dispatch. Validation + must run on a code project's spec. +2. **Implementation discoveries have nowhere to land, and this document + will feel it first.** A document already `implemented` has a frozen + body, and a discovery that invalidates one of its sentences fits + neither a supersession pointer nor the ledger, which records review + dispositions rather than implementation events. That gap predates + this change and its answer is the same for two documents as for + three, so this spec does not invent one. It does move the weight: + what implementation overturns is usually structure — this class must + split, that boundary sits wrong — so the technical design becomes the + most frequent casualty of a gap nobody has closed. +3. **The author inherits the spec's blind spot.** A session writes the + document in the context that already assumed a decomposition, and + questioning that assumption is the document's purpose. Until a + fresh-context author exists, the only fresh readings of the document + are the architect round, the propagation gate, and the integrity + audit it shares with its design spec. + +## Open questions + +None outstanding; the decisions above were settled with the developer on +2026-09-16, informed by an architect and a system-designer consultation +whose records live under `.claude/working-process/technical-design-step/`. + +## Review rounds + +### 2026-09-16 — architect, fable 5.1, blocking (round 1, full-document) + +Full-document read. Five Important, four Minor. Record: +`.claude/working-process/technical-design-step/architect-round-1.md`. + +Correction, recorded at round 2: the four Minor lines below were written +`fixed` before their edits existed. F6, F7, F8 and F9 landed only after +round 2 found them still open, and they are re-listed in round 2's +dispositions. The lines stand as written so the error is legible. + +- fixed 2026-09-16 — [Important] F1: the technical design's Parts + section carries the same five columns as the design spec's, so the + identity/placement boundary the spec draws has nowhere to land; + ruling: 2026-09-16; the technical design's table gains a `placement` + column, the design spec's keeps five, and the clause parts `kind` + (what a part is) from `placement` (where it belongs), requiring the + cell to be filled even where a domain convention derives it. +- fixed 2026-09-16 — [Important] F2: the gate's offer has two owners, + and the path-scoped rule cannot fire it, loading only while a + technical design is open while the offer fires before any design + exists; license: the document's own statement that the new rule is + path-scoped, read together with `workflow.md` owning the step; the + trigger, the offer and the step belong to the always-on rule, and the + path-scoped rule is read once the offer is accepted. +- fixed 2026-09-16 — [Important] F3: the new frontmatter fields and the + date comparison the gate was to run had no owning row; license: + applying the ownership the document already declares; field meaning, + optionality, cardinality and staleness go to the lifecycle rule, + writing both pointers to the authoring step in `workflow.md`, and + checking the pair to the propagation auditor and its author-facing + rule, with the misplaced exclusion returned to its own row. +- fixed 2026-09-16 — [Important] F4: a day-resolution comparison passes + same-day edits, and in this process whole loops fit inside one day, so + the mechanism misses its dominant case; ruling: 2026-09-16; the date + comparison is withdrawn — no precision of dates catches an edit that + changes a design without opening a round — and the audit stamp records + the identity and body hash of each document read as context, with the + gate recomputing both. +- fixed 2026-09-16 — [Important] F5: the design takes the spec's side at + every branch, so a code project pays two architect loops and two + top-tier integrity audits per feature, and the spec named neither the + cost nor why the design's own audit is not redundant; ruling: + 2026-09-16; the cost is recorded rather than argued away, with the + tier distinction that makes the count misleading, the benefit stated + as a hypothesis validation must settle, a criterion for skipping the + document, and what validation measures on both sides. Merging the two + reviews stays out of scope. +- fixed 2026-09-16 — [Minor] F6: the spec falsifies the glossary's + Consumption gate entry — a fifth item and one ordering — while saying + it leaves the entry standing; license: the entry is this change's own + artifact; the entry is updated. +- fixed 2026-09-16 — [Minor] F7: "session" names a skill, which the + glossary's Session skill entry bans; license: that `_Avoid_` ban; the + `system-designer-session` skill is named. +- fixed 2026-09-16 — [Minor] F8: the table omits two dispatched + consumers of the two-directory enumeration, one of which assigns field + sets per directory; license: an enumerated missed consumer, decided by + its own derivation; the rows are added. +- fixed 2026-09-16 — [Minor] F9: "the verified-interfaces table leaving + the plan" names an artifact no plan convention defines; license: the + named artifact does not exist; plans carry per-task `**Interfaces:**` + blocks, and the sentence names those. + +- signal 2026-09-16 — the reviewer expects the next round to close on + `concerns` or `LGTM`, judging the repairs local rather than a + redesign. + +### 2026-09-16 — architect, fable 5.1, blocking (round 2, diff-scoped) + +Diff-scoped over round one's fix wave. Two Important, seven Minor. +Record: +`.claude/working-process/technical-design-step/architect-round-2.md`. + +- fixed 2026-09-16 — [Important] F10: the three clauses guard the pair + against invalidation by the stamp, never by the audit's dispositions, + which on the spec's dominant path re-arm the offer without end; + ruling: 2026-09-16; the pair is audited as one target. + One integrity audit before plan-writing reads the design spec and its + technical design whole, judging each against itself, the two against + each other, and the pair's sufficiency; one stamp on the design spec + names both documents and both hashes, and the design carries no + `integrity:` of its own. The check ends by confirmation — the auditor + reads what the dispositions changed and what it did to both, and the + stamp waits for that confirmation — or by a recorded waiver, which + leaves no current stamp, never refreshes an older one, and closes this + passage through the gate alone. The pair earns one offer, and the + record says which debts it discharged. Superseded by this ruling: + the context-reading clause of round one's B1, and the design's own + `integrity:` field. What the original finding said, for the record: + the context record's three clauses guarded against + invalidation by stamping, which never moved a body hash; what moves it + is an audit's dispositions, and the spec makes the technical design + the last producer of changes to its design spec, so the mechanism has + no terminator on its own main path. The reviewer offers two shapes and + the choice is the developer's: an offer whose decline is recorded like + chain debt, or one audit of the pair with no `integrity:` on the + design. No license for either. +- fixed 2026-09-16 — [Important] F11: the set of context documents, on + which both the stamp's content and clause 2's "insufficient" depend, + is not fixed by a closed field list, and the spec calls `revises:` a + pointer too; license: the lifecycle rule's own definition of + `revises:`, which names a document whose design no longer matches what + shipped; context is now the closed pair `spec:` and + `technical-design:`, with `revises:` excluded in both places. +- fixed 2026-09-16 — [Minor] F12: extending the `integrity:` grammar + changes a stamped field the README's field table documents, and the + exclusion was written for pointers; license: an enumerated consumer; + `integrity:` carries the extended value shape in the field table and + the exclusion narrows to the two pointers. +- fixed 2026-09-16 — [Minor] F13: the gate's batched questions and the + design's `status` coupling are lifecycle sentences with no owning + cell; license: applying the ownership already declared; both join the + lifecycle rule's row. +- fixed 2026-09-16 — [Minor] F14: validation measures both sides on one + project that has the document, so the avoided-rework hypothesis has no + comparison arm, and "what this change has measured" overstates a + symptom; license: the document's own claim; "measured" became + "observed", and validation names the arm it is read against. +- fixed 2026-09-16 — [Minor] F15, F16, F17, F18: the four round-one + Minors, re-raised because their `fixed` lines were written before the + edits existed; license: the licenses of F7, F6, F8 and F9 in round + one, unchanged; all four landed for real this time and were verified + on disk — the skill named rather than "session", the glossary's + Consumption gate entry at five items with the one fixed ordering, rows + for `ticket-frontmatter.md` and the `process-status` skill, and the + interface prescription named as the `**Interfaces:**` blocks that + exist. + +- signal 2026-09-16 — a further round is unearned until the developer + settles F10, since a round before that choice repeats the finding at + the most expensive tier; after it, a diff-scoped round three over the + context-record paragraph should close on `LGTM` or `concerns`. + +The propagation gate before round three, under this heading because a +gate mints none of its own: + +- hit dismissed 2026-09-16 — five occurrences of "design document" still + stand in the shipped cards and README, against the prescribed + replacements; counter: the prescriptions are promises, not landed + changes. This spec has no plan yet, and editing shipped plugin content + before one exists would put implementation ahead of the process that + reviews it. The anchors the edits need all exist, which is what the + duty checks for a promised change. +- hit fixed 2026-09-16 — the superseded context-record passage survived + its own replacement, leaving two accounts of one mechanism; the block + is deleted and the pair audit is the only account. +- hit fixed 2026-09-16 — the stamp's worked example appeared in two + shapes, `with:` and `context:`; the surviving example is the pair form. +- hit fixed 2026-09-16 — the claim about the glossary's Consumption gate + entry described the entry as it stood before this change's own edit to + it. + +### 2026-09-16 — architect, fable 5.1, concerns (round 3, diff-scoped) + +Diff-scoped over round two's fix wave. The prescribed tier refused the +dispatch on a spend cap, so the round ran on Opus 5, one family below, +and again on Fable 5.1 once the cap reset — the same brief, the same +document byte for byte, no edit between them. Both records stand: +`architect-round-3.md` (Opus 5, `blocking`, four Important and five +Minor) and `architect-round-3-2026-09-16-15-21-40.md` (Fable 5.1, +`concerns`, one Important and six Minor). The stamped verdict is the +prescribed tier's, so no `architect-fallback:` field is owed: the field +records that the stamped verdict was produced below tier, and this one +was not. The below-tier run is recorded here and in its own dispatch +record, and nothing is re-reviewed on its account. + +What the two runs shared: the stamping moment, the waiver with no home, +the second lens reopening a closed context set, the missing fourth case, +the duplicated README paragraph, and the judgement that ADR 0004's added +sentence keeps its principle. Opus alone found the coverage tell left +counting one document for a target that had become two. Fable alone +found the retired word "context" surviving in the glossary and the +table, the cost correction contradicting itself two sentences on, and — +the reading that decided the wave — that the confirming read was a +second mechanism for a failure the merge to one target had already +removed. + +The two runs number their findings independently and the numbers +collide, so every line below names its run. + +- fixed 2026-09-16 — [Important] Fable F19, Opus F19: the confirming + read is a second mechanism for a failure the merge to one target had + already removed; ruling: 2026-09-16, variant (a); the confirming read + is withdrawn, the stamp lands once dispositions are applied as it does + for every audited document today, and the spec says so rather than + raising a standard it does not also raise for a lone design spec. +- fixed 2026-09-16 — [Minor] Fable F21, F22 and [Important] Opus F21: + the card's second lens and its case list reopened a context set the + merge had closed; license: the card's own closed set and the + document's own statement that a technical design carries no + `integrity:`; the second lens repeats the pair, and a technical design + is never a target alone. +- fixed 2026-09-16 — [Minor] Fable F23: the cost correction contradicts + itself two sentences on; license: the document's own words; the + contradiction left with the mechanism that caused it. +- fixed 2026-09-16 — [Minor] Fable F24, Opus F23: ADR 0004's added + sentence asserts its licence rather than deriving it; license: ADR + 0004's own principle; the sentence now derives its licence from the + reader. +- fixed 2026-09-16 — [Minor] Fable F25: the retired word "context" + survives in the glossary and the table; license: the document's own + words; "context" gives way to the pair wherever the design is a + co-target, in the spec and in the glossary's Consumption gate entry. +- fixed 2026-09-16 — [Important] Opus F22: the coverage tell still + counts one document for a target that had become two; license: the + tell exists to expose a partial read, and a joint target doubles what + can be read partially; the card reports a pair of numbers per + document. +- fixed 2026-09-16 — [Minor] Opus F27: the README field-table paragraph + stands twice, verbatim, from the F12 wave; license: its own + derivation; the duplicate is deleted. +- fixed 2026-09-16 — [Minor] Opus F26: the `integrity-auditor` row's + `owns` cell lacks the target selection the spec prescribes to the + card; license: an enumerated consumer, the class of F13 and F17; the + cell names which documents are its target in each case. Recorded + 2026-09-17: the edit landed in this wave and no line was written for + it at the time. +- declined 2026-09-16 — [Minor] Fable F20, [Important] Opus F20: the + waiver has no home. A declined audit offer has had no record since + before this change, and naming one here would widen a spec already + touching fourteen parts; ruling: 2026-09-16; the spec says only that a + declined offer behaves as it does today, and the gap and its + consequence for chain debt's promised trace are recorded in the + developer's memory store. Corrected 2026-09-17: the line read "twelve + parts" and the table carried fourteen when it was written. The count + is corrected; the decision is not reopened. + +- signal 2026-09-16 — Fable judges a fourth round unearned under variant + (a) and recommends closing the loop at `concerns` once these land. + This is the cap round, so a fourth is the developer's to order. + +### Loop closed — 2026-09-16 + +Three rounds, one of them run twice on two model families. Every finding +of every round is disposed: fixed under a license or a ruling, except +one declined with its reason recorded and its gap parked in the +developer's memory store. Correction, recorded at the integrity audit of +2026-09-17: this sentence was not true when it was written. Two of the +below-tier run's findings carried no line — Opus F26, whose edit had +landed unrecorded, and Opus F24, which nothing had addressed and which +the audit re-found. Both are disposed now, F26 in round three and F24 in +the audit's own block. The prescribed tier's last verdict was +`concerns`, and it is resolved here rather than by a fourth round — +Fable's own stop signal judged a fourth unearned under the ruling taken, +and round three was the cap in any case, so a further round would have +been the developer's to order. + +What remains for the consumption gate, before any plan is written: an +integrity audit of this document. It has no technical design and will +not have one — this repository ships the contract and does not execute +it — so the audit takes the first of the four cases this spec defines, +a design spec as a single target. + +### Integrity audit — 2026-09-17, integrity-auditor, fable 5.1 + +The consumption gate's audit, single target, fresh context, dispatched at +the prescribed tier after a `CLEAN` propagation gate. Eleven defects and +sixteen implementer questions; the coverage tell reported the whole +document. Every citation was checked against what it names before any +line below was written. The eleven are disposed in thirteen lines: two +defects share one line, one defect takes two, one implementer question +was promoted to a defect, and one round-three finding the ledger had +never recorded is disposed here. + +- fixed 2026-09-17 — the class section counted two agent cards where its + own body quotes three and ADR 0004 says three plus the README; + license: the document's own enumeration; the count reads three and + "Both cards" reads "All three cards". +- fixed 2026-09-17 — the integrity-auditor's prescribed block was + introduced as three cases and carries four since round three added + one; license: the ledger's own record of that addition; both mentions + read four. +- fixed 2026-09-17 — "Four rules keep the pair honest" was followed by + five; license: the enumeration itself; the count reads five. +- fixed 2026-09-17 — Known limitation 3 named the architect round and + the propagation gate as the only fresh readings, denying the pair + audit the fresh-read status the body grants it; license: the + document's own statement that an audit is a fresh read; the audit + joins the list. +- fixed 2026-09-17 — the Parts table's glossary row omitted `Origin`, + which the body prescribes rewriting; license: the body's own + prescription and the entry on disk; the row names it. +- fixed 2026-09-17 — no row owned `spec:` widening from plans to + technical designs; license: the same; the lifecycle row names it. +- fixed 2026-09-17 — the round headings took none of the grammar the + lifecycle rule prescribes, so neither the round cap's ordinal + derivation nor the scope recovery could read them; license: the + lifecycle rule's heading grammar; the three round headings take the + prescribed shape, and round one is marked `full-document`. +- fixed 2026-09-17 — the frontmatter carried `architect-fallback: opus 5 + (degraded 2026-09-16)` and a sentence redefining the field as a record + that a below-tier round ran; license: the lifecycle rule, where the + field records that the stamped verdict was produced below tier and a + dispatch at the prescribed tier gets no field. The stamped verdict is + Fable's. The field is removed and the sentence says why none is owed. +- fixed 2026-09-17 — the gate's one fixed ordering cannot run on a first + pass: the offer to write the technical design is itself an item of the + design spec's gate, so the design owes no item until that offer is + accepted, and the document never said the gate re-opens; ruling: + 2026-09-17; the dependency narrows to what its own rationale supports + — writing the design and applying its review dispositions precedes the + joint integrity audit — and the gate runs in passes, suspending on an + accepted offer and resuming once the design exists, with answers + already given staying binding unless their basis changed and a batch + meaning every question answerable at that pass. +- fixed 2026-09-17 — [Minor] Opus F24, unrecorded in round three: the + ordering does not say where the jointly owed item sits; ruling: + 2026-09-17; the same narrowing places it, since the joint audit is now + the thing the ordering is about. +- fixed 2026-09-17 — a domain may add `change` values while the + plan-coverage rule reads the column as a closed set, so a + domain-added value escapes coverage; ruling: 2026-09-17; the values + are `new | changed | retired | unchanged`, the set is closed to + domains because the column steers the process, a value outside it is + an error the author corrects, and the extension clause excepts the + column. +- fixed 2026-09-17 — the marker test classifies this repository as + carrying markers, against the document's claim that a repository whose + product is prose carries none; ruling: 2026-09-17; the claim is + removed rather than the test narrowed — a marker proves the repository + holds code, never that a change needs decomposing, so narrowing the + test to exclude one repository would buy the exclusion with a worse + test. The declaration outranks the marker, this repository is named as + a detected case that declines by declaration, and the missing + declaration surface is made visible rather than argued away. +- fixed 2026-09-17 — the ledger's disposition lines took none of the + grammar the lifecycle rule prescribes, and the gate block minted a + heading the rule forbids; ruling: 2026-09-17; every line is rewritten + to the prescribed shapes with severities recovered from the three + dispatch records rather than re-graded, real dates and authorizers + kept, the round-one correction note kept, the gate's lines moved under + round two's heading in the `hit` shapes, and the two runs of round + three disambiguated by run since their numbering collides. + +The propagation gate over this wave, twice: three located hits, then +`CLEAN`. + +- hit fixed 2026-09-17 — the section heading still read "its two cards" + after the body was corrected to three; the heading reads three. +- hit fixed 2026-09-17 — the declined line's "twelve parts" against a + table of fourteen; the count is corrected with a dated note and the + decision left alone. +- hit fixed 2026-09-17 — the audit block's header counted eleven defects + against thirteen disposition lines; the header reconciles them. + +These lines sit here rather than under round three's heading, where the +gate-line rule would put them, because they belong to the audit's wave +and not to any round. That the rule has no place for them is a gap this +spec should answer: it designs a document class whose audits produce +dispositions, and the lifecycle rule says only that an audit's +dispositions land as edits plus the stamp, which leaves a `held` line +and a gate line homeless. Recorded here, not fixed here — the spec's +scope is the class, not the ledger's grammar. From 0097c524e75fc085c6a47077f81684da6888daaa Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Thu, 17 Sep 2026 10:31:52 +0200 Subject: [PATCH 002/143] docs: dispose the plan-adversary round one findings --- docs/domain/glossary.md | 7 +- .../plans/2026-09-17-technical-design-step.md | 692 +++++++++++++++--- 2 files changed, 602 insertions(+), 97 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index faa0ae4..6800921 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -315,8 +315,11 @@ re-review offer on a fallback-recorded verdict, the integrity audit offered when a document's body, or that of the document audited with it, no longer matches its stamp, the pair question a diff-scoped chain earns, the chain debt itself, and the offer of a technical design. Only one ordering is fixed: -everything a technical design owes settles before anything its design -spec owes, and the questions reach the developer in one batch. +writing the technical design and applying its review dispositions +precede the joint integrity audit. The gate runs in passes where that +offer is accepted, and each pass reaches the developer in one batch — +every question answerable at that pass, never every question the gate +will ever ask. _Avoid_: usage point **Unfinished-work list**: diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index c523976..b97f46c 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2,6 +2,7 @@ ticket: none date: 2026-09-17 status: draft +adversary: blocking spec: ../specs/2026-09-16-technical-design-step.md branch: feature/technical-design-step base: develop @@ -72,9 +73,10 @@ verification. ### Task 1: Verify the two documents already on disk The spec's Parts table lists the glossary and ADR 0004 as parts of this -change. Both landed while the spec was authored and are uncommitted in -the working tree. This task verifies them rather than writing them, so a -later task never re-mints a term that already exists. +change. Both landed while the spec was authored and are already +committed, in `dc85b9b` together with the spec and this plan. This task +verifies them rather than writing them, so a later task never re-mints a +term that already exists, and it commits nothing. **Files:** - Verify: `docs/domain/glossary.md` @@ -124,25 +126,28 @@ tr -s '[:space:]' ' ' < docs/domain/adr/0004-ceremonies-follow-the-reader.md | \ Expected: `1` or more. -- [ ] **Step 4: Confirm the Consumption gate entry names five items** +- [ ] **Step 4: Confirm the gate entry carries the narrow ordering** -Run: +The entry must state the one dependency the spec settled, not the broad +one an earlier draft carried. Run: ```bash grep -A12 '^\*\*Consumption gate\*\*:' docs/domain/glossary.md | \ - tr -s '[:space:]' ' ' + tr -s '[:space:]' ' ' | grep -c 'precede the joint integrity audit' +grep -c 'settles before anything' docs/domain/glossary.md ``` -Expected: five items listed, with the ordering of one pair fixed and the -rest unordered. This is the entry Task 10 points the gate offer at. +Expected: `1`, then `0`. The second is the deletion assertion: the broad +ordering — everything the design owes before anything its design spec +owes — was withdrawn by the spec's integrity ruling of 2026-09-17, and a +surviving copy would send Task 8's text into contradiction with a +canonical term. -- [ ] **Step 5: Commit** +- [ ] **Step 5: Nothing to commit** -```bash -git add docs/domain/glossary.md docs/domain/adr/0004-ceremonies-follow-the-reader.md \ - docs/specs/2026-09-16-technical-design-step.md docs/plans/2026-09-17-technical-design-step.md -git commit -m "docs: spec and plan the technical-design step, with its glossary terms and ADR" -``` +The four documents are already in `dc85b9b`. This task writes nothing, +so it has no commit of its own; the wave that follows commits per round +on `feature/technical-design-step.docs`. --- @@ -442,13 +447,18 @@ git commit -m "feat(working-process): give technical designs the process field s **Files:** - Modify: `plugins/working-process/rules/spec-plan-lifecycle.md:1-6` - (`paths:`), `:481-495` (Lifecycle offers), `:504-509` (the - consumption-gate backstop) + (`paths:`), `:7-10` (title and opener), `:73-75` (the re-review + offer's gate), `:100-104` (the `integrity:` recomputation), `:229-231` + (the cross-document clause), `:447-448` (Chain debt's owner leg), + `:481-495` (Lifecycle offers), `:504-509` (the consumption-gate + backstop) **Interfaces:** - Consumes: `Judged document` from Task 1. -- Produces: the rule loading for `docs/technical-designs/**`, and every - branch reading the class rather than a filename. Tasks 6, 7 and 8 edit +- Produces: the rule loading for `docs/technical-designs/**`, and the + six branch sentences listed in Step 3 reading the class rather than a + filename. Six, not "every" — the blanket claim hid which sentences + were covered and which were merely unvisited. Tasks 6, 7 and 8 edit other sections of this file and must not touch these lines. - [ ] **Step 1: Measure the before state** @@ -470,9 +480,42 @@ In the frontmatter, after ` - "docs/specs/**"`, insert: - "docs/technical-designs/**" ``` -- [ ] **Step 3: Name the class in the offers paragraph** +- [ ] **Step 3: Name the class at all six branch sentences** -Replace: +The rule branches on the document in six places. Two are the offers +paragraph and the backstop below; the other four are the title and +opener, the re-review offer's gate, the `integrity:` recomputation, and +the Chain debt entry's owner leg. The last of these is read verbatim by +the `process-status` skill, which is told never to supply an owner from +its own knowledge of the process, so a stale owner there misinstructs a +component that cannot correct it. + +First, the title and opener. Replace: + +``` +# Specs and plans — frontmatter and lifecycle +``` + +with: + +``` +# Judged documents and plans — frontmatter and lifecycle +``` + +and replace: + +``` +Specs live in `docs/specs/`, plans in `docs/plans/`. Both open with a +``` + +with: + +``` +Design specs live in `docs/specs/`, technical designs in +`docs/technical-designs/`, plans in `docs/plans/`. All three open with a +``` + +Then the offers paragraph. Replace: ``` architect-review a grilled spec (architect agent dispatch); @@ -516,7 +559,78 @@ document passes to plan-writing, nor a plan to implementation, while `held` lines stay open — an LGTM can leave the frontmatter clean while a ``` -- [ ] **Step 5: State that a technical design is not grilled** +- [ ] **Step 5: Name the class at the three remaining branches** + +Replace: + +``` + gate — before plan-writing for a spec, before implementation for a + plan. Accepted: a fresh round at the prescribed tier replaces the +``` + +with: + +``` + gate — before plan-writing for a judged document, before + implementation for a plan. Accepted: a fresh round at the prescribed + tier replaces the +``` + +Replace: + +``` +- A spec's consumption gate owns the recomputation: before plan-writing it +``` + +with: + +``` +- A judged document's consumption gate owns the recomputation: before + plan-writing it +``` + +and, in the same bullet: + +``` + are spec-gate semantics — on a plan, a permitted target on explicit +``` + +becomes + +``` + are judged-document semantics — on a plan, a permitted target on + explicit +``` + +The cross-document clause cites a value the reviewer will no longer +emit. Replace: + +``` +reviewed one — a plan review's finding carrying `origin: spec` is the +``` + +with: + +``` +reviewed one — a plan review's finding whose `origin` names a judged +document is the +``` + +Finally the Chain debt entry's owner leg, which `process-status` reads +literally. Replace: + +``` + Owner: for a spec, the consumption gate's pair offer; for a plan, the +``` + +with: + +``` + Owner: for a judged document, the consumption gate's pair offer; for a + plan, the +``` + +- [ ] **Step 6: State that a technical design is not grilled** After the offers paragraph, add: @@ -531,9 +645,17 @@ its design spec, which was grilled, and from the domain's own skills. The names it does mint are part and component names, checked against those skills where one exists and recorded as a vocabulary gap where none does. + +One mechanism it shares rather than owns: the integrity check is due on +it like any judged document, and one audit of the pair discharges it, so +the `integrity:` stamp sits on the design spec and names both. ``` -- [ ] **Step 6: Verify** +Without that last sentence this block and Task 7's contradict each +other four hundred lines apart — "the same consumption-gate semantics +for a stale stamp" against "carries no `integrity:` of its own". + +- [ ] **Step 7: Verify** Run: @@ -542,9 +664,20 @@ F=plugins/working-process/rules/spec-plan-lifecycle.md grep -c '"docs/technical-designs/\*\*"' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'no judged document passes to plan-writing' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'It is not grilled' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'One mechanism it shares rather than owns' # expect 1 +grep -c '^# Judged documents and plans' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'for a judged document, the consumption' # expect 1 +grep -c 'for a spec, the consumption' $F # expect 0 +grep -c 'spec-gate semantics' $F # expect 0 +grep -c 'plan-writing for a spec' $F # expect 0 +grep -c 'origin: spec' $F # expect 0 ``` -- [ ] **Step 7: Commit** +The last three are deletion assertions: each names a branch sentence +this task replaces, and a survivor would leave the rule saying both +things at once. + +- [ ] **Step 8: Commit** ```bash git add plugins/working-process/rules/spec-plan-lifecycle.md @@ -607,7 +740,23 @@ After the field notes, add: the pair. One technical design per design spec. ``` -- [ ] **Step 4: Verify** +- [ ] **Step 4: Say what the pointer does to a plan's interface blocks** + +A plan naming a technical design must not redefine what that design +already defines. Add after the five rules: + +``` +- Where a plan's `technical-design:` names a document, that document + defines the interfaces. The plan's `**Interfaces:**` blocks reference + the contracts it defines and say which part of one each task + implements or changes; they do not independently redefine those + contracts. Where a plan names no technical design, the existing plan + convention stands unchanged. This binds how the blocks are filled and + changes no plan template — the template belongs to the tool that + writes plans. +``` + +- [ ] **Step 5: Verify** Run: @@ -616,13 +765,14 @@ F=plugins/working-process/rules/spec-plan-lifecycle.md grep -c '^technical-design: ' $F # expect 1 grep -c 'plans and technical designs' $F # expect 1 grep -c 'plans only' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'they do not independently redefine those contracts' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'One technical design per design spec' # expect 1 ``` The third check is the deletion assertion: the old `plans only` comment must be gone, not merely contradicted further down. -- [ ] **Step 5: Commit** +- [ ] **Step 6: Commit** ```bash git add plugins/working-process/rules/spec-plan-lifecycle.md @@ -670,8 +820,9 @@ integrity: (sha: [; with: @]) # optio - [ ] **Step 3: Write the pair clause** -After the paragraph ending `so writing the stamp never invalidates what -it stamps.`, add: +The bullet does not end where that sentence does — it runs twelve more +lines, through the hash command the pair clause depends on. Anchor on +the bullet's real last line, `a typo fix included.`, and add after it: ``` A design spec that names a technical design is audited with it as one @@ -687,7 +838,24 @@ it stamps.`, add: unsettles it too. ``` -- [ ] **Step 4: Verify both directions** +- [ ] **Step 4: Widen the discharge paths to a pair** + +The three discharge paths describe annotating one document. Replace: + +``` +applied and before the `integrity:` stamp; and any later full-document +``` + +with: + +``` +applied and before the `integrity:` stamp — and where that audit read a +pair, it discharges the debt of both documents it read, annotating each +document's own unannotated diff-scoped `LGTM` headings; and any later +full-document +``` + +- [ ] **Step 5: Verify both directions** Run: @@ -696,9 +864,10 @@ F=plugins/working-process/rules/spec-plan-lifecycle.md grep -c 'with: @' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'carries no `integrity:` of its own' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'recomputes both hashes' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'both documents it read' # expect 1 ``` -- [ ] **Step 5: Commit** +- [ ] **Step 6: Commit** ```bash git add plugins/working-process/rules/spec-plan-lifecycle.md @@ -808,30 +977,67 @@ In the frontmatter, after ` - "docs/specs/**"`, insert: - "docs/technical-designs/**" ``` -- [ ] **Step 3: Add the pointer-pair duty** +- [ ] **Step 3: Add the pointer-pair duty as a table row** -Append to the duty list: +The duties are a table keyed by the edit, not a bullet list. Append a +row after the `copied a citation out of a review report` row: ``` -- **The pointer pair resolves both ways.** Where a design spec carries - `technical-design:` or a technical design carries `spec:`, both target - files exist and each pointer names the document that names it. A - pointer resolving to a missing file, or to a document pointing - somewhere else, is a hit: the pair is what identifies the audit's - target, so a broken half silently narrows what the next audit reads. +| added or repointed a `spec:` or `technical-design:` pointer | both targets, and that each pointer names the document that names it; on a plan, that its `spec:` and `technical-design:` name a design spec and the design that design spec names | 9 | ``` -- [ ] **Step 4: Verify** +- [ ] **Step 4: Re-derive the two counters the row invalidates** + +The rule opens by counting its own duties and the edits that key them. +Replace: + +``` +Before a document goes to an expensive reader, eight duties fall due, +keyed by the nine edits that trigger them — one duty answers two +different edits. +``` + +with: + +``` +Before a document goes to an expensive reader, nine duties fall due, +keyed by the ten edits that trigger them — one duty answers two +different edits. +``` + +and replace: + +``` +When the `propagation-auditor` agent is available it walks the same +eight as a gate, and its card is their definition and keeps the +``` + +with: + +``` +When the `propagation-auditor` agent is available it walks the same +nine as a gate, and its card is their definition and keeps the +``` + +- [ ] **Step 5: Verify, both counters against what they count** Run: ```bash F=plugins/working-process/rules/propagation-duties.md grep -c '"docs/technical-designs/\*\*"' $F # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'The pointer pair resolves both ways' # expect 1 +grep -c '^| ' $F # expect 12: header, separator, ten edit rows +grep -c 'nine duties fall due' $F # expect 1 +grep -c 'keyed by the ten edits' $F # expect 1 +grep -c 'walks the same nine' $F # expect 1 +grep -c 'eight duties\|the nine edits\|same eight' $F # expect 0 ``` -- [ ] **Step 5: Commit** +The last line is the deletion assertion, and it is the one that matters: +the old counters read as true prose and nothing but a recount catches +them. + +- [ ] **Step 6: Commit** ```bash git add plugins/working-process/rules/propagation-duties.md @@ -866,39 +1072,62 @@ sed -n '36,38p' $F Expected: `0`, and step 4 opening `4. **Spec → plan.** At the spec's consumption gate, before the plan is`. -- [ ] **Step 2: Insert the new step before the gate's other business** +- [ ] **Step 2: Insert the new offer at a byte-exact anchor** -Immediately after the line +The insertion point is byte-exact, not positional: an earlier draft said +"after the step's existing offer sentence", and that sentence ends +mid-line, so the block could land before or after the pair-form sentence +that also says "the offer". Replace: ``` -4. **Spec → plan.** At the spec's consumption gate, before the plan is + absent. The brief ``` -the step continues as it does today. Insert, as the step's first -paragraph after its existing offer sentence, a new bullet under it: +with: ``` + absent. + At the same gate, and before the integrity audit above, offer the - technical design when the repository is one that has code. A + technical design when the repository is one that has code, and when + the `system-designer-session` skill is available to write it. A declaration the project records binds and is never re-asked, in - either direction. Without a declaration, a repository carrying a + either direction; a session reads it from a `CLAUDE.md` note at the + repository root, or from the file such a note points at, which is + the shape available until a standing home for a project's process + answers exists. Without a declaration, a repository carrying a toolchain manifest — a file a language or platform toolchain reads to build, test or deploy it — gets the offer at every consumption gate and nothing is written; a repository carrying neither gets no - offer. A marker proves the repository holds code, never that this - change needs decomposing, so the declaration is the signal and the - marker only raises the question. The offer reads *open the - `system-designer-session` skill and write the technical design?* — - never *dispatch*, which names a background agent here. A session may - propose writing the declaration and never writes it unasked. A - change may skip the document when it sits inside boundaries and - contracts already settled and leaves the implementer no new - responsibility split, placement, or ownership of state. + offer. The core names no closed list of markers, and a domain's own + skill may name the markers of its technology. A marker proves the + repository holds code, never that this change needs decomposing, so + the declaration is the signal and the marker only raises the + question. The offer reads *open the `system-designer-session` skill + and write the technical design?* — never *dispatch*, which names a + background agent here. A session may propose writing the declaration + and never writes it unasked. A change may skip the document when it + sits inside boundaries and contracts already settled and leaves the + implementer no new responsibility split, placement, or ownership of + state. + + The brief ``` -- [ ] **Step 3: Order the audit behind it** +Three things ride in that block beyond the offer itself. The +`CLAUDE.md` sentence is what makes a declaration readable — without it +the rule says a declaration binds while no session can find one, and no +project could ever silence the offer. The `when the +`system-designer-session` skill is available` clause matches the +conditional form every neighbouring step uses, because committed +project-level rules load for people who do not have the plugin. The +domain-marker sentence is the spec's, and had no other task. -Replace, inside step 4: +- [ ] **Step 3: Order the audit behind the technical-design offer** + +Step 2 already moved `The brief` onto its own paragraph. Now name which +offer the ordering is about — "the offer" alone reads as the pair offer +two sentences above. Replace: ``` The brief @@ -908,9 +1137,11 @@ Replace, inside step 4: with: ``` - Where the offer is accepted, the audit waits: the design is the last - producer of changes to its design spec, and the two are then audited - as one target. The brief + Where the technical-design offer is accepted, the audit waits: the + design is the last producer of changes to its design spec, and the + two are then audited as one target. The gate makes one offer for the + pair rather than one per document, and where the audit discharges + chain debt it discharges it for both documents it read. The brief confirms the auditor's two preconditions: every edit from the ``` @@ -923,6 +1154,10 @@ F=plugins/working-process/rules/workflow.md tr -s '[:space:]' ' ' < $F | grep -c 'system-designer-session` skill and write the technical design' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'the declaration is the signal and the marker only raises the question' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'audited as one target' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'from a `CLAUDE.md` note at the repository root' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'when the `system-designer-session` skill is available' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'may name the markers of its technology' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'one offer for the pair' # expect 1 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill ``` @@ -942,7 +1177,11 @@ git commit -m "feat(working-process): offer the technical design at the consumpt offer added in Task 10 **Interfaces:** -- Consumes: the offer from Task 10 and the pointers from Task 6. +- Consumes: the offer from Task 10, the pointers from Task 6, and the + architect card sentence Task 13 writes. **Task 13 must land first** — + this task quotes that card, and quoting a phrase the card does not yet + carry is the propagation duty on prescribed blocks failing by our own + hand. Execute 13 before 11, or swap their numbers. - Produces: the one turn in which both ends of the pair are written, so Task 9's duty never finds a half-written pair. @@ -962,17 +1201,21 @@ Expected: `0`. After the offer paragraph, add: ``` - Accepted, the session opens the `system-designer-session` skill in - the main thread and writes the document to - `docs/technical-designs/`, following the technical-design rule that - loads there. It writes both ends of the pair in the same turn — the - design's `spec:` and the design spec's `technical-design:` — so no - audit ever meets a half-written pair. The reviewer is the - `architect` agent, whose card admits any judged document dispatched + Accepted, the session opens the `system-designer-session` skill when + it is available and writes the document to `docs/technical-designs/`, + following the technical-design rule that loads there. It writes both + ends of the pair in the same turn — the design's `spec:` and the + design spec's `technical-design:` — so no audit ever meets a + half-written pair. The reviewer is the `architect` agent when that + agent is available, whose card admits any judged document dispatched standalone, with the propagation audit gating that dispatch as it gates every verdict dispatch. The plan-adversary stays on plans. ``` +Both tool mentions are conditional, as every neighbouring step in this +rule is: committed project-level rules load for people who do not have +the plugin installed. + - [ ] **Step 3: Verify** Run: @@ -1144,10 +1387,32 @@ grep -c 'coverage: ' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'Your primary target is a spec' # expect 1 ``` -- [ ] **Step 2: Rename the class in the description** +- [ ] **Step 2: Bring the description in line with the duties below it** + +The description paraphrases all three edits, so renaming the class +alone would leave it stating premises this task retires. Replace: + +``` +Judgment audit of a churned design document, read on a fresh context — primarily a spec at the consumption gate before plan-writing +``` + +with: + +``` +Judgment audit of a churned judged document, read on a fresh context — primarily a design spec at the consumption gate before plan-writing, together with its technical design where it names one +``` + +and replace: + +``` +reads it once more as an implementer who must build from this text and has no other context +``` + +with: -Replace `Judgment audit of a churned design document` with -`Judgment audit of a churned judged document`. +``` +reads it once more as an implementer who must build from this text, the documents audited with it, and the documents its pointers name, and has no other context +``` - [ ] **Step 3: Write the four cases** @@ -1256,7 +1521,7 @@ git commit -m "feat(working-process): audit a design spec and its technical desi **Files:** - Modify: `plugins/working-process/agents/plan-adversary.md:46`, `:49`, - `:102`, and the generic dimensions list at `:57-70` + `:51-55`, `:102`, and the generic dimensions list at `:57-70` **Interfaces:** - Consumes: the triage clause from Task 12 and the `change` set from @@ -1307,7 +1572,31 @@ with: "origin": ["design-spec" | "technical-design" | "implementation-plan", …], ``` -- [ ] **Step 4: Add the coverage read of the `change` column** +- [ ] **Step 4: Retire the old values from the card's own prose** + +The schema is not the only place the card names `origin` values. +Replace: + +``` +Naming a document is not reviewing it. A finding whose `origin` is +`spec` or `both` says where the defect traces to and proposes no change +to the spec, so this boundary holds: what to do about a spec-origin +finding is the dispatcher's, and its own rules hold one for the +developer unless a written decision licenses the edit. +``` + +with: + +``` +Naming a document is not reviewing it. A finding whose `origin` names a +judged document — a design spec or a technical design — says where the +defect traces to and proposes no change to that document, so this +boundary holds: what to do about such a finding is the dispatcher's, +and its own rules hold one for the developer unless a written decision +licenses the edit. +``` + +- [ ] **Step 5: Add the coverage read of the `change` column** Append to the generic dimensions: @@ -1318,13 +1607,21 @@ Where the plan's `technical-design:` names a document, read its Parts table. The plan must cover every part marked `new`, `changed` or `retired`; one part may span several tasks and one task several parts, and a part carried as `unchanged` context needs none. An uncovered part -is an Important finding whose `origin` is the plan. A `change` value +is an Important finding whose `origin` is `implementation-plan`. A `change` value outside `new | changed | retired | unchanged` is an Important finding -whose `origin` is the technical design: the set is closed, and an -unknown value would otherwise slip past this dimension unchecked. +whose `origin` is `technical-design`: the set is closed, and an unknown +value would otherwise slip past this dimension unchecked. + +The `**Interfaces:**` blocks are read here too. Where the plan names a +technical design, those blocks reference the contracts that design +defines and say which part of one each task implements or changes; a +block that redefines a contract independently is an Important finding +whose `origin` is `implementation-plan`. Where no technical design is +named, the existing plan convention stands and this paragraph is +silent. ``` -- [ ] **Step 5: Verify** +- [ ] **Step 6: Verify** Run: @@ -1333,10 +1630,16 @@ F=plugins/working-process/agents/plan-adversary.md grep -c 'design document' $F # expect 0 grep -c '"origin": \["design-spec"' $F # expect 1 grep -c '"origin": "plan"' $F # expect 0 +grep -c '`spec` or `both`' $F # expect 0 +grep -c 'spec-origin' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'Coverage of the technical design' # expect 1 ``` -- [ ] **Step 6: Commit** +The two zeros are deletion assertions. A card whose prose still admits +`both` while its schema cannot emit it contradicts itself, which is the +defect class this very agent is dispatched to find. + +- [ ] **Step 7: Commit** ```bash git add plugins/working-process/agents/plan-adversary.md @@ -1372,19 +1675,34 @@ Replace `Mechanical propagation audit of a spec or plan before an expensive dispatch` with `Mechanical propagation audit of a design spec, a technical design or a plan before an expensive dispatch`. -- [ ] **Step 3: Add the pointer-pair duty** +- [ ] **Step 3: Add the pointer-pair duty as a ninth numbered duty** -Append to the duty list: +The card's duties are `### 1.` through `### 8.` headings, not bullets, +and the rule's table numbers its rows against them. Add after duty 8's +last paragraph: ``` -- **The pointer pair.** Where the document carries `technical-design:` - or `spec:`, check that both targets exist and that the two pointers - name each other. A pointer resolving to a missing file, or to a - document pointing elsewhere, is a hit — the pair identifies the - integrity audit's target, so a broken half silently narrows what the - next audit reads. +### 9. The pointer pair + +Where a design spec carries `technical-design:`, or a technical design +carries `spec:`, open both targets and confirm each names the document +that names it. A pointer resolving to a missing file, or to a document +pointing elsewhere, is a hit — the pair identifies the integrity +audit's target, so a broken half silently narrows what the next audit +reads. + +A plan is scoped differently and must not be read as half a pair. Its +`spec:` names a design spec, which never names the plan back; what is +checked there is that its `spec:` and `technical-design:` name a design +spec and the technical design that design spec names. A plan whose two +pointers disagree with the pair is a hit; a plan whose `spec:` is not +named back is not. ``` +The second paragraph is what keeps this duty from firing on every plan +in the repository: the reciprocity check belongs to the design spec and +its technical design alone. + - [ ] **Step 4: Verify** Run: @@ -1393,9 +1711,15 @@ Run: F=plugins/working-process/agents/propagation-auditor.md grep -c 'a design spec, a technical design or a plan' $F # expect 1 grep -c 'a spec or plan' $F # expect 0 -tr -s '[:space:]' ' ' < $F | grep -c 'The pointer pair' # expect 1 +grep -c '^### 9\. The pointer pair' $F # expect 1 +grep -c '^### [0-9]' $F # expect 9 +tr -s '[:space:]' ' ' < $F | grep -c 'is not named back is not' # expect 1 ``` +The count of `###` headings must equal the duty count the +propagation-duties rule now states, or the rule's last column points at +a duty that does not exist. + - [ ] **Step 5: Commit** ```bash @@ -1454,7 +1778,10 @@ git commit -m "feat(working-process): cover technical designs in the status pass **Files:** - Modify: `plugins/working-process/README.md:6-9` (the flow line), `:18` - (the architect paraphrase), `:133` (the `integrity` field row) + (the architect paraphrase), `:47-49` (the adversary paraphrase), + `:53-54` (the propagation-auditor paraphrase), `:66-68` (the + integrity-auditor paraphrase), `:133` (the `integrity` field row), + `:184-192` (the Rules payload enumeration and its count) **Interfaces:** - Consumes: every earlier task. This is the reader-facing summary and it @@ -1508,18 +1835,62 @@ and append to its `Meaning` cell: ; where a design spec names a technical design the two are audited as one target and one stamp records the pair, on the design spec ``` -- [ ] **Step 5: Verify, and sweep the plugin for the retired phrase** +- [ ] **Step 5: Retire the premises in three more paraphrases** + +Replace `handed a spec it declines toward the `architect`` with +`handed a judged document it declines toward the `architect``. + +Replace `the mechanical audit of a spec or` with +`the mechanical audit of a design spec, a technical design or a`. + +Replace `the document as an implementer who must build from that text +alone.` with `the document as an implementer who must build from that +text and whatever was audited with it.` + +- [ ] **Step 6: Re-count the Rules payload, which Task 2 changed** + +The README enumerates the rule files and states their number; Task 2 +ships a seventh. Replace: + +``` +The plugin ships six rule files in `rules/` — the preferred workflow +(always loaded once installed), spec/plan frontmatter and lifecycle, +Process directory conventions, ticket frontmatter, the propagation +duties keyed by the edit that triggers them (`propagation-duties.md`, +loaded while a spec, plan or domain document is open), and the +``` + +with: + +``` +The plugin ships seven rule files in `rules/` — the preferred workflow +(always loaded once installed), frontmatter and lifecycle for judged +documents and plans, what a technical design must contain +(`technical-design.md`, loaded while one is open), Process directory +conventions, ticket frontmatter, the propagation duties keyed by the +edit that triggers them (`propagation-duties.md`, loaded while a design +spec, technical design, plan or domain document is open), and the +``` + +- [ ] **Step 7: Verify, and sweep the plugin for the retired phrase** Run: ```bash grep -rc 'design document' plugins/working-process/ | grep -v ':0$' +grep -c 'seven rule files' plugins/working-process/README.md # expect 1 +grep -c 'six rule files' plugins/working-process/README.md # expect 0 +ls plugins/working-process/rules/*.md | wc -l # expect 7 +grep -rc 'a spec or plan\|build from that text alone\|handed a spec' \ + plugins/working-process/README.md | grep -v ':0$' ``` -Expected: no output — zero occurrences anywhere in the plugin. This is -the Global Constraint's closing check. +Expected: no output from the first and last, `1`, `0`, `7`. The stated +count and the directory listing must agree — a README that counts its +own payload wrong is the failure this check exists for. The first and +last lines are the Global Constraint's closing sweep. -- [ ] **Step 6: Commit** +- [ ] **Step 8: Commit** ```bash git add plugins/working-process/README.md @@ -1563,8 +1934,9 @@ Add at the top of the changelog, under its one-line preamble: one `integrity:` stamp on the design spec naming both. - `origin` on a plan-adversary finding is a list of named documents; `both` retires. -- The glossary mints `design spec`, `judged document` and `technical - design`; `design document` retires from every card and the README. +- `design document` retires from every agent card and the README in + favour of `judged document`, the class a design spec and a technical + design share. ``` - [ ] **Step 3: Set the dogfooding version** @@ -1644,3 +2016,133 @@ git commit -m "chore(working-process): changelog and dogfooding version for the is a known cost, recorded in the spec. - It renames no existing spec to `-spec.md`, and adds no `-technical-design` skill. Both are out of scope in the spec. +- **It changes no plan template.** The `**Interfaces:**` block shape + belongs to `superpowers:writing-plans`, another vendor's plugin, and + modifying it is not this work. The spec's sentence about the interface + prescription leaving the plan is delivered instead as a contract on + how the existing blocks are filled: where a technical design exists it + defines the interfaces, and the blocks reference its contracts rather + than restating them (Task 6, and the adversary's dimension 6 in Task + 15). A separate ticket earns its keep only if validation shows the + existing block shape cannot reference a design usefully. + +## Review rounds + +### 2026-09-17 — plan-adversary, fable 5.1, blocking (round 1, full-document) + +Full-document read. Eleven Important, eight Minor. Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-1.md`. +Every citation the report supplied was checked against what it names +before any line below was written; none missed. + +- fixed 2026-09-17 — [Important] Task 5 promised "every branch reading + the class" and edited two, leaving four spec/plan binaries untouched, + one of them read verbatim by `process-status`, which is told never to + supply an owner from its own knowledge; license: ADR 0004, where the + rule's branches say which class they mean rather than which filename; + Task 5 now names six branch sentences explicitly, its Interfaces + claim says six rather than every, and its verify step carries a + deletion assertion per branch. +- fixed 2026-09-17 — [Important] `origin: spec` and `both` survived in + two shipped files no task touched, so the adversary's card would + contradict its own schema; license: the design spec, where `both` + retires with the value; `plan-adversary.md:51-55` joins Task 15 and + `spec-plan-lifecycle.md:229-231` joins Task 5, both with deletion + assertions. +- fixed 2026-09-17 — [Important] Task 5 wrote "the same consumption-gate + semantics for a stale stamp" while Task 7 wrote "carries no + `integrity:` of its own", and the plan dropped the spec's reconciling + sentence; license: the design spec's own sentence, quoted verbatim; + Task 5 Step 6 now carries "One mechanism it shares rather than owns…". +- fixed 2026-09-17 — [Important] Tasks 9 and 16 appended a bullet to a + "duty list" that is a table in one file and `### 1.`–`### 8.` headings + in the other, and left the counters binding them stale; license: the + files' own structure and the counters' own derivation; Task 9 adds a + table row and re-derives eight/nine to nine/ten, Task 16 adds + `### 9.`, and both verify the heading count against the stated one. +- fixed 2026-09-17 — [Important] the card's pointer-pair duty was scoped + wider than the rule's and would have turned every plan's `spec:` into + a hit, while the spec's third check — a plan's two pointers agreeing + with the pair — had no deliverer; license: the design spec, which + scopes reciprocity to the design spec and its technical design and + states the plan clause separately; Task 16's duty now separates the + two and says a plan whose `spec:` is not named back is not a hit. +- fixed 2026-09-17 — [Important] the README counts "six rule files" and + Task 2 ships a seventh; license: the counter's own derivation; Task 18 + gains the enumeration, the count, and a check comparing the stated + number against `ls rules/*.md | wc -l`. +- fixed 2026-09-17 — [Important] the rule said a recorded declaration + binds while nothing said where a session reads one, so no project + could ever silence the offer; license: the design spec, which fixes + that a session reads it without being told where to look and names + the `CLAUDE.md` shape an implementation may assume; Task 10's block + now names that surface. +- fixed 2026-09-17 — [Important] the new rule text named the + `system-designer-session` skill and the `architect` agent + unconditionally; license: the repo's plugin-authoring rule, where + mentions of skills and agents inside rule text are conditional because + committed project-level rules load for people without the plugin; + both mentions in Tasks 10 and 11 now carry "when available". +- fixed 2026-09-17 — [Important] the spec's "One offer, and what it + discharges" paragraph had no delivering task and the lifecycle rule's + discharge paths still described one document; license: the design + spec's paragraph; the one-offer sentence joins Task 10 and the + discharge widening joins Task 7 as its own step. +- fixed 2026-09-17 — [Important] the glossary's Consumption gate entry + still carried the broad ordering the spec's integrity ruling withdrew, + so Task 8's text contradicted a canonical term; license: the design + spec's narrowed ordering, ruled 2026-09-17; the entry now reads + "writing the technical design and applying its review dispositions + precede the joint integrity audit", and Task 1 Step 4 greps that + phrase instead of counting items. The glossary edit lands in this + plan's wave because the spec is stamped and its ledger closed. +- fixed 2026-09-17 — [Important] Task 10's insertion point was + positional and Step 3's "the offer" could have referred to the pair + offer two sentences above; license: the propagation duty on prescribed + blocks, which requires a byte-exact anchor; Step 2 anchors on + `absent. The brief` and Step 3 says "the technical-design offer". +- fixed 2026-09-17 — [Minor] Task 1 called the glossary and ADR + uncommitted and committed them in a step that would have found nothing + to commit; license: the repository state, `dc85b9b`; the task states + they are committed and its last step commits nothing. +- fixed 2026-09-17 — [Minor] Task 7's anchor named a paragraph end that + does not exist — the sentence closes mid-bullet and the bullet runs + twelve more lines; license: the file itself; the anchor moves to the + bullet's real last line. +- fixed 2026-09-17 — [Minor] four paraphrase sites kept the premises the + tasks retire, in the auditor's description and three README bullets; + license: the design spec, which retires the phrase and narrows the + lens; Task 14 Step 2 rewrites the description's three clauses and Task + 18 gains the three README sites. +- fixed 2026-09-17 — [Minor] the lifecycle rule's title and opener + stayed binary after its scope widened to three classes; license: the + design spec, which puts technical designs under this rule; Task 5 + Step 3 rewrites both. +- fixed 2026-09-17 — [Minor] dimension 6 named `origin` values in prose + while the schema three lines up emits literals; license: the schema in + the same task; dimension 6 writes the literals. +- fixed 2026-09-17 — [Minor] Task 11 quotes a card sentence Task 13 + writes, an ordering dependency the plan did not state; license: the + propagation duty on prescribed blocks, which this would breach by our + own hand; Task 11's Interfaces block states the dependency outright. +- fixed 2026-09-17 — [Minor] the changelog credited the plugin with + glossary terms that live in this repository and not in the plugin; + license: the changelog's own scope line; the bullet keeps the shipped + half only. +- fixed 2026-09-17 — [Minor] two spec sentences had neither a task nor a + not-done line: the domain skill naming its technology's markers, and + the interface prescription leaving the plan; ruling: 2026-09-17; the + marker sentence joins Task 10's block, and the interface sentence is + delivered as a contract rather than a template change — where a + technical design exists it defines the interfaces and the plan's + `**Interfaces:**` blocks reference its contracts rather than restating + them (Task 6 Step 4, enforced by the adversary's dimension 6 in Task + 15), with the `superpowers:writing-plans` template explicitly out of + scope. The developer chose this over both declaring the sentence + undelivered and editing another vendor's plugin. +- signal 2026-09-17 — the reviewer judges a second round worth its cost + after this wave and says it should be diff-scoped: most Important + findings are one class, consumers no task enumerated, which a + propagation gate should catch once the plan names those sites. It + expects round two to close on `concerns` or `LGTM`, and judges this + round's leftovers insufficient to earn a third without new evidence. From 1263e6939e79e686085ad7e0f544261fd24a0e25 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 21 Sep 2026 09:11:09 +0200 Subject: [PATCH 003/143] docs: re-derive the duty table count against the file --- docs/plans/2026-09-17-technical-design-step.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index b97f46c..c0c85a1 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -966,9 +966,13 @@ Run: F=plugins/working-process/rules/propagation-duties.md sed -n '2,5p' $F grep -c 'technical-design' $F # expect 0 -grep -c '^- \|^## ' $F +grep -c '^| ' $F # expect 10: header plus nine edit rows ``` +The separator row (`|---|---|---|`) carries no space after the pipe and +never matches this pattern — which is why the count is ten and not +eleven. + - [ ] **Step 2: Widen the rule's scope** In the frontmatter, after ` - "docs/specs/**"`, insert: @@ -1026,7 +1030,7 @@ Run: ```bash F=plugins/working-process/rules/propagation-duties.md grep -c '"docs/technical-designs/\*\*"' $F # expect 1 -grep -c '^| ' $F # expect 12: header, separator, ten edit rows +grep -c '^| ' $F # expect 11: header plus ten edit rows grep -c 'nine duties fall due' $F # expect 1 grep -c 'keyed by the ten edits' $F # expect 1 grep -c 'walks the same nine' $F # expect 1 @@ -2140,6 +2144,13 @@ before any line below was written; none missed. 15), with the `superpowers:writing-plans` template explicitly out of scope. The developer chose this over both declaring the sentence undelivered and editing another vendor's plugin. +- hit fixed 2026-09-21 — Task 9's before-state step still counted + bullet-list lines, a measurement left over from the bullet its Step 3 + no longer writes; it counts the duty table's rows instead. +- hit fixed 2026-09-21 — Task 9's verify step expected twelve table + lines, counting a separator row that `^| ` never matches because it + carries no space after the pipe; measured against the file, the + before count is ten and the after count eleven. - signal 2026-09-17 — the reviewer judges a second round worth its cost after this wave and says it should be diff-scoped: most Important findings are one class, consumers no task enumerated, which a From 9972d9ecc6a07523770fd9a3eb64c178622e0da6 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 21 Sep 2026 09:21:54 +0200 Subject: [PATCH 004/143] docs: dispose the plan-adversary round two findings --- .../plans/2026-09-17-technical-design-step.md | 147 +++++++++++++++--- 1 file changed, 125 insertions(+), 22 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index c0c85a1..5dea921 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -559,6 +559,21 @@ document passes to plan-writing, nor a plan to implementation, while `held` lines stay open — an LGTM can leave the frontmatter clean while a ``` +The next sentence of the same paragraph is a seventh branch and has to +move with it. Replace: + +``` +latest. Writing a plan from a spec is that same seam: the held spec +questions are asked before the plan is written, whoever writes it. +``` + +with: + +``` +latest. Writing a plan from a judged document is that same seam: its +held questions are asked before the plan is written, whoever writes it. +``` + - [ ] **Step 5: Name the class at the three remaining branches** Replace: @@ -671,6 +686,7 @@ grep -c 'for a spec, the consumption' $F # expect 0 grep -c 'spec-gate semantics' $F # expect 0 grep -c 'plan-writing for a spec' $F # expect 0 grep -c 'origin: spec' $F # expect 0 +grep -c 'the held spec questions' $F # expect 0 ``` The last three are deletion assertions: each names a branch sentence @@ -1093,8 +1109,7 @@ with: absent. At the same gate, and before the integrity audit above, offer the - technical design when the repository is one that has code, and when - the `system-designer-session` skill is available to write it. A + technical design when the repository is one that has code. A declaration the project records binds and is never re-asked, in either direction; a session reads it from a `CLAUDE.md` note at the repository root, or from the file such a note points at, which is @@ -1107,9 +1122,12 @@ with: skill may name the markers of its technology. A marker proves the repository holds code, never that this change needs decomposing, so the declaration is the signal and the marker only raises the - question. The offer reads *open the `system-designer-session` skill - and write the technical design?* — never *dispatch*, which names a - background agent here. A session may propose writing the declaration + question. The offer reads *open the `system-designer-session` skill, + when available, and write the technical design?* — never *dispatch*, + which names a background agent here. Where no skill covers the + technology the document is still written, from generic knowledge and + best effort: a tool that is not installed disables its suggestion, + never the work. A session may propose writing the declaration and never writes it unasked. A change may skip the document when it sits inside boundaries and contracts already settled and leaves the implementer no new responsibility split, placement, or ownership of @@ -1121,10 +1139,11 @@ with: Three things ride in that block beyond the offer itself. The `CLAUDE.md` sentence is what makes a declaration readable — without it the rule says a declaration binds while no session can find one, and no -project could ever silence the offer. The `when the -`system-designer-session` skill is available` clause matches the -conditional form every neighbouring step uses, because committed -project-level rules load for people who do not have the plugin. The +project could ever silence the offer. The `when available` clause sits +on the skill and not on the offer: this rule's own preamble says a tool +that is not installed disables its suggestion and never the work, and +the spec carries a degradation path for exactly that case, so gating +the offer on the tool would make the work vanish with it. The domain-marker sentence is the spec's, and had no other task. - [ ] **Step 3: Order the audit behind the technical-design offer** @@ -1159,7 +1178,8 @@ tr -s '[:space:]' ' ' < $F | grep -c 'system-designer-session` skill and write t tr -s '[:space:]' ' ' < $F | grep -c 'the declaration is the signal and the marker only raises the question' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'audited as one target' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'from a `CLAUDE.md` note at the repository root' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'when the `system-designer-session` skill is available' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'skill, when available, and write the technical design' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'never the work' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'may name the markers of its technology' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'one offer for the pair' # expect 1 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill @@ -1415,7 +1435,7 @@ reads it once more as an implementer who must build from this text and has no ot with: ``` -reads it once more as an implementer who must build from this text, the documents audited with it, and the documents its pointers name, and has no other context +reads it once more as an implementer who must build from this text, the documents audited with it, and the documents its `spec:` and `technical-design:` name, and has no other context ``` - [ ] **Step 3: Write the four cases** @@ -1541,7 +1561,7 @@ Run: F=plugins/working-process/agents/plan-adversary.md grep -c 'design document' $F # expect 2 grep -c '"origin": "plan" | "spec" | "both"' $F # expect 1 -grep -c '^### [0-9]' $F +grep -c '^### [0-9]' $F # expect 4 ``` - [ ] **Step 2: Rename the class in both sentences** @@ -1605,7 +1625,7 @@ licenses the edit. Append to the generic dimensions: ``` -### 6. Coverage of the technical design's parts +### 5. Coverage of the technical design's parts Where the plan's `technical-design:` names a document, read its Parts table. The plan must cover every part marked `new`, `changed` or @@ -1637,6 +1657,7 @@ grep -c '"origin": "plan"' $F # expect 0 grep -c '`spec` or `both`' $F # expect 0 grep -c 'spec-origin' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'Coverage of the technical design' # expect 1 +grep -c '^### [0-9]' $F # expect 5 ``` The two zeros are deletion assertions. A card whose prose still admits @@ -1938,9 +1959,9 @@ Add at the top of the changelog, under its one-line preamble: one `integrity:` stamp on the design spec naming both. - `origin` on a plan-adversary finding is a list of named documents; `both` retires. -- `design document` retires from every agent card and the README in - favour of `judged document`, the class a design spec and a technical - design share. +- The class a design spec and a technical design share is named + `judged document` on every agent card and in the README; the phrase + those surfaces carried before is retired. ``` - [ ] **Step 3: Set the dogfooding version** @@ -2026,7 +2047,7 @@ git commit -m "chore(working-process): changelog and dogfooding version for the prescription leaving the plan is delivered instead as a contract on how the existing blocks are filled: where a technical design exists it defines the interfaces, and the blocks reference its contracts rather - than restating them (Task 6, and the adversary's dimension 6 in Task + than restating them (Task 6, and the adversary's dimension 5 in Task 15). A separate ticket earns its keep only if validation shows the existing block shape cannot reference a design usefully. @@ -2122,9 +2143,10 @@ before any line below was written; none missed. stayed binary after its scope widened to three classes; license: the design spec, which puts technical designs under this rule; Task 5 Step 3 rewrites both. -- fixed 2026-09-17 — [Minor] dimension 6 named `origin` values in prose - while the schema three lines up emits literals; license: the schema in - the same task; dimension 6 writes the literals. +- fixed 2026-09-17 — [Minor] the new coverage dimension named `origin` + values in prose while the schema three lines up emits literals; + license: the schema in the same task; the dimension writes the + literals. Renumbered 5 at round 2. - fixed 2026-09-17 — [Minor] Task 11 quotes a card sentence Task 13 writes, an ordering dependency the plan did not state; license: the propagation duty on prescribed blocks, which this would breach by our @@ -2140,8 +2162,9 @@ before any line below was written; none missed. delivered as a contract rather than a template change — where a technical design exists it defines the interfaces and the plan's `**Interfaces:**` blocks reference its contracts rather than restating - them (Task 6 Step 4, enforced by the adversary's dimension 6 in Task - 15), with the `superpowers:writing-plans` template explicitly out of + them (Task 6 Step 4, enforced by the adversary's new coverage + dimension in Task 15), with the `superpowers:writing-plans` template + explicitly out of scope. The developer chose this over both declaring the sentence undelivered and editing another vendor's plugin. - hit fixed 2026-09-21 — Task 9's before-state step still counted @@ -2157,3 +2180,83 @@ before any line below was written; none missed. propagation gate should catch once the plan names those sites. It expects round two to close on `concerns` or `LGTM`, and judges this round's leftovers insufficient to earn a third without new evidence. + +### 2026-09-21 — plan-adversary, fable 5.1, blocking (round 2, diff-scoped) + +Diff-scoped over round one's fix wave. Two Important, six Minor. Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-2.md`. +Every citation was checked against what it names; none missed. The +reviewer read the recorded deviation and declined to refute it, having +verified in the `superpowers:writing-plans` skill that the +`**Interfaces:**` block is indeed that plugin's. + +- fixed 2026-09-21 — [Important] round one's fix hung the conditional on + the offer rather than on the tool, so the technical design would never + be offered where the skill is absent and the work would vanish with + its tool; license: this rule's own preamble, "a tool that is not + installed disables its suggestion — never the work itself", and the + design spec's degradation path, where a document with no covering + skill is written from generic knowledge; the offer now fires on + declaration and marker alone, `when available` sits on the skill, and + the block says outright that the document is still written without + one. +- fixed 2026-09-21 — [Minor] the backstop widened to "no judged + document" while the next sentence of the same paragraph stayed + spec-only, leaving a seventh branch one line below the sixth; license: + ADR 0004, as for the other six; Task 5 Step 4 extends through that + sentence and its verify step asserts the old wording is gone. +- fixed 2026-09-21 — [Minor] the new coverage dimension was numbered 6 + in a list whose last heading is 4, and the before-state count carried + no expected value to catch it; license: the count's own derivation + from the file; the dimension is 5, the before count expects 4 and the + after count 5, and three stale references to "dimension 6" are + rewritten. +- fixed 2026-09-21 — [Minor] the rewritten auditor description said the + implementer lens reads "the documents its pointers name" while the + body two steps later excludes `revises:` — the description restating a + premise the body retires, which is the defect that step existed to + remove; license: the design spec, which narrows context to the two + pointers; the description names `spec:` and `technical-design:`. +- fixed 2026-09-21 — [Minor] Task 19 wrote the retired phrase into the + changelog one task after Task 18's plugin-wide sweep expects zero, so + the sweep passed on its run and failed on a re-run, breaking the + plan's own Global Constraint; license: that constraint; the changelog bullet + names the new term and describes the old one without spelling it. +- held — [Important] the discharge widening covers the audit arm only, + so with one joint offer for two documents a decline leaves undefined + whether the technical design's chain debt is discharged and which + headings are annotated; question: does declining the joint offer + discharge the chain debt of both documents, or only the design + spec's?; + options: (a) both, since one offer covered both and the developer + released both by the same act — recommended, because the alternative + makes one offer produce two different outcomes; (b) only the design + spec's, leaving the design's debt to its own later reading. +- held — [Minor] after Task 10 names `CLAUDE.md` as the readable + declaration surface, the plan's "writes no declaration surface" bullet + describes a cost the plan itself just made avoidable, and the spec's + "this repository declines by declaration" has no deliverer; question: + does this change write this repository's own declaration, dogfooding + the surface it ships?; options: (a) yes, one step adding the + declaration to this repo's `CLAUDE.md` — recommended, since the + surface now exists and the alternative leaves us clicking the offer + away at every gate; (b) no, and the bullet is rewritten to say the + surface exists and this repo's declaration is deliberately a separate + change. +- held — [Minor] the wave uses "the pair" for the design spec and its + technical design while the glossary already uses "pair offer" and + "pair question" for the two-arm chain-debt question, and one block + puts both senses two sentences apart; question: which sense keeps the + bare word, and what is the other called?; options: (a) mint a glossary + term for the document pair — `audit pair` reads best — and leave + "pair offer" alone, recommended because the offer sense is already + written into three glossary entries and a shipped rule; (b) rename the + offer side; (c) qualify every occurrence in prose and mint nothing. + This is spec-origin: the design spec is where the second sense enters. +- signal 2026-09-21 — the reviewer judges a third round worth its cost + only as the confirming full-document round the plan already owes, not + as another diff-scoped pass. It reads both Important findings as one + class again — a fix pinned to the wrong noun, an extension stopped + halfway down a list — and expects the confirming round to close on + `concerns` or `LGTM`, with no material for a fourth absent new + evidence. From 72b4817268d1d2d93195e25f901e615abe12f8bd Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Thu, 24 Sep 2026 21:25:12 +0200 Subject: [PATCH 005/143] docs: settle the decline arm, this repo's declaration, and the audit-pair term --- docs/domain/glossary.md | 8 + .../plans/2026-09-17-technical-design-step.md | 207 +++++++++++++----- .../specs/2026-09-16-technical-design-step.md | 27 ++- 3 files changed, 183 insertions(+), 59 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index 6800921..02ade7f 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -228,6 +228,14 @@ contracts between them, and the state they keep — written between the design spec and the plan, and read next by the plan-writer. _Avoid_: detailed design +**Audit pair**: +A design spec and the technical design it names, audited together as one +target, with one integrity record on the design spec identifying both +documents and their body hashes. Where both senses of "pair" stand close +together, write the full name — an audit pair is two documents, a pair +offer is the two-armed question a diff-scoped chain earns. +_Avoid_: pair (bare, where a pair offer is also in view), document pair + **Vocabulary gap**: A kind of part, contract, or home of state that no domain skill available to the author names, recorded in the technical design where it diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index 5dea921..e7e7e82 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -83,10 +83,10 @@ term that already exists, and it commits nothing. - Verify: `docs/domain/adr/0004-ceremonies-follow-the-reader.md` **Interfaces:** -- Produces: the seven glossary terms every later task's prose leans on — +- Produces: the glossary terms every later task's prose leans on — `Design spec`, `Judged document`, `Technical design`, `Part`, - `Contract`, `Vocabulary gap`, and `Origin` in its list form — plus ADR - 0004 as the citable decision behind the class. + `Contract`, `Vocabulary gap`, `Audit pair`, and `Origin` in its list + form — plus ADR 0004 as the citable decision behind the class. - [ ] **Step 1: Confirm every term the spec mints exists** @@ -94,7 +94,8 @@ Run: ```bash for t in "Design spec" "Judged document" "Technical design" "Part" \ - "Contract" "Vocabulary gap" "Origin" "Consumption gate"; do + "Contract" "Vocabulary gap" "Origin" "Consumption gate" \ + "Audit pair"; do printf '%-18s %s\n' "$t" \ "$(grep -c "^\*\*${t}\*\*:" docs/domain/glossary.md)" done @@ -653,7 +654,7 @@ After the offers paragraph, add: A technical design is a judged document and takes the design spec's side of every branch in this rule: the same fields, the same consumption-gate semantics for a stale stamp, the same owner for an -unresolved verdict, and the same pair offered against chain debt. It is +unresolved verdict, and the same pair offer against chain debt. It is not grilled — grilling stress-tests terminology against the project glossary, and a technical design mints none: its vocabulary comes from its design spec, which was grilled, and from the domain's own skills. @@ -662,8 +663,9 @@ those skills where one exists and recorded as a vocabulary gap where none does. One mechanism it shares rather than owns: the integrity check is due on -it like any judged document, and one audit of the pair discharges it, so -the `integrity:` stamp sits on the design spec and names both. +it like any judged document, and one audit of the audit pair — the +design spec together with the technical design it names — discharges it, +so the `integrity:` stamp sits on the design spec and names both. ``` Without that last sentence this block and Task 7's contradict each @@ -842,7 +844,7 @@ the bullet's real last line, `a typo fix included.`, and add after it: ``` A design spec that names a technical design is audited with it as one - target, and one stamp records the pair. The stamp lives on the design + target — an **audit pair** — and one stamp records it. The stamp lives on the design spec, names both documents and both body hashes, and the technical design carries no `integrity:` of its own — it owes the check like any judged document and discharges it jointly. The value takes the form @@ -865,10 +867,31 @@ applied and before the `integrity:` stamp; and any later full-document with: ``` -applied and before the `integrity:` stamp — and where that audit read a -pair, it discharges the debt of both documents it read, annotating each -document's own unannotated diff-scoped `LGTM` headings; and any later -full-document +applied and before the `integrity:` stamp — and where that audit read an +audit pair, it discharges the debt of both documents it read, annotating +each document's own unannotated diff-scoped `LGTM` headings; and any +later full-document +``` + +The decline path takes the same widening and a bound. Replace: + +``` +dispatcher writes it in every case: the developer declining the gate's +pair offer, written in the decline turn before the work that decline +licenses begins; +``` + +with: + +``` +dispatcher writes it in every case: the developer declining the gate's +pair offer, written in the decline turn before the work that decline +licenses begins, and written for every document that offer named — a +declined offer over an audit pair discharges the chain debt of both. +What a decline never does is stand in for the audit itself: it writes +no `integrity:` stamp, leaves every other open finding open, and closes +no `blocking` verdict. "Do not run the audit" is a release from the +chain debt the offer named and from nothing else; ``` - [ ] **Step 5: Verify both directions** @@ -1162,9 +1185,11 @@ with: ``` Where the technical-design offer is accepted, the audit waits: the design is the last producer of changes to its design spec, and the - two are then audited as one target. The gate makes one offer for the - pair rather than one per document, and where the audit discharges - chain debt it discharges it for both documents it read. The brief + two are then audited as one target — an audit pair. The gate makes + one offer for that pair rather than one per document, and the offer + names both documents, so declining it releases the chain debt of the + two it named and nothing besides. Where the audit runs instead, it + discharges that debt for both documents it read. The brief confirms the auditor's two preconditions: every edit from the ``` @@ -1181,7 +1206,8 @@ tr -s '[:space:]' ' ' < $F | grep -c 'from a `CLAUDE.md` note at the repository tr -s '[:space:]' ' ' < $F | grep -c 'skill, when available, and write the technical design' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'never the work' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'may name the markers of its technology' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'one offer for the pair' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'one offer for that pair' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'an audit pair' # expect 1 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill ``` @@ -1924,7 +1950,72 @@ git commit -m "docs(working-process): put the technical design in the README flo --- -### Task 19: Changelog and the dogfooding version +### Task 19: This repository's own declaration + +Task 10 gives the declaration a readable surface, so this repository can +use it. Its product is prose, but `.claude-plugin/plugin.json`, +`.claude-plugin/marketplace.json` and the CI workflows are files a +platform reads to deploy it, so the marker test reaches it and without a +declaration the offer fires at every consumption gate. Writing the +declaration here also exercises the surface the plugin ships — the one +thing this repository can dogfood about a document class it will never +write. + +**Files:** +- Modify: `CLAUDE.md` (repository root) + +**Interfaces:** +- Consumes: the declaration surface from Task 10. +- Produces: nothing later tasks read. This is the plugin's contract used + on its own repository, not part of the payload. + +- [ ] **Step 1: Measure the before state** + +Run: + +```bash +grep -c 'technical design' CLAUDE.md # expect 0 +grep -n '^## ' CLAUDE.md +``` + +- [ ] **Step 2: Write the declaration** + +Add a section after `## Worktrees and topic branches`: + +```markdown +## Technical designs + +This repository does not get the technical-design offer by default. Its +product is prose: a part's name is its path and behaviour is already the +shape of the contract, so a technical design here would restate the +design spec and the plan. The declaration switches the default offer +off; it is not a ban. Ask for a technical design explicitly and the +step runs as it would anywhere else. +``` + +- [ ] **Step 3: Verify the declaration reads as a switch, not a ban** + +Run: + +```bash +grep -c 'does not get the technical-design offer by default' CLAUDE.md # expect 1 +grep -c 'it is not a ban' CLAUDE.md # expect 1 +``` + +Both matter. A declaration a later session reads as a prohibition would +refuse work the developer asked for, which is the failure the round-two +finding about conditionals was about, one level up. + +- [ ] **Step 4: Commit** + +```bash +git add CLAUDE.md +git commit -m "docs: declare that this repository skips the technical-design offer" +``` + +--- + +### Task 20: Changelog and the dogfooding version **Files:** - Modify: `plugins/working-process/CHANGELOG.md` @@ -2020,13 +2111,13 @@ git commit -m "chore(working-process): changelog and dogfooding version for the - **The dogfooding version uses a counter the versioning rule does not yet carry.** `.claude/rules/plugin-versioning.md` describes - `X.Y.Z-dev.` with no counter, while Task 19 mints + `X.Y.Z-dev.` with no counter, while Task 20 mints `0.18.0-dev.1.technical-design-step`. The counter is the developer's decision of 2026-09-17 — `n` separates the successive dogfood releases of successive topics and increments on `develop`, which is the owner the counter lacked when an earlier attempt at it was declined. The rule's amendment is deliberately not part of this branch, so the - divergence stands until that separate change lands. Task 19 follows + divergence stands until that separate change lands. Task 20 follows the decision, not the current rule text. ## What this plan does not do @@ -2035,10 +2126,11 @@ git commit -m "chore(working-process): changelog and dogfooding version for the The plugin ships the contract; this repo's product is prose and it declines the offer. The first project to accept the offer creates the directory, and the first-create question fires there. -- It writes no declaration surface. Where a project records the - declaration is deferred by the spec, so this repo keeps receiving the - offer at every consumption gate until that question is settled. That - is a known cost, recorded in the spec. +- It does not settle where a project's standing process answers live. + The spec defers that question; Task 10 names the shape available + today — a `CLAUDE.md` note, or a file it points at — and Task 19 uses + it for this repository. When the wider question is settled, the + declaration moves with every other standing answer. - It renames no existing spec to `-spec.md`, and adds no `-technical-design` skill. Both are out of scope in the spec. - **It changes no plan template.** The `**Interfaces:**` block shape @@ -2217,42 +2309,41 @@ verified in the `superpowers:writing-plans` skill that the premise the body retires, which is the defect that step existed to remove; license: the design spec, which narrows context to the two pointers; the description names `spec:` and `technical-design:`. -- fixed 2026-09-21 — [Minor] Task 19 wrote the retired phrase into the - changelog one task after Task 18's plugin-wide sweep expects zero, so - the sweep passed on its run and failed on a re-run, breaking the - plan's own Global Constraint; license: that constraint; the changelog bullet +- fixed 2026-09-21 — [Minor] the changelog task wrote the retired phrase + one task after Task 18's plugin-wide sweep expects zero, so the sweep + passed on its run and failed on a re-run, breaking the plan's own + Global Constraint; license: that constraint; the changelog bullet names the new term and describes the old one without spelling it. -- held — [Important] the discharge widening covers the audit arm only, - so with one joint offer for two documents a decline leaves undefined - whether the technical design's chain debt is discharged and which - headings are annotated; question: does declining the joint offer - discharge the chain debt of both documents, or only the design - spec's?; - options: (a) both, since one offer covered both and the developer - released both by the same act — recommended, because the alternative - makes one offer produce two different outcomes; (b) only the design - spec's, leaving the design's debt to its own later reading. -- held — [Minor] after Task 10 names `CLAUDE.md` as the readable - declaration surface, the plan's "writes no declaration surface" bullet - describes a cost the plan itself just made avoidable, and the spec's - "this repository declines by declaration" has no deliverer; question: - does this change write this repository's own declaration, dogfooding - the surface it ships?; options: (a) yes, one step adding the - declaration to this repo's `CLAUDE.md` — recommended, since the - surface now exists and the alternative leaves us clicking the offer - away at every gate; (b) no, and the bullet is rewritten to say the - surface exists and this repo's declaration is deliberately a separate - change. -- held — [Minor] the wave uses "the pair" for the design spec and its - technical design while the glossary already uses "pair offer" and - "pair question" for the two-arm chain-debt question, and one block - puts both senses two sentences apart; question: which sense keeps the - bare word, and what is the other called?; options: (a) mint a glossary - term for the document pair — `audit pair` reads best — and leave - "pair offer" alone, recommended because the offer sense is already - written into three glossary entries and a shipped rule; (b) rename the - offer side; (c) qualify every occurrence in prose and mint nothing. - This is spec-origin: the design spec is where the second sense enters. +- fixed 2026-09-24 — [Important] the discharge widening covered the + audit arm only, so with one joint offer for two documents a decline + left undefined whether the technical design's chain debt was + discharged and which headings were annotated; ruling: 2026-09-24, + variant (a); a declined offer over an audit pair discharges the chain + debt of both documents the offer named, the offer names them so a + reader can see what the decline settles, and the same clause bounds + it — a decline writes no `integrity:` stamp, leaves every other open + finding open and closes no `blocking` verdict, so "do not run the + audit" never reads as a wider release. +- fixed 2026-09-24 — [Minor] after Task 10 named `CLAUDE.md` as the + readable declaration surface, the plan's "writes no declaration + surface" bullet described a cost the plan itself had just made + avoidable, and the spec's "this repository declines by declaration" + had no deliverer; ruling: 2026-09-24, variant (a); new Task 19 writes + this repository's declaration and the changelog task moves to 20. The + declaration switches the default offer off rather than banning the + document — a session asked for a technical design here still writes + one — and its verify step asserts both halves, since a declaration a + later session read as a prohibition would refuse work the developer + asked for. +- fixed 2026-09-24 — [Minor] "the pair" named both the two documents and + the two-armed chain-debt question, two sentences apart in one block; + ruling: 2026-09-24, variant (a); the glossary mints **Audit pair** for + the document sense, prose writes the full name wherever both senses + stand close, and the term's own entry says so. The glossary and the + design spec's Parts row changed for this, so the line lands in the + design spec's ledger too, under + `### 2026-09-24 — fix from docs/plans/2026-09-17-technical-design-step.md`, + which leaves that spec's `integrity:` stamp stale by design. - signal 2026-09-21 — the reviewer judges a third round worth its cost only as the confirming full-document round the plan already owes, not as another diff-scoped pass. It reads both Important findings as one diff --git a/docs/specs/2026-09-16-technical-design-step.md b/docs/specs/2026-09-16-technical-design-step.md index c9be22f..a5379b2 100644 --- a/docs/specs/2026-09-16-technical-design-step.md +++ b/docs/specs/2026-09-16-technical-design-step.md @@ -366,7 +366,7 @@ with its own audit, and it buys nothing this change needs. | `propagation-auditor` card | agent card | changed | the document class it audits, today "a spec or plan"; the pointer pair among what it checks | — | | `integrity-auditor` card | agent card | changed | which documents are its target in each case, what it may read as context, the second lens's premise, and the coverage tell's shape for a joint target | which gaps are structural | | `plan-adversary` card | agent card | changed | `origin` as a list of named documents; the coverage dimension | the document's contents | -| glossary | reference document | changed | `Part`, `Contract`, `Technical design`, `Judged document`, `Design spec`, `Vocabulary gap`, `Origin` rewritten as a list of named documents, and the `Consumption gate` entry's count and ordering | — | +| glossary | reference document | changed | `Part`, `Contract`, `Technical design`, `Judged document`, `Design spec`, `Vocabulary gap`, `Audit pair`, `Origin` rewritten as a list of named documents, and the `Consumption gate` entry's count and ordering | — | | `architect` card | agent card | changed | the class name in what it accepts | its duties | | ADR 0004 | reference document | new | why a class owes the ceremonies it owes | the ceremonies themselves | | plugin README | reference document | changed | the class name where it paraphrases the architect's card; the `integrity:` value shape in the field table | the two pointer fields, which are not stamped | @@ -898,3 +898,28 @@ dispositions, and the lifecycle rule says only that an audit's dispositions land as edits plus the stamp, which leaves a `held` line and a gate line homeless. Recorded here, not fixed here — the spec's scope is the class, not the ledger's grammar. + +### 2026-09-24 — fix from docs/plans/2026-09-17-technical-design-step.md + +- fixed 2026-09-24 — [Minor] the bare word "pair" named both this + change's two documents and the two-armed chain-debt question, and one + block put the senses two sentences apart; ruling: 2026-09-24; the + glossary mints **Audit pair** for the document sense, the Parts + table's glossary row names it, and prose writes the full name wherever + both senses stand close. Raised by the plan-adversary's round two + against the plan; that plan's ledger line under its round-two heading + points here. + +This heading takes the cross-document fix shape the lifecycle rule +defines, which that rule scopes to a document whose loop closed at +`LGTM`. This one closed at `concerns (resolved)`, which has the +resolution annotation instead — an annotation for closing a verdict, not +for a fix arriving later from another document's review. The shape is +the closest honest fit and the gap is worth naming: it carries no +ordinal and no verdict, so it stays out of the round cap and out of the +Unfinished-work anchors, which is what this record needs. + +The `integrity:` stamp above is stale as of this edit, by the rule that +any body change re-arms it. That is the designed consequence of a +licensed spec-origin fix, not an oversight: the stamp certifies the body +it was given, and this body has changed. From 1fbb6c6f4ab542d9329f7b8cefa8d8cb6d5817a6 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Thu, 24 Sep 2026 22:47:51 +0200 Subject: [PATCH 006/143] docs: dispose the plan-adversary round three findings --- .../plans/2026-09-17-technical-design-step.md | 490 +++++++++++++++--- .../specs/2026-09-16-technical-design-step.md | 21 +- 2 files changed, 429 insertions(+), 82 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index e7e7e82..ff6eb91 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -46,6 +46,23 @@ verification. (`tr -s '[:space:]' ' '`), so a phrase matches wherever a line wraps. Only a check anchoring something that cannot wrap — a path, a filename shape, a heading, a frontmatter key — is written plain. +- **An anchor that ends mid-line runs to the line's end.** A + replacement whose anchor stops mid-line leaves the rest of that line + glued to the block's last line. Every anchor here ends at a line's + end, and where the original line's tail matters the replacement + carries it. +- **Counting after `tr` proves existence, not occurrence.** `tr` + collapses a file to one line, so `grep -c` then reports 1 however + many times a phrase appears. Where a check must prove a block landed + once, it counts with `grep -o … | wc -l`, and it anchors on a phrase + unique to the block rather than one the file already carries. +- **Checks are written for GNU grep and name the behaviour they need.** + `-H` wherever a filename prefix is filtered on, since GNU grep prints + none for a single named file; `-e` before any pattern that starts with + `-`. Run them where `grep` is not wrapped by a shell function — for + example `env -i PATH=/usr/bin:/bin bash --noprofile --norc` — because + a wrapper that resolves to a different implementation answers a + different question. - **Every step states its before value and its after value.** The before values in this plan were measured against the files on 2026-09-17, not predicted. @@ -217,11 +234,11 @@ one from the other, and the cell is filled even then: a cell reading `rules/` says the convention was applied deliberately, while an empty one cannot be told apart from an omission. -`change` takes one of `new`, `changed`, `retired` or `unchanged`. The -set is closed and a domain adds no value to it: the column steers the -process, so its meaning belongs to the core, and a value outside the -set is an error the author corrects rather than a part that quietly -escapes the plan's coverage check. +`change` takes one value from the closed set `new | changed | retired | +unchanged`, and a domain adds no value to it: the column steers the +process, so its meaning belongs to the core, and a value outside the set +is an error the author corrects rather than a part that quietly escapes +the plan's coverage check. Tests are not a section. What verifies a contract is the contract's `check` column; when that check runs is the plan's business. @@ -264,12 +281,12 @@ kinds of contract, homes of state — from the Domain expertise duty the personas already carry, which has them scan and load the domain's own skills. No discovery convention of its own ships here. -A kind of part that no domain skill names is recorded in the document -as a **vocabulary gap**: the author names the kind, writes the gap -down, and the architect round judges the boundary, which is what it -judges in any case. Accumulated vocabulary gaps are either the -specification of a `-technical-design` skill or the evidence -that none is needed. +A kind of part, contract, or home of state that no domain skill names is +recorded in the document as a **vocabulary gap**: the author names the +kind, writes the gap down, and the architect round judges the boundary, +which is what it judges in any case. Accumulated vocabulary gaps are +either the specification of a `-technical-design` skill or the +evidence that none is needed. Where no skill covers the technology at all, the document is written from generic knowledge, best effort. That degradation path is what @@ -737,13 +754,15 @@ spec: ../specs/.md # plans only: the spec this plan implements; inline l with: ``` -spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several +spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several, on a plan technical-design: ../technical-designs/.md # optional, on a design spec and on a plan: the technical design that develops this design spec ``` - [ ] **Step 3: Write the five rules that keep the pair honest** -After the field notes, add: +The field notes are the bullets under the frontmatter example; the last +of them ends `omitted entirely when there is no topic branch.` Add after +that line: ``` - `technical-design:` is optional and appears once the technical design @@ -753,15 +772,18 @@ After the field notes, add: and it gives a reader holding the design spec the structure that develops it. The author who creates the design writes both ends in the same turn — the design's `spec:` and the design spec's - `technical-design:`. A plan keeps its own `spec:` and - `technical-design:`, and where it carries both they must agree with - the pair. One technical design per design spec. + `technical-design:`. A plan written from a design spec that names a + technical design carries that `technical-design:` as well as its + `spec:`, and the two name that audit pair; a plan written from a + design spec with no technical design carries no `technical-design:`. + One technical design per design spec. ``` - [ ] **Step 4: Say what the pointer does to a plan's interface blocks** A plan naming a technical design must not redefine what that design -already defines. Add after the five rules: +already defines. Add after Step 3's block, whose last line is +` One technical design per design spec.`: ``` - Where a plan's `technical-design:` names a document, that document @@ -785,6 +807,8 @@ grep -c 'plans and technical designs' $F # expect 1 grep -c 'plans only' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'they do not independently redefine those contracts' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'One technical design per design spec' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'carries that `technical-design:` as well as its' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'where it carries both they must agree' # expect 0 ``` The third check is the deletion assertion: the old `plans only` comment @@ -844,8 +868,9 @@ the bullet's real last line, `a typo fix included.`, and add after it: ``` A design spec that names a technical design is audited with it as one - target — an **audit pair** — and one stamp records it. The stamp lives on the design - spec, names both documents and both body hashes, and the technical + target — an **audit pair** — and one stamp records it. The stamp + lives on the design spec, names both documents and both body hashes, + and the technical design carries no `integrity:` of its own — it owes the check like any judged document and discharges it jointly. The value takes the form `integrity: (sha: ; with: @)`, @@ -878,7 +903,7 @@ The decline path takes the same widening and a bound. Replace: ``` dispatcher writes it in every case: the developer declining the gate's pair offer, written in the decline turn before the work that decline -licenses begins; +licenses begins; an integrity audit, written once its dispositions are ``` with: @@ -891,9 +916,15 @@ declined offer over an audit pair discharges the chain debt of both. What a decline never does is stand in for the audit itself: it writes no `integrity:` stamp, leaves every other open finding open, and closes no `blocking` verdict. "Do not run the audit" is a release from the -chain debt the offer named and from nothing else; +chain debt the offer named and from nothing else; an integrity audit, +written once its dispositions are ``` +Both anchors in this step end at a line's end: the one above ends where +the next anchor, `applied and before the `integrity:` stamp; …`, +begins, so the two replacements touch disjoint lines and neither leaves +a tail glued to the other. + - [ ] **Step 5: Verify both directions** Run: @@ -923,7 +954,9 @@ git commit -m "feat(working-process): record a pair audit in one integrity stamp paragraph **Interfaces:** -- Consumes: the class branches from Task 5. +- Consumes: the class branches from Task 5. **Task 5 must land first** + — its Step 4 rewrites the sentence this task anchors on, so the anchor + below is the post-Task-5 wording and does not exist before Task 5. - Produces: the ordering and the pass structure Task 10's gate offer fires inside. @@ -940,8 +973,8 @@ Expected: `0`. - [ ] **Step 2: Write the ordering and the passes** -After the paragraph ending `the held spec questions are asked before the -plan is written, whoever writes it.`, add: +After the paragraph ending `its held questions are asked before the plan +is written, whoever writes it.` — Task 5 Step 4's wording — add: ``` A technical design's own consumption gate is plan-writing, the same @@ -1026,7 +1059,7 @@ The duties are a table keyed by the edit, not a bullet list. Append a row after the `copied a citation out of a review report` row: ``` -| added or repointed a `spec:` or `technical-design:` pointer | both targets, and that each pointer names the document that names it; on a plan, that its `spec:` and `technical-design:` name a design spec and the design that design spec names | 9 | +| added or repointed a `spec:` or `technical-design:` pointer | both targets, and that each pointer names the document that names it; on a plan, that where its `spec:` target names a technical design the plan names the same one — a plan missing that pointer is a hit | 9 | ``` - [ ] **Step 4: Re-derive the two counters the row invalidates** @@ -1072,7 +1105,7 @@ grep -c '"docs/technical-designs/\*\*"' $F # expect 1 grep -c '^| ' $F # expect 11: header plus ten edit rows grep -c 'nine duties fall due' $F # expect 1 grep -c 'keyed by the ten edits' $F # expect 1 -grep -c 'walks the same nine' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'walks the same nine' # expect 1 grep -c 'eight duties\|the nine edits\|same eight' $F # expect 0 ``` @@ -1093,7 +1126,7 @@ git commit -m "feat(working-process): make the author check the pointer pair" **Files:** - Modify: `plugins/working-process/rules/workflow.md:36-56` (step 4, - *Spec → plan*) + *Spec → plan*), including the pair-offer sentence at `:43-45` **Interfaces:** - Consumes: the passes from Task 8. @@ -1186,32 +1219,59 @@ with: Where the technical-design offer is accepted, the audit waits: the design is the last producer of changes to its design spec, and the two are then audited as one target — an audit pair. The gate makes - one offer for that pair rather than one per document, and the offer + one offer for that audit pair rather than one per document, and the + offer names both documents, so declining it releases the chain debt of the two it named and nothing besides. Where the audit runs instead, it - discharges that debt for both documents it read. The brief + discharges that debt for both documents it read. The other arm stays + per-document: a full-document architect round discharges the debt of + the one document it read. The brief confirms the auditor's two preconditions: every edit from the ``` -- [ ] **Step 4: Verify** +- [ ] **Step 4: Name the class in the pair-offer sentence** + +The sentence that sends a diff-scoped chain to the pair offer still +says "a spec". Replace: + +``` + comparison. For a spec whose LGTM came from a diff-scoped chain the + offer takes the pair form the verdict-agent dispatch subsection + defines, and narrows as that definition says when the auditor is +``` + +with: + +``` + comparison. For a judged document whose LGTM came from a diff-scoped + chain the offer takes the pair-offer form the verdict-agent dispatch + subsection defines — one pair offer for an audit pair, never one per + document — and narrows as that definition says when the auditor is +``` + +The last line ends where the original did, so ` absent. The brief`, +Step 2's anchor, is untouched. + +- [ ] **Step 5: Verify** Run: ```bash F=plugins/working-process/rules/workflow.md -tr -s '[:space:]' ' ' < $F | grep -c 'system-designer-session` skill and write the technical design' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'the declaration is the signal and the marker only raises the question' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'audited as one target' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'from a `CLAUDE.md` note at the repository root' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'skill, when available, and write the technical design' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'never the work' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -o 'from generic knowledge and best effort' | wc -l # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'may name the markers of its technology' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'one offer for that pair' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'one offer for that audit pair' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'an audit pair' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document whose LGTM' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'For a spec whose LGTM' # expect 0 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill ``` -- [ ] **Step 5: Commit** +- [ ] **Step 6: Commit** ```bash git add plugins/working-process/rules/workflow.md @@ -1248,7 +1308,9 @@ Expected: `0`. - [ ] **Step 2: Write the authoring step** -After the offer paragraph, add: +The offer paragraph is the block Task 10 Step 2 inserted; its last line +is ` state.`, directly above the paragraph opening ` Where the +technical-design offer is accepted`. Add after that line: ``` Accepted, the session opens the `system-designer-session` skill when @@ -1260,6 +1322,9 @@ After the offer paragraph, add: agent is available, whose card admits any judged document dispatched standalone, with the propagation audit gating that dispatch as it gates every verdict dispatch. The plan-adversary stays on plans. + The plan written afterwards carries both `spec:` and + `technical-design:`, the second copied from its design spec, so the + plan-adversary always has the design to read. ``` Both tool mentions are conditional, as every neighbouring step in this @@ -1274,6 +1339,7 @@ Run: F=plugins/working-process/rules/workflow.md tr -s '[:space:]' ' ' < $F | grep -c 'writes both ends of the pair in the same turn' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'The plan-adversary stays on plans' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'the second copied from its design spec' # expect 1 ``` - [ ] **Step 4: Commit** @@ -1285,10 +1351,12 @@ git commit -m "feat(working-process): name the technical design's author and rev --- -### Task 12: The workflow rule — the triage clause reads a list +### Task 12: The workflow rule — the triage clause and the remaining class branches **Files:** -- Modify: `plugins/working-process/rules/workflow.md:333-339` +- Modify: `plugins/working-process/rules/workflow.md:333-339` (the + triage clause), `:169-171` (the Process directories resolved before a + verdict dispatch), `:526-537` (the chain-debt pair offer) **Interfaces:** - Consumes: `Origin` in its list form from Task 1. @@ -1300,11 +1368,12 @@ git commit -m "feat(working-process): name the technical design's author and rev Run: ```bash -sed -n '333,339p' plugins/working-process/rules/workflow.md +grep -n -e 'A finding whose `origin` names the spec' plugins/working-process/rules/workflow.md ``` -Expected: the clause opening `- A finding whose `origin` names the spec -is held unless a written` and carrying ``both` holds the same way`. +Expected: one line. The measurement is by content rather than by line +number, because Tasks 10 and 11 insert some forty lines above this +clause and a positional `sed` would print unrelated bullets. - [ ] **Step 2: Widen the hold to the class** @@ -1317,7 +1386,8 @@ Replace: line names in `options:` which half is fixable at once. A licensed spec-origin fix lands in the spec's own ledger and the plan's line points at it, by the cross-document clause the spec-plan-lifecycle - rule defines + rule defines — which also leaves the spec's `integrity:` stamp + stale, as any body edit does. ``` with: @@ -1330,10 +1400,76 @@ with: way, and its `held` line names in `options:` which part is fixable at once. A licensed fix lands in the ledger of the document that changed, and the plan's line points at it, by the cross-document - clause the spec-plan-lifecycle rule defines + clause the spec-plan-lifecycle rule defines — which also leaves the + design spec's `integrity:` stamp stale, as any body edit to either + document of an audit pair does. +``` + +The anchor runs to the end of the bullet, so the replacement ends a +sentence rather than leaving the original line's tail glued to its last +line. The tail itself changes too: for an audit pair the stamp that +goes stale is the design spec's, whichever document was edited. + +- [ ] **Step 3: Name the new directory before a verdict dispatch** + +Replace: + +``` +- Before dispatch, resolve any undecided Process directory + (`docs/specs/`, `docs/plans/`) so the first-create question cannot + interrupt the stamp turn. +``` + +with: + +``` +- Before dispatch, resolve any undecided Process directory + (`docs/specs/`, `docs/technical-designs/`, `docs/plans/`) so the + first-create question cannot interrupt the stamp turn. +``` + +- [ ] **Step 4: Name the class at the chain-debt pair offer** + +Each clause is considered on its own: the audit arm reads an audit pair +together, while the round arm stays per-document, and the paragraph +says both. It is rewrapped whole, since this task changes it. Replace: + +``` +For a spec, the consumption gate before plan-writing offers the pair as +one question — an integrity audit or a full-document round — and never +an offer followed by a re-offer of the option just declined. When the +`integrity-auditor` agent is absent the offer carries the full-document +round alone. The two arms cost differently and the offer says so: an +audit returns material for the dispatcher to dispose of and leaves the +verdict alone, while a full-document round on a spec is a new loop's +first round, since the spec's LGTM already closed its loop — it mints +its own verdict and stamps it, so a `concerns` there flips the field +back while plan-writing waits. The full-document-round arm therefore +blocks plan-writing; the audit arm does not, and plan-writing follows +its dispositions. +``` + +with: + +``` +For a judged document, the consumption gate before plan-writing makes +the pair offer as one question — an integrity audit or a full-document +round — and never an offer followed by a re-offer of the option just +declined. When the `integrity-auditor` agent is absent the offer +carries the full-document round alone. For an audit pair the gate makes +one pair offer for both documents: its audit arm reads the two +together, while its round arm stays per-document, one architect round +for each document it reviews. The two arms cost differently and the +offer says so: an audit returns material for the dispatcher to dispose +of and leaves the verdict alone, while a full-document round on a +judged document is a new loop's first round, since that document's +LGTM already closed its loop — it mints its own verdict and stamps it, +so a `concerns` there flips the field back while plan-writing waits. +The full-document-round arm therefore blocks plan-writing; the audit +arm does not, and plan-writing follows its dispositions. ``` -- [ ] **Step 3: Verify the retired value is gone** +- [ ] **Step 5: Verify the retired value and the old branches are gone** Run: @@ -1341,13 +1477,19 @@ Run: F=plugins/working-process/rules/workflow.md tr -s '[:space:]' ' ' < $F | grep -c 'names a judged document' # expect 1 grep -c '`both`' $F # expect 0 +grep -cF '(`docs/specs/`, `docs/technical-designs/`, `docs/plans/`) so the' $F # expect 1 +grep -cF '(`docs/specs/`, `docs/plans/`) so the' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document, the consumption gate' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'its round arm stays per-document' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'For a spec, the consumption gate' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'round on a spec is a new loop' # expect 0 ``` The second check is the deletion assertion: `both` retires with the value, and a surviving mention would send triage down a branch the adversary can no longer emit. -- [ ] **Step 4: Commit** +- [ ] **Step 6: Commit** ```bash git add plugins/working-process/rules/workflow.md @@ -1653,22 +1795,25 @@ Append to the generic dimensions: ``` ### 5. Coverage of the technical design's parts -Where the plan's `technical-design:` names a document, read its Parts -table. The plan must cover every part marked `new`, `changed` or -`retired`; one part may span several tasks and one task several parts, -and a part carried as `unchanged` context needs none. An uncovered part -is an Important finding whose `origin` is `implementation-plan`. A `change` value -outside `new | changed | retired | unchanged` is an Important finding -whose `origin` is `technical-design`: the set is closed, and an unknown -value would otherwise slip past this dimension unchecked. - -The `**Interfaces:**` blocks are read here too. Where the plan names a -technical design, those blocks reference the contracts that design +Where the plan's `technical-design:` names a document — or, failing +that, where its design spec's does — read that document's Parts table. +A plan whose design spec names a technical design the plan does not +carry is itself an Important finding whose `origin` is +`implementation-plan`. The plan must cover every part marked `new`, +`changed` or `retired`; one part may span several tasks and one task +several parts, and a part carried as `unchanged` context needs none. An +uncovered part is an Important finding whose `origin` is +`implementation-plan`. A `change` value outside +`new | changed | retired | unchanged` is an Important finding whose +`origin` is `technical-design`: the set is closed, and an unknown value +would otherwise slip past this dimension unchecked. + +The `**Interfaces:**` blocks are read here too. Where a technical design +applies to the plan, those blocks reference the contracts that design defines and say which part of one each task implements or changes; a block that redefines a contract independently is an Important finding whose `origin` is `implementation-plan`. Where no technical design is -named, the existing plan convention stands and this paragraph is -silent. +named, the existing plan convention stands and this paragraph is silent. ``` - [ ] **Step 6: Verify** @@ -1683,6 +1828,7 @@ grep -c '"origin": "plan"' $F # expect 0 grep -c '`spec` or `both`' $F # expect 0 grep -c 'spec-origin' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'Coverage of the technical design' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "or, failing that, where its design spec's does" # expect 1 grep -c '^### [0-9]' $F # expect 5 ``` @@ -1744,10 +1890,11 @@ reads. A plan is scoped differently and must not be read as half a pair. Its `spec:` names a design spec, which never names the plan back; what is -checked there is that its `spec:` and `technical-design:` name a design -spec and the technical design that design spec names. A plan whose two -pointers disagree with the pair is a hit; a plan whose `spec:` is not -named back is not. +checked there starts from the design spec: where the plan's `spec:` +target names a technical design, the plan must name the same one. A +plan that omits the pointer or names a different design is a hit — the +check reads the design spec's pointer, so a missing field is found +rather than skipped. A plan whose `spec:` is not named back is not. ``` The second paragraph is what keeps this duty from firing on every plan @@ -1765,6 +1912,7 @@ grep -c 'a spec or plan' $F # expect 0 grep -c '^### 9\. The pointer pair' $F # expect 1 grep -c '^### [0-9]' $F # expect 9 tr -s '[:space:]' ' ' < $F | grep -c 'is not named back is not' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "the check reads the design spec's pointer" # expect 1 ``` The count of `###` headings must equal the duty count the @@ -1832,7 +1980,8 @@ git commit -m "feat(working-process): cover technical designs in the status pass (the architect paraphrase), `:47-49` (the adversary paraphrase), `:53-54` (the propagation-auditor paraphrase), `:66-68` (the integrity-auditor paraphrase), `:133` (the `integrity` field row), - `:184-192` (the Rules payload enumeration and its count) + `:184-192` (the Rules payload enumeration and its count), `:243-244` + (the directories the plugin creates) **Interfaces:** - Consumes: every earlier task. This is the reader-facing summary and it @@ -1888,15 +2037,55 @@ and append to its `Meaning` cell: - [ ] **Step 5: Retire the premises in three more paraphrases** -Replace `handed a spec it declines toward the `architect`` with -`handed a judged document it declines toward the `architect``. +Each replacement covers whole lines and is rewrapped, so no line runs +past 72 columns and nothing is glued to the next line. Replace: + +``` + plans (plans only; handed a spec it declines toward the `architect` + agent). Generic failure-mode dimensions live here; domain specifics +``` + +with: + +``` + plans (plans only; handed a judged document it declines toward the + `architect` agent). Generic failure-mode dimensions live here; domain + specifics +``` + +Replace: + +``` +- **`propagation-auditor` agent** — the mechanical audit of a spec or + plan: it parses every changed interface to enumerate its consumers, +``` + +with: + +``` +- **`propagation-auditor` agent** — the mechanical audit of a design + spec, a technical design or a plan: it parses every changed interface + to enumerate its consumers, +``` + +Replace: + +``` + document, read on a fresh context: the document against itself, then + the document as an implementer who must build from that text alone. +``` + +with: -Replace `the mechanical audit of a spec or` with -`the mechanical audit of a design spec, a technical design or a`. +``` + document, read on a fresh context: the document against itself, then + the document as an implementer who must build from that text and + whatever was audited with it. +``` -Replace `the document as an implementer who must build from that text -alone.` with `the document as an implementer who must build from that -text and whatever was audited with it.` +The first two leave a short line where the original paragraph resumes; +that is the cost of an anchor ending at a line's end, and it keeps every +line within 72 columns. - [ ] **Step 6: Re-count the Rules payload, which Task 2 changed** @@ -1923,16 +2112,38 @@ edit that triggers them (`propagation-duties.md`, loaded while a design spec, technical design, plan or domain document is open), and the ``` -- [ ] **Step 7: Verify, and sweep the plugin for the retired phrase** +- [ ] **Step 7: Re-count the directories the plugin creates** + +Task 11 has the plugin's own step write into `docs/technical-designs/`, +and Task 3 makes it a Process directory with the first-create question, +so the README's count goes stale. Replace: + +``` +This plugin creates two directories in a project repo: `docs/domain/` +(glossary + ADRs) and `docs/code-review/` (Review reports — one per +``` + +with: + +``` +This plugin creates three directories in a project repo: +`docs/domain/` (glossary + ADRs), `docs/technical-designs/` (technical +designs, written when a project accepts the offer) and +`docs/code-review/` (Review reports — one per +``` + +- [ ] **Step 8: Verify, and sweep the plugin for the retired phrase** Run: ```bash -grep -rc 'design document' plugins/working-process/ | grep -v ':0$' +grep -rcH 'design document' plugins/working-process/ | grep -v ':0$' grep -c 'seven rule files' plugins/working-process/README.md # expect 1 grep -c 'six rule files' plugins/working-process/README.md # expect 0 ls plugins/working-process/rules/*.md | wc -l # expect 7 -grep -rc 'a spec or plan\|build from that text alone\|handed a spec' \ +grep -c 'creates three directories' plugins/working-process/README.md # expect 1 +grep -c 'creates two directories' plugins/working-process/README.md # expect 0 +grep -rcH 'a spec or plan\|build from that text alone\|handed a spec' \ plugins/working-process/README.md | grep -v ':0$' ``` @@ -1941,7 +2152,7 @@ count and the directory listing must agree — a README that counts its own payload wrong is the failure this check exists for. The first and last lines are the Global Constraint's closing sweep. -- [ ] **Step 8: Commit** +- [ ] **Step 9: Commit** ```bash git add plugins/working-process/README.md @@ -2051,8 +2262,8 @@ Add at the top of the changelog, under its one-line preamble: - `origin` on a plan-adversary finding is a list of named documents; `both` retires. - The class a design spec and a technical design share is named - `judged document` on every agent card and in the README; the phrase - those surfaces carried before is retired. + `judged document` on the three agent cards that carried the old + phrase and in the README; that phrase is retired. ``` - [ ] **Step 3: Set the dogfooding version** @@ -2351,3 +2562,132 @@ verified in the `superpowers:writing-plans` skill that the halfway down a list — and expects the confirming round to close on `concerns` or `LGTM`, with no material for a fourth absent new evidence. + +### 2026-09-24 — plan-adversary, fable 5.1, blocking (round 3, full-document) + +The confirming round the plan owed. Five Important, eleven Minor. +Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-3.md`. +The reviewer went past reading and simulated execution: it applied every +replacement of Tasks 2–19 in numbering order to copies of the files and +ran each published after-check. That found a class no before-state +measurement could see — a task whose anchor an earlier task destroys, +and checks that fail on the plan's own replacement text. Fifteen of +sixteen citations held against the files; the sixteenth is the grep +finding below, and it held too, in a different environment. + +- fixed 2026-09-24 — [Important] Task 8 anchored its insertion on a + sentence Task 5 Step 4 rewrites, and Task 5's own verify step asserts + that sentence is gone, so in numbering order the anchor no longer + existed when Task 8 ran; license: Task 5's deletion assertion, which + fixes what text survives; Task 8 anchors on Task 5's wording and its + Interfaces block states that Task 5 lands first. +- fixed 2026-09-24 — [Important] Task 2's check for the closed `change` + set grepped a spelling the prescribed rule text never used; license: + the design spec's spelling, `new | changed | retired | unchanged`, + which the adversary's dimension 5 also quotes; the rule text takes + that spelling, so rule, spec, card and check agree. +- fixed 2026-09-24 — [Important] Task 9 checked `walks the same nine` + with a plain grep while its replacement wraps between "same" and + "nine"; license: the plan's own Global Constraint that prose checks + normalize whitespace first; the check runs through `tr`. +- fixed 2026-09-24 — [Important] Task 10 still checked the pre-round-2 + wording beside the check that replaced it, so it could not pass, and + an implementer satisfying it would have deleted the conditional round + two fixed as Important; license: the deletion the round-two fix + implied and never made; the stale check is deleted. Third time in this + work a superseded assertion survived its replacement. +- fixed 2026-09-24 — [Important] nothing wrote `technical-design:` onto + a plan and neither duty 9 nor dimension 5 read its absence, so a plan + written from an audit pair that omitted the pointer escaped the + coverage check; ruling: 2026-09-24; a plan written from a design spec + that names a technical design carries that pointer, the plan-writing + step writes it, and both checks start from the design spec's pointer + — so an omitted field is found rather than skipped. A plan from a + design spec with no technical design carries none. +- fixed 2026-09-24 — [Minor] the `never the work` check was vacuous, the + phrase already standing in the file, and every `tr | grep -c` check + proves existence rather than occurrence; license: the check's own + purpose; it counts a phrase unique to the block with `grep -o … | wc + -l`, and a Global Constraint now says when to count that way. +- fixed 2026-09-24 — [Minor] an 86-character line in Task 7's block, and + two anchors ending mid-line that would glue the original line's tail + onto the block's last line; license: the plan's 72-column constraint; + the line is rewrapped, both anchors run to their line's end, and a + Global Constraint states the rule. Task 12's tail also carried a + binary — the stamp going stale is the design spec's for either + document of an audit pair — and says so now. +- fixed 2026-09-24 — [Minor] Task 12 measured its before-state by line + number, and Tasks 10 and 11 insert some forty lines above it; + license: the measurement's purpose; it greps the clause's content, + with `-e` since the clause opens with a dash. +- fixed 2026-09-24 — [Minor] the reviewer found that `grep -rc` on a + single named file prints a bare count, so the sweep's `grep -v ':0$'` + filter would not suppress it; ruling: 2026-09-24; both results are + true — GNU grep 3.11 prints a bare `0`, while this machine's `grep` is + a shell function resolving to ugrep 7.8.4, which prints the prefix. + The dispatcher's first reading, that the finding was refuted, was + wrong: it measured a different implementation. The sweeps take `-H`, + and a Global Constraint requires every check to name the behaviour it + needs and to run where `grep` is not wrapped. +- fixed 2026-09-24 — [Minor] three spec/plan binaries in `workflow.md` + stayed untouched — the pair-offer sentence, the Process directories + resolved before a verdict dispatch, and the chain-debt pair offer; + ruling: 2026-09-24; each is widened on its own terms, Task 10 taking + the first and Task 12 the other two, with deletion assertions. The + audit-pair exception is kept: the audit arm reads the pair together, + while the round arm stays per-document. +- fixed 2026-09-24 — [Minor] the README's "creates two directories" went + stale once the plugin's own step writes into `docs/technical-designs/`; + license: the count's own derivation; Task 18 gains a step and a check + for "three". +- fixed 2026-09-24 — [Minor] the changelog said the class is named on + every agent card, where three of six take it; license: the count; + the bullet names the three cards. +- fixed 2026-09-24 — [Minor] the rule defined a vocabulary gap as a kind + of part only, narrower than the glossary term it mints; ruling: + 2026-09-24; the rule and the design spec both take the glossary's + breadth — kinds of part, contract, and home of state. The spec change + is recorded in that spec's ledger under + `### 2026-09-24 — fix from docs/plans/2026-09-17-technical-design-step.md`. +- fixed 2026-09-24 — [Minor] the widened `spec:` comment admitted an + inline list on a technical design, which may name one design spec; + license: the plan's own "one technical design per design spec"; the + list clause is scoped to plans. +- fixed 2026-09-24 — [Minor] the one-offer sentence dropped the spec's + clause that the round arm stays per-document; license: the design + spec's own sentence; Task 10's block carries it. +- fixed 2026-09-24 — [Minor] "one offer for that pair" used the bare + word in the document sense three sentences from "the pair form" in the + question sense; license: the Audit pair entry's `_Avoid_`; the block + writes "audit pair" and its check follows. +After the wave, the dispatcher ran the sequential simulation the round +had shown was missing: every edit of Tasks 2–20 applied in numbering +order to copies of the files, each task's after-checks run in a clean +shell under GNU grep, and every edit-bearing step the parser could not +resolve reported rather than skipped. Its first run skipped one step +silently anyway — a filter meant for verify steps matched "validate" +inside "invalidates" — which was caught and fixed before any result was +trusted. The final run: 91 of 91 after-checks pass, no step +unsimulated, both sweeps silent, the plugin validates, and 64 of 65 +non-zero checks fail on the pre-plan files, the sixty-fifth being a +deliberate invariant. The simulation also found these: + +- hit fixed 2026-09-24 — three anchors named a position rather than + text: "After the field notes", "after the five rules", and "After the + offer paragraph", the last pointing at a block another task inserts; + each now quotes the exact line it follows. +- hit fixed 2026-09-24 — Task 18 Step 5 nested backticks inside inline + code, which Markdown cannot render and which left "handed a spec" + standing after the task ran; its three edits are block replaces over + whole lines. +- hit fixed 2026-09-24 — this wave's own edits left lines past 72 + columns in Task 2's rule text and Task 15's new dimension; each + paragraph is rewrapped whole. +- signal 2026-09-24 — the reviewer judges a further round worth its cost + only after this wave and only as a full-document round, since a plan's + loop cannot close on a diff-scoped one. It reads the five Important + findings as three classes — checks never run on their own replacement + text, an anchor an earlier task destroys, and one contract gap — and + expects the next full-document round to close on `LGTM` or + `concerns`. diff --git a/docs/specs/2026-09-16-technical-design-step.md b/docs/specs/2026-09-16-technical-design-step.md index a5379b2..5aaf31a 100644 --- a/docs/specs/2026-09-16-technical-design-step.md +++ b/docs/specs/2026-09-16-technical-design-step.md @@ -216,13 +216,13 @@ kinds of contract, homes of state — from the **Domain expertise** duty the personas already carry, which has them scan and load the domain's skills. No new discovery convention ships with this change. -A kind of part that no domain skill names is recorded in the document as -a **vocabulary gap** — a term this change mints, kept apart from the -review cascade's candidate gap, which is a graded and counted finding -with a reviewer for its producer. Accumulated vocabulary gaps are either -the specification of a `-technical-design` skill or the evidence -that none is needed; the decision is then made on evidence rather than -remembered. +A kind of part, contract, or home of state that no domain skill names is +recorded in the document as a **vocabulary gap** — a term this change +mints, kept apart from the review cascade's candidate gap, which is a +graded and counted finding with a reviewer for its producer. Accumulated +vocabulary gaps are either the specification of a +`-technical-design` skill or the evidence that none is needed; +the decision is then made on evidence rather than remembered. The core closes the three definitions, the table columns, the contract entry's fields, and the four questions of the failure section. A domain @@ -909,6 +909,13 @@ scope is the class, not the ledger's grammar. both senses stand close. Raised by the plan-adversary's round two against the plan; that plan's ledger line under its round-two heading points here. +- fixed 2026-09-24 — [Minor] this spec defined a vocabulary gap as a + kind of part no domain skill names, while the glossary entry this + change minted covers kinds of part, contract, and home of state, so an + author following the spec would record fewer gaps than the term it + mints; ruling: 2026-09-24; the sentence takes the glossary's breadth. + Raised by the plan-adversary's round three against the plan, whose + round-three ledger points here. This heading takes the cross-document fix shape the lifecycle rule defines, which that rule scopes to a document whose loop closed at From cd9a4cc3dde9653b3004b91d148c3f177eec58b4 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Thu, 24 Sep 2026 23:23:33 +0200 Subject: [PATCH 007/143] docs: count four cards and let a declaration live in either CLAUDE.md --- .../plans/2026-09-17-technical-design-step.md | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index ff6eb91..4513477 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -17,7 +17,7 @@ base: develop **Goal:** Ship the contract for a third document class — the technical design — across the `working-process` plugin: one new path-scoped rule, -six changed rules and skills, five changed agent cards and the plugin +six changed rules and skills, four changed agent cards and the plugin README, with the glossary and ADR 0004 already landed during spec authoring. @@ -1167,9 +1167,10 @@ with: At the same gate, and before the integrity audit above, offer the technical design when the repository is one that has code. A declaration the project records binds and is never re-asked, in - either direction; a session reads it from a `CLAUDE.md` note at the - repository root, or from the file such a note points at, which is - the shape available until a standing home for a project's process + either direction; a session reads it from the project instructions + Claude Code loads at session start — a `CLAUDE.md` at the repository + root or in `.claude/` — or from the file such a note points at, which + is the shape available until a standing home for a project's process answers exists. Without a declaration, a repository carrying a toolchain manifest — a file a language or platform toolchain reads to build, test or deploy it — gets the offer at every consumption @@ -1260,7 +1261,8 @@ Run: F=plugins/working-process/rules/workflow.md tr -s '[:space:]' ' ' < $F | grep -c 'the declaration is the signal and the marker only raises the question' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'audited as one target' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'from a `CLAUDE.md` note at the repository root' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'root or in `.claude/`' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'note at the repository root, or from' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'skill, when available, and write the technical design' # expect 1 tr -s '[:space:]' ' ' < $F | grep -o 'from generic knowledge and best effort' | wc -l # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'may name the markers of its technology' # expect 1 @@ -2684,6 +2686,21 @@ deliberate invariant. The simulation also found these: - hit fixed 2026-09-24 — this wave's own edits left lines past 72 columns in Task 2's rule text and Task 15's new dimension; each paragraph is rewrapped whole. +The propagation gate before round four found one more, and the +developer's question about which `CLAUDE.md` files load found another: + +- hit fixed 2026-09-24 — the Goal counted five changed agent cards where + the tasks change four — the architect, integrity-auditor, + plan-adversary and propagation-auditor cards; Task 9 edits a rule; the + Goal says four. +- hit fixed 2026-09-24 — Task 10 had a session read the declaration + from a `CLAUDE.md` "at the repository root", narrower than the design + spec, which names a `CLAUDE.md` note without a location. Measured the + same day: Claude Code loads `.claude/CLAUDE.md` at session start as + well, alone or beside a root `CLAUDE.md`, so a project keeping its + instructions there would have a declaration the rule's literal + wording never names. The sentence names both files, and its check + asserts the root-only wording is gone. - signal 2026-09-24 — the reviewer judges a further round worth its cost only after this wave and only as a full-document round, since a plan's loop cannot close on a diff-scoped one. It reads the five Important From 8cc820ac549bf5e502ccb069d3c42529f642f54e Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 08:01:55 +0200 Subject: [PATCH 008/143] docs: dispose the plan-adversary round four findings --- .../plans/2026-09-17-technical-design-step.md | 217 ++++++++++++++++-- .../specs/2026-09-16-technical-design-step.md | 11 +- 2 files changed, 211 insertions(+), 17 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index 4513477..23e402e 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -874,8 +874,11 @@ the bullet's real last line, `a typo fix included.`, and add after it: design carries no `integrity:` of its own — it owes the check like any judged document and discharges it jointly. The value takes the form `integrity: (sha: ; with: @)`, - where `` is the technical design's path relative to the design - spec. Changing either document unsettles the pair, and the gate + where `` is exactly the value the design spec's + `technical-design:` pointer carries, read relative to the design + spec's directory, so the gate can check that the recorded value still + equals the pointer. Changing either document unsettles the pair, and + the gate recomputes both hashes to see it; because the two pointers are what identify the pair, an added, removed or repointed `technical-design:` unsettles it too. @@ -935,6 +938,8 @@ grep -c 'with: @' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'carries no `integrity:` of its own' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'recomputes both hashes' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'both documents it read' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "exactly the value the design spec's" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "the technical design's path relative to the design" # expect 0 ``` - [ ] **Step 6: Commit** @@ -1059,7 +1064,7 @@ The duties are a table keyed by the edit, not a bullet list. Append a row after the `copied a citation out of a review report` row: ``` -| added or repointed a `spec:` or `technical-design:` pointer | both targets, and that each pointer names the document that names it; on a plan, that where its `spec:` target names a technical design the plan names the same one — a plan missing that pointer is a hit | 9 | +| added or repointed a `spec:` or `technical-design:` pointer | both targets; on a design spec or a technical design, that each pointer names the document that names it; on a plan, that where its `spec:` target names a technical design the plan names the same one — a plan missing that pointer is a hit | 9 | ``` - [ ] **Step 4: Re-derive the two counters the row invalidates** @@ -1104,6 +1109,7 @@ F=plugins/working-process/rules/propagation-duties.md grep -c '"docs/technical-designs/\*\*"' $F # expect 1 grep -c '^| ' $F # expect 11: header plus ten edit rows grep -c 'nine duties fall due' $F # expect 1 +grep -c 'on a design spec or a technical design, that each pointer' $F # expect 1 grep -c 'keyed by the ten edits' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'walks the same nine' # expect 1 grep -c 'eight duties\|the nine edits\|same eight' $F # expect 0 @@ -1126,7 +1132,8 @@ git commit -m "feat(working-process): make the author check the pointer pair" **Files:** - Modify: `plugins/working-process/rules/workflow.md:36-56` (step 4, - *Spec → plan*), including the pair-offer sentence at `:43-45` + *Spec → plan*), including the audit sentence at `:38-41` and the + pair-offer sentence at `:43-45` **Interfaces:** - Consumes: the passes from Task 8. @@ -1253,7 +1260,35 @@ with: The last line ends where the original did, so ` absent. The brief`, Step 2's anchor, is untouched. -- [ ] **Step 5: Verify** +- [ ] **Step 5: Let the audit sentence read the pair** + +Two lines above, the step still describes one document and one hash, +while Task 7 has the gate recompute both hashes of an audit pair. A +reader of this always-on rule alone would recompute the design spec's +hash, see a match, and never offer the audit after the technical design +changed. Replace: + +``` + is available: a fresh-context read of the whole spec, returning + defects and the questions an implementer would have to ask. The + offer fires unless a standing `integrity:` stamp still matches the + spec's recomputed body hash — a match means the standing stamp +``` + +with: + +``` + is available: a fresh-context read of the whole design spec, and of + its technical design where it names one, returning defects and the + questions an implementer would have to ask. The offer fires unless a + standing `integrity:` stamp still matches the recomputed body hash of + every document the stamp names — a match means the standing stamp +``` + +The last line ends as the original did, so the next line and Step 4's +anchor stay untouched. + +- [ ] **Step 6: Verify** Run: @@ -1270,10 +1305,12 @@ tr -s '[:space:]' ' ' < $F | grep -c 'one offer for that audit pair' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'an audit pair' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document whose LGTM' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'For a spec whose LGTM' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'every document the stamp names' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "spec's recomputed body hash" # expect 0 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill ``` -- [ ] **Step 6: Commit** +- [ ] **Step 7: Commit** ```bash git add plugins/working-process/rules/workflow.md @@ -1316,8 +1353,10 @@ technical-design offer is accepted`. Add after that line: ``` Accepted, the session opens the `system-designer-session` skill when - it is available and writes the document to `docs/technical-designs/`, - following the technical-design rule that loads there. It writes both + it is available and reads the technical-design rule, installed beside + this one, before drafting — a path-scoped rule loads only when a + matching file is read, and none exists yet — then writes the document + to `docs/technical-designs/` following it. It writes both ends of the pair in the same turn — the design's `spec:` and the design spec's `technical-design:` — so no audit ever meets a half-written pair. The reviewer is the `architect` agent when that @@ -1342,6 +1381,8 @@ F=plugins/working-process/rules/workflow.md tr -s '[:space:]' ' ' < $F | grep -c 'writes both ends of the pair in the same turn' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'The plan-adversary stays on plans' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'the second copied from its design spec' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'a path-scoped rule loads only when a matching file is read' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'following the technical-design rule that loads there' # expect 0 ``` - [ ] **Step 4: Commit** @@ -1715,7 +1756,8 @@ git commit -m "feat(working-process): audit a design spec and its technical desi **Files:** - Modify: `plugins/working-process/agents/plan-adversary.md:46`, `:49`, - `:51-55`, `:102`, and the generic dimensions list at `:57-70` + `:51-55`, `:102`, the generic dimensions list at `:57-70`, the + section heading at `:44` and the description at `:3` **Interfaces:** - Consumes: the triage clause from Task 12 and the `change` set from @@ -1752,6 +1794,31 @@ implementation plan)? Decline Replace `to a design document and would misfire as findings.` with `to a judged document and would misfire as findings.` +The section's heading and the card's description state the same +binary. Replace: + +``` +## Specs: decline +``` + +with: + +``` +## Judged documents: decline +``` + +and, in the `description:` field, replace: + +``` +Specs are out of scope — design review of a spec belongs to the architect agent. +``` + +with: + +``` +Judged documents — a design spec or a technical design — are out of scope; design review of one belongs to the architect agent. +``` + - [ ] **Step 3: Make `origin` a list** Replace: @@ -1829,6 +1896,9 @@ grep -c '"origin": \["design-spec"' $F # expect 1 grep -c '"origin": "plan"' $F # expect 0 grep -c '`spec` or `both`' $F # expect 0 grep -c 'spec-origin' $F # expect 0 +grep -c '^## Judged documents: decline' $F # expect 1 +grep -c '^## Specs: decline' $F # expect 0 +grep -c 'Specs are out of scope' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'Coverage of the technical design' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c "or, failing that, where its design spec's does" # expect 1 grep -c '^### [0-9]' $F # expect 5 @@ -1851,7 +1921,7 @@ git commit -m "feat(working-process): read origin as a list and cover the design **Files:** - Modify: `plugins/working-process/agents/propagation-auditor.md:3` - (description) and its duty list + (description), duty 5 at `:112-116`, and its duty list **Interfaces:** - Consumes: the pointers from Task 6. @@ -1903,7 +1973,38 @@ The second paragraph is what keeps this duty from firing on every plan in the repository: the reciprocity check belongs to the design spec and its technical design alone. -- [ ] **Step 4: Verify** +- [ ] **Step 4: Give duty 5 the sources of each document class** + +Duty 5 diffs a document's names against "the spec" and reports the rest +as spec gaps. A technical design mints part names by design, so read +literally the gate before every architect round on one would return a +hit per minted name. Replace: + +``` +Diff the names one document uses against the names its sources define. A +name the plan uses that the spec never defines is a spec gap, not a plan +error — the invention is the symptom, and report it as the gap it is. +``` + +with: + +``` +Diff the names one document uses against the names its sources define — +for a plan, the design spec and the technical design its `spec:` and +`technical-design:` name; for a technical design, its design spec and +the domain skills available to its author. A name a plan uses that no +source defines is a gap in the source, not a plan error — the invention +is the symptom, and report it as the gap it is. A technical design mints +part names by design: a name it introduces as its own, or records as a +vocabulary gap, is never a hit. A name it presents as coming from its +design spec or a domain skill is checked at that source like any other — +the exception covers what the design mints, never the names it borrows. +``` + +The last sentence is the boundary: without it the minting exception +would switch off name checking for a whole document class. + +- [ ] **Step 5: Verify** Run: @@ -1915,13 +2016,15 @@ grep -c '^### 9\. The pointer pair' $F # expect 1 grep -c '^### [0-9]' $F # expect 9 tr -s '[:space:]' ' ' < $F | grep -c 'is not named back is not' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c "the check reads the design spec's pointer" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'never the names it borrows' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'A name the plan uses that the spec never defines' # expect 0 ``` The count of `###` headings must equal the duty count the propagation-duties rule now states, or the rule's last column points at a duty that does not exist. -- [ ] **Step 5: Commit** +- [ ] **Step 6: Commit** ```bash git add plugins/working-process/agents/propagation-auditor.md @@ -2025,16 +2128,17 @@ Replace `or any design document dispatched standalone; verdict` with - [ ] **Step 4: Extend the `integrity` field row** -Replace the `Values` cell `` ` (sha: )` `` with +The row is replaced whole, since both its value and its meaning change +and a table row is one line. Replace: ``` -` (sha: [; with: @])` +| `integrity` | ` (sha: )` | last integrity audit — the date for the reader, the body hash for the check; the dispatcher writes it once the audit's dispositions land, and a spec's consumption gate recomputes the hash to decide whether the stamp still holds | ``` -and append to its `Meaning` cell: +with: ``` -; where a design spec names a technical design the two are audited as one target and one stamp records the pair, on the design spec +| `integrity` | ` (sha: [; with: @])` | last integrity audit — the date for the reader, the body hash for the check; the dispatcher writes it once the audit's dispositions land, and a judged document's consumption gate recomputes the hash of every document the stamp names to decide whether the stamp still holds; where a design spec names a technical design the two are audited as one target — an audit pair — and one stamp on the design spec records both | ``` - [ ] **Step 5: Retire the premises in three more paraphrases** @@ -2145,6 +2249,8 @@ grep -c 'six rule files' plugins/working-process/README.md # expect 0 ls plugins/working-process/rules/*.md | wc -l # expect 7 grep -c 'creates three directories' plugins/working-process/README.md # expect 1 grep -c 'creates two directories' plugins/working-process/README.md # expect 0 +grep -c 'recomputes the hash of every document the stamp names' plugins/working-process/README.md # expect 1 +grep -c "a spec's consumption gate recomputes the hash" plugins/working-process/README.md # expect 0 grep -rcH 'a spec or plan\|build from that text alone\|handed a spec' \ plugins/working-process/README.md | grep -v ':0$' ``` @@ -2333,6 +2439,15 @@ git commit -m "chore(working-process): changelog and dogfooding version for the divergence stands until that separate change lands. Task 20 follows the decision, not the current rule text. +- **A plan's `technical-design:` pointer is mandatory, not + conditional.** The design spec says a plan keeps its own `spec:` and + `technical-design:` and that, where it carries both, they must agree + with the pair. The developer's ruling of 2026-09-24 sharpens that: a + plan written from a design spec that names a technical design carries + that pointer, and its absence is a hit, found from the design spec's + side (Tasks 6, 9, 11, 15 and 16). Where this plan and the spec's + sentence differ on that point, the plan follows the ruling. + ## What this plan does not do - It creates no `docs/technical-designs/` directory in this repository. @@ -2708,3 +2823,73 @@ developer's question about which `CLAUDE.md` files load found another: text, an anchor an earlier task destroys, and one contract gap — and expects the next full-document round to close on `LGTM` or `concerns`. + +### 2026-09-24 — plan-adversary, fable 5.1, blocking (round 4, full-document) + +Full-document read. Two Important, five Minor. Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-4.md`. +Every citation held against the files it names; the one platform fact — +that a path-scoped rule loads when a matching file is read — the +reviewer checked against Claude Code's documentation rather than recall. + +- fixed 2026-09-25 — [Important] the propagation auditor's duty 5 read + every name against "the spec" and reported the rest as spec gaps, + while a technical design mints part names by design, so the gate + before each architect round on one would have returned a hit per + minted name; ruling: 2026-09-25; Task 16 gains a step giving duty 5 + each class's sources — for a plan, both documents its pointers name; + for a technical design, its design spec and the domain skills — with + minted part names and recorded vocabulary gaps exempt. The developer + set the boundary: names a design presents as coming from its design + spec or a domain skill are still checked at that source, so the + exemption never switches name checking off for the class. +- fixed 2026-09-25 — [Important] the authoring step relied on "the + technical-design rule that loads there", while a path-scoped rule + loads only when a matching file is read and none exists at the moment + of writing; license: the design spec's own sentence that the session + reads the path-scoped rule once the offer is accepted and it sits down + to write; Task 11 has the session read the rule before drafting. +- fixed 2026-09-25 — [Minor] the always-on audit sentence and the + README's `integrity` row still described one hash and one document + after Task 7 made the gate recompute both hashes of an audit pair; + license: Task 7's own statement; Task 10 gains a step widening the + sentence — which also read "the whole spec" where an audit pair has + two documents — and Task 18 replaces the row whole, each with a + deletion assertion. +- fixed 2026-09-25 — [Minor] the plan-adversary card's heading and + description kept "Specs" over a paragraph that now declines judged + documents; license: ADR 0004, where a branch names its class rather + than a filename; Task 15 renames both, with deletion assertions. +- fixed 2026-09-25 — [Minor] the duty table's new row demanded + reciprocity of every pointer, without the plan carve-out the card + states; license: the card's own clause under the 2026-09-24 ruling; + the row scopes reciprocity to a design spec or a technical design. +- fixed 2026-09-25 — [Minor] `` in `with:` was defined as a + path relative to the design spec while the spec's example carried a + bare filename; ruling: 2026-09-25; `` is exactly the value of + the design spec's `technical-design:` pointer, read relative to the + design spec's directory, so the gate can check the recorded value + against the pointer. The spec's example is corrected in that spec's + ledger under + `### 2026-09-24 — fix from docs/plans/2026-09-17-technical-design-step.md`. +- fixed 2026-09-25 — [Minor] the 2026-09-24 ruling that makes a plan's + `technical-design:` pointer mandatory sharpens the design spec's + conditional, yet nothing recorded it while the Global Constraints say + the spec wins where the two disagree; license: those constraints, + which name the Deviations section as the one place a departure is + recorded; a Deviations bullet cites the ruling. +The simulation, rebuilt in a fresh session from its memory entry, ran +after the wave: 106 of 106 after-checks pass, no step unsimulated, 71 of +72 non-zero checks fail on the pre-plan files (the seventy-second the +deliberate invariant), no new line past 72 columns, both sweeps silent, +the plugin validates. The rebuild first lost one anchor shape the old +parser knew, reported the step as unsimulated rather than passing it, +and was fixed before the result was read. A simulation proves the edits +land and their checks pass; it does not find a consumer no task names, +which is what this round's two Important findings were. + +- signal 2026-09-24 — the reviewer judges another round worth its cost + only after this wave and only as a full-document round, and expects it + to close on `LGTM` or `concerns`. It reads both Important findings as + a class earlier rounds met too: a consumer no task enumerated, and a + delivery mechanism assumed rather than read. diff --git a/docs/specs/2026-09-16-technical-design-step.md b/docs/specs/2026-09-16-technical-design-step.md index 5aaf31a..17984b0 100644 --- a/docs/specs/2026-09-16-technical-design-step.md +++ b/docs/specs/2026-09-16-technical-design-step.md @@ -314,7 +314,8 @@ owes the check like any judged document and discharges it jointly, which ADR 0004's principle allows, since the class decides what a document owes and not how many dispatches discharge it. - integrity: 2026-09-16 (sha: abc1234; with: x-technical-design.md@def5678) + technical-design: ../technical-designs/x-technical-design.md + integrity: 2026-09-16 (sha: abc1234; with: ../technical-designs/x-technical-design.md@def5678) Changing either document unsettles the pair, and the gate recomputes both hashes to see it. The two pointers are what identify the pair, so @@ -916,6 +917,14 @@ scope is the class, not the ledger's grammar. mints; ruling: 2026-09-24; the sentence takes the glossary's breadth. Raised by the plan-adversary's round three against the plan, whose round-three ledger points here. +- fixed 2026-09-25 — [Minor] the worked stamp example carried a bare + filename after `with:`, while the lifecycle rule the plan prescribes + defines that value from the design spec's pointer, so the two gave one + string two derivations; ruling: 2026-09-25; `with:` carries exactly + the value of the design spec's `technical-design:` pointer, read + relative to the design spec's directory, and the example now shows + both lines. Raised by the plan-adversary's round four against the + plan. This heading takes the cross-document fix shape the lifecycle rule defines, which that rule scopes to a document whose loop closed at From 3f6513746a8038b34600ae665842e74957981886 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 08:23:17 +0200 Subject: [PATCH 009/143] docs: dispose the plan-adversary round five findings --- .../plans/2026-09-17-technical-design-step.md | 384 ++++++++++++++++-- 1 file changed, 348 insertions(+), 36 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index 23e402e..8685e18 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2,7 +2,7 @@ ticket: none date: 2026-09-17 status: draft -adversary: blocking +adversary: concerns spec: ../specs/2026-09-16-technical-design-step.md branch: feature/technical-design-step base: develop @@ -277,16 +277,18 @@ text would leave an implementer to decide. ## Domain vocabulary The author takes the part vocabulary — kinds of part, placement rules, -kinds of contract, homes of state — from the Domain expertise duty the -personas already carry, which has them scan and load the domain's own -skills. No discovery convention of its own ships here. +kinds of contract, homes of state — from the domain's own skills: +through the Domain expertise duty the personas carry when the +working-process plugin is installed, which has them scan and load those +skills, and by reading the skills directly otherwise. No discovery +convention of its own ships here. A kind of part, contract, or home of state that no domain skill names is recorded in the document as a **vocabulary gap**: the author names the -kind, writes the gap down, and the architect round judges the boundary, -which is what it judges in any case. Accumulated vocabulary gaps are -either the specification of a `-technical-design` skill or the -evidence that none is needed. +kind, writes the gap down, and the architect round, when that agent is +available, judges the boundary, which is what it judges in any case. +Accumulated vocabulary gaps are either the specification of a +`-technical-design` skill or the evidence that none is needed. Where no skill covers the technology at all, the document is written from generic knowledge, best effort. That degradation path is what @@ -310,6 +312,9 @@ Run: F=plugins/working-process/rules/technical-design.md grep -c '^| [0-9]' $F # skeleton rows grep -c 'new | changed | retired | unchanged' $F +tr -s '[:space:]' ' ' < $F | grep -c 'when that agent is available, judges the boundary' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'personas carry when the working-process plugin is installed' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'the personas already carry' # expect 0 awk 'length > 72' $F | grep -v '^|' | wc -l # over-wide non-table lines ``` @@ -402,6 +407,7 @@ git commit -m "feat(working-process): count docs/technical-designs among Process **Files:** - Modify: `plugins/working-process/rules/ticket-frontmatter.md:13-15` + and the ticket-sourcing sentence at `:54` **Interfaces:** - Consumes: the directory named in Task 3. @@ -439,7 +445,22 @@ with: spec-plan-lifecycle rule. ``` -- [ ] **Step 3: Verify the catch-all no longer claims the directory** +- [ ] **Step 3: Let a technical design inherit its design spec's ticket** + +Replace: + +``` +for one unit of work — a plan inherits its spec's ticket, and artifacts +``` + +with: + +``` +for one unit of work — a technical design and a plan inherit their +design spec's ticket, and artifacts +``` + +- [ ] **Step 4: Verify the catch-all no longer claims the directory** Run: @@ -447,12 +468,14 @@ Run: F=plugins/working-process/rules/ticket-frontmatter.md tr -s '[:space:]' ' ' < $F | grep -c 'technical designs and plans' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'Every other document under `docs/`' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "a technical design and a plan inherit their design spec's ticket" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "a plan inherits its spec's ticket" # expect 0 ``` The second still stands: it is a catch-all, and the first bullet now takes the new directory out of its reach. -- [ ] **Step 4: Commit** +- [ ] **Step 5: Commit** ```bash git add plugins/working-process/rules/ticket-frontmatter.md @@ -665,7 +688,14 @@ with: - [ ] **Step 6: State that a technical design is not grilled** -After the offers paragraph, add: +After the lines: + +``` +written, while only the frontmatter stamp waits for the confirming +full-document round. +``` + +which close the offers paragraph, add: ``` A technical design is a judged document and takes the design spec's side @@ -956,7 +986,8 @@ git commit -m "feat(working-process): record a pair audit in one integrity stamp **Files:** - Modify: `plugins/working-process/rules/spec-plan-lifecycle.md`, Lifecycle offers section, after the consumption-gate backstop - paragraph + paragraph, and the two authoring enumerations at `:513-515` and + `:546-547` **Interfaces:** - Consumes: the class branches from Task 5. **Task 5 must land first** @@ -978,8 +1009,14 @@ Expected: `0`. - [ ] **Step 2: Write the ordering and the passes** -After the paragraph ending `its held questions are asked before the plan -is written, whoever writes it.` — Task 5 Step 4's wording — add: +After the lines: + +``` +latest. Writing a plan from a judged document is that same seam: its +held questions are asked before the plan is written, whoever writes it. +``` + +which are Task 5 Step 4's wording, add: ``` A technical design's own consumption gate is plan-writing, the same @@ -1004,7 +1041,40 @@ the same turn and by the same hand, since otherwise the rule freezing an implemented document's body never reaches it. ``` -- [ ] **Step 3: Verify** +- [ ] **Step 3: Name the technical design in two authoring enumerations** + +Two sentences further down the section list the authoring phase as +spec and plan alone. Replace: + +``` +is about to start. During authoring — spec drafting, grilling, review +rounds, plan writing — it never makes that suggestion; the documents' +``` + +with: + +``` +is about to start. During authoring — spec drafting, grilling, +technical-design writing, review rounds, plan writing — it never makes +that suggestion; the documents' +``` + +and replace: + +``` +The loop runs on that branch for the whole authoring phase — the spec's +rounds and the plan's alike — and the topic branch takes it at the +``` + +with: + +``` +The loop runs on that branch for the whole authoring phase — the design +spec's rounds, the technical design's and the plan's alike — and the +topic branch takes it at the +``` + +- [ ] **Step 4: Verify** Run: @@ -1013,9 +1083,13 @@ F=plugins/working-process/rules/spec-plan-lifecycle.md tr -s '[:space:]' ' ' < $F | grep -c 'The gate therefore runs in passes' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'precedes the joint integrity audit' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'in the same turn and by the same hand' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'technical-design writing, review rounds' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "the technical design's and the plan's alike" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'spec drafting, grilling, review rounds' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c "the spec's rounds and the plan's alike" # expect 0 ``` -- [ ] **Step 4: Commit** +- [ ] **Step 5: Commit** ```bash git add plugins/working-process/rules/spec-plan-lifecycle.md @@ -1028,7 +1102,7 @@ git commit -m "feat(working-process): run the consumption gate in passes" **Files:** - Modify: `plugins/working-process/rules/propagation-duties.md:1-6` - (`paths:`) and its duty list + (`paths:`), its duty list, and the loads-at sentence at `:38-39` **Interfaces:** - Consumes: the pointers from Task 6. @@ -1100,7 +1174,25 @@ When the `propagation-auditor` agent is available it walks the same nine as a gate, and its card is their definition and keeps the ``` -- [ ] **Step 5: Verify, both counters against what they count** +- [ ] **Step 5: Re-state where the rule loads** + +Step 2 widens `paths:`, and the rule states its own loading in prose. +Replace: + +``` +shipped occurrence of the banned term — which is why this rule loads at +the domain directory as well as at specs and plans. And a count asserted +``` + +with: + +``` +shipped occurrence of the banned term — which is why this rule loads at +the domain directory as well as at design specs, technical designs and +plans. And a count asserted +``` + +- [ ] **Step 6: Verify, both counters against what they count** Run: @@ -1110,6 +1202,8 @@ grep -c '"docs/technical-designs/\*\*"' $F # expect 1 grep -c '^| ' $F # expect 11: header plus ten edit rows grep -c 'nine duties fall due' $F # expect 1 grep -c 'on a design spec or a technical design, that each pointer' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'as well as at design specs, technical designs and plans' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'as well as at specs and plans' # expect 0 grep -c 'keyed by the ten edits' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'walks the same nine' # expect 1 grep -c 'eight duties\|the nine edits\|same eight' $F # expect 0 @@ -1119,7 +1213,7 @@ The last line is the deletion assertion, and it is the one that matters: the old counters read as true prose and nothing but a recount catches them. -- [ ] **Step 6: Commit** +- [ ] **Step 7: Commit** ```bash git add plugins/working-process/rules/propagation-duties.md @@ -1327,10 +1421,11 @@ git commit -m "feat(working-process): offer the technical design at the consumpt **Interfaces:** - Consumes: the offer from Task 10, the pointers from Task 6, and the - architect card sentence Task 13 writes. **Task 13 must land first** — - this task quotes that card, and quoting a phrase the card does not yet - carry is the propagation duty on prescribed blocks failing by our own - hand. Execute 13 before 11, or swap their numbers. + architect card as Task 13 leaves it. The block paraphrases that card + rather than quoting it, and the two tasks edit different files with + no shared anchor, so numbering order is safe: between them the rule + describes a card one task ahead of it, and nothing reads the card in + that interval. - Produces: the one turn in which both ends of the pair are written, so Task 9's duty never finds a half-written pair. @@ -1399,7 +1494,8 @@ git commit -m "feat(working-process): name the technical design's author and rev **Files:** - Modify: `plugins/working-process/rules/workflow.md:333-339` (the triage clause), `:169-171` (the Process directories resolved before a - verdict dispatch), `:526-537` (the chain-debt pair offer) + verdict dispatch), `:526-537` (the chain-debt pair offer), and three + enumerations at `:127-128`, `:131-132` and `:149-150` **Interfaces:** - Consumes: `Origin` in its list form from Task 1. @@ -1512,7 +1608,55 @@ The full-document-round arm therefore blocks plan-writing; the audit arm does not, and plan-writing follows its dispositions. ``` -- [ ] **Step 5: Verify the retired value and the old branches are gone** +- [ ] **Step 5: Name the technical design in three enumerations** + +None is a branch, but each reads as a complete list, and the technical +design belongs in all three: the glossary binds it, it is prose under +`docs/`, and Task 4 gives it the branch fields. Replace: + +``` +terms and `_Avoid_` bans bind specs, plans, code identifiers, and +reviews. +``` + +with: + +``` +terms and `_Avoid_` bans bind design specs, technical designs, plans, +code identifiers, and reviews. +``` + +Replace: + +``` +available, prose artifacts under `docs/` — specs, plans, ADRs, the +glossary — get its pass: invoke it before drafting a new document, and +``` + +with: + +``` +available, prose artifacts under `docs/` — design specs, technical +designs, plans, ADRs, the glossary — get its pass: invoke it before +drafting a new document, and +``` + +Replace: + +``` +name and this convention does not govern it. The work's spec and plan +record the result in their `branch:` field — the topic branch, not the +``` + +with: + +``` +name and this convention does not govern it. The work's design spec, +technical design and plan record the result in their `branch:` field — +the topic branch, not the +``` + +- [ ] **Step 6: Verify the retired value and the old branches are gone** Run: @@ -1526,13 +1670,19 @@ tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document, the consumption gat tr -s '[:space:]' ' ' < $F | grep -c 'its round arm stays per-document' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'For a spec, the consumption gate' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'round on a spec is a new loop' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'bind design specs, technical designs, plans' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'bind specs, plans, code identifiers' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'design specs, technical designs, plans, ADRs' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'specs, plans, ADRs, the glossary' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c "The work's design spec, technical design and plan" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "The work's spec and plan" # expect 0 ``` The second check is the deletion assertion: `both` retires with the value, and a surviving mention would send triage down a branch the adversary can no longer emit. -- [ ] **Step 6: Commit** +- [ ] **Step 7: Commit** ```bash git add plugins/working-process/rules/workflow.md @@ -1604,7 +1754,7 @@ auditor hunts. **Files:** - Modify: `plugins/working-process/agents/integrity-auditor.md:3` (description), `:76-81` (Target and moment), `:101-103` (the second - lens), `:133-137` (the coverage tell) + lens), `:120-122` (the defect entry), `:133-137` (the coverage tell) **Interfaces:** - Consumes: the pointers from Task 6 and the stamp from Task 7. @@ -1726,7 +1876,25 @@ one risk peculiar to a pair, and a single pair of numbers cannot show it. ``` -- [ ] **Step 6: Verify all three edits landed and the old text is gone** +- [ ] **Step 6: Let a defect entry name its file on a joint target** + +The coverage tell reports per document; a defect entry must too, or a +defect found in either document of an audit pair cannot say which one +carries it. Replace: + +``` +Then the defects, one entry each: +``` + +with: + +``` +Then the defects, one entry each — on a joint target the entry opens +with the file, `:
`, so a defect found in either +document of an audit pair says which one carries it: +``` + +- [ ] **Step 7: Verify all three edits landed and the old text is gone** Run: @@ -1737,13 +1905,14 @@ tr -s '[:space:]' ' ' < $F | grep -c 'Four cases fix what you read' # exp tr -s '[:space:]' ' ' < $F | grep -c 'never a target alone' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'the documents audited with it' # expect 1 grep -c 'coverage: ' $F # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'document of an audit pair says which one carries it' # expect 1 grep -c 'coverage: ' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'Your primary target is a spec,' # expect 0 ``` The last two are deletion assertions. -- [ ] **Step 7: Commit** +- [ ] **Step 8: Commit** ```bash git add plugins/working-process/agents/integrity-auditor.md @@ -1859,7 +2028,14 @@ licenses the edit. - [ ] **Step 5: Add the coverage read of the `change` column** -Append to the generic dimensions: +After the lines: + +``` + broke?* Name it — the plan should have pre-empted it. Record it as a + finding. +``` + +which close dimension 4, add: ``` ### 5. Coverage of the technical design's parts @@ -1947,8 +2123,13 @@ a technical design or a plan before an expensive dispatch`. - [ ] **Step 3: Add the pointer-pair duty as a ninth numbered duty** The card's duties are `### 1.` through `### 8.` headings, not bullets, -and the rule's table numbers its rows against them. Add after duty 8's -last paragraph: +and the rule's table numbers its rows against them. After the line: + +``` +it rather than assuming either way. +``` + +which closes duty 8, add: ``` ### 9. The pointer pair @@ -2087,6 +2268,8 @@ git commit -m "feat(working-process): cover technical designs in the status pass integrity-auditor paraphrase), `:133` (the `integrity` field row), `:184-192` (the Rules payload enumeration and its count), `:243-244` (the directories the plugin creates) +- Modify: `plugins/working-process/skills/grilling-session/SKILL.md:8-13` + (its copy of the flow line and its scope sentence) **Interfaces:** - Consumes: every earlier task. This is the reader-facing summary and it @@ -2238,7 +2421,37 @@ designs, written when a project accepts the offer) and `docs/code-review/` (Review reports — one per ``` -- [ ] **Step 8: Verify, and sweep the plugin for the retired phrase** +- [ ] **Step 8: Bring the grilling-session skill's copy of the flow along** + +The skill carries its own copy of the flow line and a scope sentence; +after Step 2 the two copies would disagree, and neither says that a +technical design is not grilled. In +`plugins/working-process/skills/grilling-session/SKILL.md`, replace: + +``` +idea → brainstorming (spec) → **grilling-session on the spec** → +architect review (an `architect` agent dispatch) → writing-plans (plan) → +plan-adversary on the plan → implementation → code review. Offer a +grilling once a spec +exists and before its implementation plan is written. Specs are the +primary target; plans and raw ideas are in scope too. +``` + +with: + +``` +idea → brainstorming (design spec) → **grilling-session on the spec** → +architect review (an `architect` agent dispatch) → technical design +(when the repository has code) → writing-plans (plan) → plan-adversary +on the plan → implementation → code review. Offer a grilling once a +design spec exists and before its implementation plan is written. +Design specs are the primary target; plans and raw ideas are in scope +too. A technical design is not a grilling target: it mints no +terminology, and its vocabulary comes from the design spec, which was +grilled. +``` + +- [ ] **Step 9: Verify, and sweep the plugin for the retired phrase** Run: @@ -2251,6 +2464,9 @@ grep -c 'creates three directories' plugins/working-process/README.md # expect grep -c 'creates two directories' plugins/working-process/README.md # expect 0 grep -c 'recomputes the hash of every document the stamp names' plugins/working-process/README.md # expect 1 grep -c "a spec's consumption gate recomputes the hash" plugins/working-process/README.md # expect 0 +tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c 'A technical design is not a grilling target' # expect 1 +tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c 'technical design (when the repository has code)' # expect 1 +tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c 'agent dispatch) → writing-plans' # expect 0 grep -rcH 'a spec or plan\|build from that text alone\|handed a spec' \ plugins/working-process/README.md | grep -v ':0$' ``` @@ -2260,7 +2476,7 @@ count and the directory listing must agree — a README that counts its own payload wrong is the failure this check exists for. The first and last lines are the Global Constraint's closing sweep. -- [ ] **Step 9: Commit** +- [ ] **Step 10: Commit** ```bash git add plugins/working-process/README.md @@ -2299,7 +2515,13 @@ grep -n '^## ' CLAUDE.md - [ ] **Step 2: Write the declaration** -Add a section after `## Worktrees and topic branches`: +After the line: + +``` +one per topic branch, git-ignored. +``` + +which closes the `## Worktrees and topic branches` section, add: ```markdown ## Technical designs @@ -2354,7 +2576,13 @@ grep '"version"' plugins/working-process/.claude-plugin/plugin.json - [ ] **Step 2: Write the `## Unreleased` entry** -Add at the top of the changelog, under its one-line preamble: +After the line: + +``` +Released versions of this plugin, newest first. +``` + +which is the changelog's preamble, add: ```markdown ## Unreleased @@ -2448,6 +2676,22 @@ git commit -m "chore(working-process): changelog and dogfooding version for the side (Tasks 6, 9, 11, 15 and 16). Where this plan and the spec's sentence differ on that point, the plan follows the ruling. +- **The technical-design rule words two component mentions + conditionally where the design spec states them plainly.** The spec + says the author takes vocabulary from "the Domain expertise duty the + personas already carry" and that "the architect round judges the + boundary". The spec describes a design; the rule Task 2 writes is a + committed project-level rule that loads for people without the + plugin, and the repository's plugin-authoring rule requires skill and + agent mentions in such text to be conditional. The rule says "when the + working-process plugin is installed" and "when that agent is + available"; the design is unchanged. +- **The grilling-session skill is edited though the spec's Parts table + names no row for it.** It carries a copy of the flow line the README + carries, and once Task 18 changes one copy the two would disagree, so + Task 18 changes both. The spec's own Lifecycle sentence — a technical + design is not grilled — supplies the one sentence the skill gains. + ## What this plan does not do - It creates no `docs/technical-designs/` directory in this repository. @@ -2893,3 +3137,71 @@ which is what this round's two Important findings were. to close on `LGTM` or `concerns`. It reads both Important findings as a class earlier rounds met too: a consumer no task enumerated, and a delivery mechanism assumed rather than read. + +### 2026-09-25 — plan-adversary, fable 5.1, concerns (round 5, full-document) + +Full-document read, briefed to enumerate the consumers of every contract +the plan changes across the whole repository before reporting findings. +The enumeration covers ten contracts over all four plugins, personas, +skills, scripts and CI, each consumer mapped to a task or shown +unaffected; it is in the record. One Important, six Minor. Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-5.md`. +Every citation held. + +- fixed 2026-09-25 — [Important] the new rule's text named plugin + components unconditionally — the personas' Domain expertise duty and + the architect round — in a committed project-level rule that loads for + people without the plugin, the class round one fixed in Tasks 10 and + 11 and left in Task 2; license: the repository's plugin-authoring + rule; both mentions are conditional, Task 2's checks assert it, and a + Deviations bullet records why the rule words them differently from the + spec's prose. +- fixed 2026-09-25 — [Minor] the grilling-session skill carries its own + copy of the flow line and a scope sentence, which would disagree with + the README once Task 18 changed its copy; license: the design spec's + Lifecycle sentence that a technical design is not grilled; Task 18 + gains a step for the skill, and a Deviations bullet records that the + spec's Parts table names no row for it. +- fixed 2026-09-25 — [Minor] the propagation-duties rule's own sentence + about where it loads stayed "specs and plans" after Task 9 widens its + `paths:`; license: that widening; Task 9 gains a step and a deletion + assertion. +- fixed 2026-09-25 — [Minor] six enumerations read as complete lists of + spec and plan though the technical design belongs in each — the + glossary's binding, the style pass, the branch field, two authoring + enumerations, and ticket inheritance; license: ADR 0004's class and + the fields Task 4 gives the technical design; Tasks 4, 8 and 12 each + gain a step, every site with a deletion assertion. +- fixed 2026-09-25 — [Minor] on an audit pair a defect entry named no + file, so a defect in either document could not say which; license: the + design spec's "it may report defects in either", and the coverage tell + already reporting per document; Task 14 gains a step. +- fixed 2026-09-25 — [Minor] four insertion instructions still named a + position, and one, read literally, would put the declaration inside + the section it was meant to follow; license: the propagation duty that + an anchor is byte-exact; all five insertions of that shape — the four + named and the changelog's — now quote the lines they follow in a + fenced block. The reviewer's own suggested anchor for the offers + paragraph, `full-document round.`, occurs three times in that file, + so the block quotes two lines instead. +- fixed 2026-09-25 — [Minor] Task 11 left the implementer to choose + whether to run Task 13 first; license: the plan's own anchors, which + show no shared text between the two tasks; the Interfaces block says + numbering order is safe and why. + +The simulation ran after the wave: 127 of 127 after-checks pass, no step +unsimulated, 83 of 84 non-zero checks fail on the pre-plan files (the +last the deliberate invariant), no new line past 72 columns once one +glued tail was rewrapped, both sweeps silent, the plugin validates. The +finding about positional anchors exposed a fault in the simulator as +well: it had carried special cases that encoded what those instructions +meant rather than what they said, so it passed text an implementer +reading literally would have misapplied. Those cases are removed; the +simulator now accepts one explicit shape, "After the lines:" with the +anchor quoted in a block, and applies it byte-exactly. + +- signal 2026-09-25 — the reviewer judges another round worth its cost + only as a diff-scoped pass over this wave, followed by the confirming + full-document round, which it expects to close on `LGTM`; the consumer + surface has now been walked plugin-wide, and a further full read for + its own sake would find no more than this round did. From efcfba26236ae8c9babfb9ccdefc88e46bb2df07 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 08:37:56 +0200 Subject: [PATCH 010/143] docs: dispose the plan-adversary round six findings --- .../plans/2026-09-17-technical-design-step.md | 107 +++++++++++++++--- 1 file changed, 92 insertions(+), 15 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index 8685e18..36b860b 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2,7 +2,7 @@ ticket: none date: 2026-09-17 status: draft -adversary: concerns +adversary: blocking spec: ../specs/2026-09-16-technical-design-step.md branch: feature/technical-design-step base: develop @@ -63,6 +63,16 @@ verification. example `env -i PATH=/usr/bin:/bin bash --noprofile --norc` — because a wrapper that resolves to a different implementation answers a different question. +- **Every check carries its expected value as `# expect N`.** A check + whose expectation is stated only in prose is a check no tool runs; one + such check in Task 2 failed unnoticed for two rounds. The one + exception is `claude plugin validate`, whose result is its exit + status. +- **An inserted block stands on its own.** A block placed with "After + the line:" or "After the lines:" — one spelling per line count, the + same instruction — is a paragraph or heading of its own, with one + blank line before and after it. A table row or list item inserted by + its own instruction joins its table or list with no blank line. - **Every step states its before value and its after value.** The before values in this plan were measured against the files on 2026-09-17, not predicted. @@ -310,16 +320,17 @@ Run: ```bash F=plugins/working-process/rules/technical-design.md -grep -c '^| [0-9]' $F # skeleton rows -grep -c 'new | changed | retired | unchanged' $F +grep -c '^| [0-9]' $F # expect 7 +tr -s '[:space:]' ' ' < $F | grep -c 'new | changed | retired | unchanged' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'when that agent is available, judges the boundary' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'personas carry when the working-process plugin is installed' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'the personas already carry' # expect 0 -awk 'length > 72' $F | grep -v '^|' | wc -l # over-wide non-table lines +awk 'length > 72' $F | grep -v '^|' | wc -l # expect 0 ``` -Expected: `7` skeleton rows (1–6 plus the `7–8` row), `1` for the closed -set, `0` over-wide prose lines. +Each expected value is on its line: `7` skeleton rows (1–6 plus the +`7–8` row), `1` for the closed set, which wraps in the rule text and so +is counted through `tr`, and `0` over-wide prose lines. - [ ] **Step 4: Validate the plugin** @@ -2447,8 +2458,9 @@ on the plan → implementation → code review. Offer a grilling once a design spec exists and before its implementation plan is written. Design specs are the primary target; plans and raw ideas are in scope too. A technical design is not a grilling target: it mints no -terminology, and its vocabulary comes from the design spec, which was -grilled. +terminology, its vocabulary coming from the design spec, which was +grilled, and from the domain's own skills; the part and component names +it does mint are checked against those skills. ``` - [ ] **Step 9: Verify, and sweep the plugin for the retired phrase** @@ -2456,7 +2468,7 @@ grilled. Run: ```bash -grep -rcH 'design document' plugins/working-process/ | grep -v ':0$' +grep -rcH 'design document' plugins/working-process/ | grep -v ':0$' | wc -l # expect 0 grep -c 'seven rule files' plugins/working-process/README.md # expect 1 grep -c 'six rule files' plugins/working-process/README.md # expect 0 ls plugins/working-process/rules/*.md | wc -l # expect 7 @@ -2465,13 +2477,13 @@ grep -c 'creates two directories' plugins/working-process/README.md # expect grep -c 'recomputes the hash of every document the stamp names' plugins/working-process/README.md # expect 1 grep -c "a spec's consumption gate recomputes the hash" plugins/working-process/README.md # expect 0 tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c 'A technical design is not a grilling target' # expect 1 +tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c "and from the domain's own skills" # expect 1 tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c 'technical design (when the repository has code)' # expect 1 tr -s '[:space:]' ' ' < plugins/working-process/skills/grilling-session/SKILL.md | grep -c 'agent dispatch) → writing-plans' # expect 0 -grep -rcH 'a spec or plan\|build from that text alone\|handed a spec' \ - plugins/working-process/README.md | grep -v ':0$' +grep -rcH 'a spec or plan\|build from that text alone\|handed a spec' plugins/working-process/README.md | grep -v ':0$' | wc -l # expect 0 ``` -Expected: no output from the first and last, `1`, `0`, `7`. The stated +Each expected value is on its line. The stated count and the directory listing must agree — a README that counts its own payload wrong is the failure this check exists for. The first and last lines are the Global Constraint's closing sweep. @@ -2479,8 +2491,8 @@ last lines are the Global Constraint's closing sweep. - [ ] **Step 10: Commit** ```bash -git add plugins/working-process/README.md -git commit -m "docs(working-process): put the technical design in the README flow" +git add plugins/working-process/README.md plugins/working-process/skills/grilling-session/SKILL.md +git commit -m "docs(working-process): put the technical design in the README and grilling flow" ``` --- @@ -3183,7 +3195,9 @@ Every citation held. named and the changelog's — now quote the lines they follow in a fenced block. The reviewer's own suggested anchor for the offers paragraph, `full-document round.`, occurs three times in that file, - so the block quotes two lines instead. + so the block quotes two lines instead. Corrected at round six: six + insertions changed shape, not five — Task 8 Step 2's inline anchor was + reshaped too, for uniformity rather than because it named a position. - fixed 2026-09-25 — [Minor] Task 11 left the implementer to choose whether to run Task 13 first; license: the plan's own anchors, which show no shared text between the two tasks; the Interfaces block says @@ -3200,8 +3214,71 @@ reading literally would have misapplied. Those cases are removed; the simulator now accepts one explicit shape, "After the lines:" with the anchor quoted in a block, and applies it byte-exactly. +Corrected at round six, and the correction matters more than the +figure it corrects. "127 of 127 after-checks pass" counted only the +checks annotated `# expect`. Seven verify commands stated their +expectation in prose, and the simulator never compared them; one of the +seven, Task 2's closed-set check, returned 0 against an expected 1. The +paragraph claimed coverage the tool did not have — the failure recorded +four times against the propagation gate. The shape it accepts is also +spelled two ways, "After the line:" and "After the lines:", and both +were simulated. + - signal 2026-09-25 — the reviewer judges another round worth its cost only as a diff-scoped pass over this wave, followed by the confirming full-document round, which it expects to close on `LGTM`; the consumer surface has now been walked plugin-wide, and a further full read for its own sake would find no more than this round did. + +### 2026-09-25 — plan-adversary, fable 5.1, blocking (round 6, diff-scoped) + +Diff-scoped over round five's fix wave. Two Important, four Minor. +Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-6.md`. +Every citation held. The reviewer ran Task 2's checks itself and found +one that the dispatcher's simulation had reported as passing without +ever running it. + +- fixed 2026-09-25 — [Important] Task 18's new step edited the + grilling-session skill while its commit staged only the README, so + the skill edit would have shipped in no commit; license: the step's + own edit; the commit stages both files under a widened subject. +- fixed 2026-09-25 — [Important] Task 2's closed-set check was a plain + grep over a phrase the rule text wraps, so it returns 0 against an + expected 1, while the round-five paragraph said every after-check + passed; license: the plan's Global Constraint that prose checks + normalize whitespace; the check runs through `tr`, every one of the + seven verify commands that stated its expectation in prose now carries + `# expect`, a Global Constraint requires it of every check, and the + round-five paragraph carries a correction naming what the simulator + had not compared. +- fixed 2026-09-25 — [Minor] the grilling-session skill's new sentence + named the design spec as a technical design's only vocabulary source, + dropping the domain's own skills and the part names it does mint; + license: the design spec's Lifecycle section and Task 5's block, which + carry both; the sentence carries both, with a check. +- fixed 2026-09-25 — [Minor] the "After the lines:" shape never said + whether a blank line separates an inserted block, and no check could + catch a glued insert; license: the plan's own purpose for the shape, + a block that lands where it is meant to; a Global Constraint states + the convention, and the simulator applies the same one. +- fixed 2026-09-25 — [Minor] the round-five ledger counted five + insertions reshaped where six were; license: the commit that changed + them; a dated correction on that line. +- fixed 2026-09-25 — [Minor] the round-five paragraph named one + spelling of the insertion shape where the plan uses two; license: the + plan's text; a dated correction on that paragraph, and the Global + Constraint names both spellings as one instruction. + +The simulation ran after the wave, now reporting what it did not check +as well as what passed: 133 of 133 annotated checks pass, no verify +command left uncompared, no step unsimulated, 86 of 87 non-zero checks +fail on the pre-plan files (the last the deliberate invariant), no new +line past 72 columns, both sweeps silent, the plugin validates. + +- signal 2026-09-25 — the reviewer judges a further diff-scoped round + not worth its cost over two one-line repairs and four count and + wording items; the next reading worth paying for is the confirming + full-document round, expected to close on `LGTM` if the simulation is + re-run rather than restated. `blocking` leaves that round to the + developer. From 32c3611665747cd33ea47ca2e913e1bf879f8a4c Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 08:42:30 +0200 Subject: [PATCH 011/143] docs: record the gate's dismissed hits before the confirming round --- docs/plans/2026-09-17-technical-design-step.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index 36b860b..b2e6d56 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -3276,6 +3276,20 @@ command left uncompared, no step unsimulated, 86 of 87 non-zero checks fail on the pre-plan files (the last the deliberate invariant), no new line past 72 columns, both sweeps silent, the plugin validates. +The propagation gate before the confirming round returned five located +hits, all dismissed: + +- hit dismissed 2026-09-25 — `design document` still stands in + `agents/architect.md`, `agents/integrity-auditor.md`, + `agents/plan-adversary.md` (twice) and `README.md`, against checks + that expect zero; counter: those checks assert the state after the + plan runs, and the plan has not run. The five occurrences in four + files are the before-state the Global Constraint names in those + words, and the simulated tree after all twenty tasks carries zero. +- hit dismissed 2026-09-25 — the simulation "did not faithfully apply + the prescribed text replacements to disk"; counter: it applies them to + copies of the files by design, so that simulating a plan never + executes it, and the copies carry every replacement. - signal 2026-09-25 — the reviewer judges a further diff-scoped round not worth its cost over two one-line repairs and four count and wording items; the next reading worth paying for is the confirming From 916ca417ef7e02c0428cc737ad530a25d4b56c17 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 09:17:31 +0200 Subject: [PATCH 012/143] docs: dispose the plan-adversary round seven findings and the independent review --- .../plans/2026-09-17-technical-design-step.md | 291 ++++++++++++++---- 1 file changed, 231 insertions(+), 60 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index b2e6d56..e3538e9 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2,7 +2,7 @@ ticket: none date: 2026-09-17 status: draft -adversary: blocking +adversary: concerns spec: ../specs/2026-09-16-technical-design-step.md branch: feature/technical-design-step base: develop @@ -68,11 +68,13 @@ verification. such check in Task 2 failed unnoticed for two rounds. The one exception is `claude plugin validate`, whose result is its exit status. -- **An inserted block stands on its own.** A block placed with "After - the line:" or "After the lines:" — one spelling per line count, the - same instruction — is a paragraph or heading of its own, with one - blank line before and after it. A table row or list item inserted by - its own instruction joins its table or list with no blank line. +- **Every insertion quotes its anchor.** Each one reads "After the + line:" or "After the lines:" on a line of its own — one spelling per + line count, the same instruction — followed by the anchor lines in a + fenced block and then the block to insert. A block that opens with a + list marker or a table pipe joins the list or table it follows + directly; any other block is a paragraph or heading of its own, one + blank line after its anchor. - **Every step states its before value and its after value.** The before values in this plan were measured against the files on 2026-09-17, not predicted. @@ -372,7 +374,13 @@ Expected: `0`, and a `paths:` block of five globs, the last - [ ] **Step 2: Add the glob** -In the frontmatter, after the `"docs/plans/**"` line, insert: +After the line: + +``` + - "docs/plans/**" +``` + +in the frontmatter, add: ```yaml - "docs/technical-designs/**" @@ -526,7 +534,13 @@ grep -c 'technical-design' $F # expect 0 - [ ] **Step 2: Widen the rule's scope** -In the frontmatter, after ` - "docs/specs/**"`, insert: +After the line: + +``` + - "docs/specs/**" +``` + +in the frontmatter, add: ```yaml - "docs/technical-designs/**" @@ -796,14 +810,20 @@ with: ``` spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several, on a plan -technical-design: ../technical-designs/.md # optional, on a design spec and on a plan: the technical design that develops this design spec +technical-design: ../technical-designs/.md # optional: on a design spec, the technical design that develops it; on a plan, the technical design of each design spec it descends from that has one — inline list when several, each path relative to the plan ``` - [ ] **Step 3: Write the five rules that keep the pair honest** -The field notes are the bullets under the frontmatter example; the last -of them ends `omitted entirely when there is no topic branch.` Add after -that line: +The field notes are the bullets under the frontmatter example. + +After the line: + +``` + up front, omitted entirely when there is no topic branch. +``` + +which ends the last of them, add: ``` - `technical-design:` is optional and appears once the technical design @@ -813,18 +833,28 @@ that line: and it gives a reader holding the design spec the structure that develops it. The author who creates the design writes both ends in the same turn — the design's `spec:` and the design spec's - `technical-design:`. A plan written from a design spec that names a - technical design carries that `technical-design:` as well as its - `spec:`, and the two name that audit pair; a plan written from a - design spec with no technical design carries no `technical-design:`. + `technical-design:`. A plan's `technical-design:` names, for every + design spec its `spec:` names that has a technical design, that + technical design — an inline list when several, each entry the same + document the design spec's own pointer names, its path written + relative to the plan's directory rather than copied. A design spec + with no technical design contributes no entry, so a plan from specs + `a` and `b`, where only `a` names a design, carries that one design. One technical design per design spec. ``` - [ ] **Step 4: Say what the pointer does to a plan's interface blocks** A plan naming a technical design must not redefine what that design -already defines. Add after Step 3's block, whose last line is -` One technical design per design spec.`: +already defines. + +After the line: + +``` + One technical design per design spec. +``` + +which is Step 3's last line, add: ``` - Where a plan's `technical-design:` names a document, that document @@ -848,7 +878,9 @@ grep -c 'plans and technical designs' $F # expect 1 grep -c 'plans only' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'they do not independently redefine those contracts' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'One technical design per design spec' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'carries that `technical-design:` as well as its' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "its path written relative to the plan's directory rather than copied" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'contributes no entry' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'carries that `technical-design:` as well as its' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'where it carries both they must agree' # expect 0 ``` @@ -904,8 +936,15 @@ integrity: (sha: [; with: @]) # optio - [ ] **Step 3: Write the pair clause** The bullet does not end where that sentence does — it runs twelve more -lines, through the hash command the pair clause depends on. Anchor on -the bullet's real last line, `a typo fix included.`, and add after it: +lines, through the hash command the pair clause depends on. + +After the line: + +``` + edits included; any change re-arms the stamp, a typo fix included. +``` + +which is the bullet's real last line, add: ``` A design spec that names a technical design is audited with it as one @@ -1137,7 +1176,13 @@ eleven. - [ ] **Step 2: Widen the rule's scope** -In the frontmatter, after ` - "docs/specs/**"`, insert: +After the line: + +``` + - "docs/specs/**" +``` + +in the frontmatter, add: ```yaml - "docs/technical-designs/**" @@ -1145,11 +1190,18 @@ In the frontmatter, after ` - "docs/specs/**"`, insert: - [ ] **Step 3: Add the pointer-pair duty as a table row** -The duties are a table keyed by the edit, not a bullet list. Append a -row after the `copied a citation out of a review report` row: +The duties are a table keyed by the edit, not a bullet list. + +After the line: + +``` +| copied a citation out of a review report | the file and line it names, read at the source | 8 | +``` + +which is the table's last row, add: ``` -| added or repointed a `spec:` or `technical-design:` pointer | both targets; on a design spec or a technical design, that each pointer names the document that names it; on a plan, that where its `spec:` target names a technical design the plan names the same one — a plan missing that pointer is a hit | 9 | +| added or repointed a `spec:` or `technical-design:` pointer | both targets; on a design spec or a technical design, that each pointer names the document that names it; on a plan, that its `technical-design:` entries resolve, from the plan's own directory, to exactly the technical designs its design specs name — none missing, none extra | 9 | ``` - [ ] **Step 4: Re-derive the two counters the row invalidates** @@ -1213,6 +1265,7 @@ grep -c '"docs/technical-designs/\*\*"' $F # expect 1 grep -c '^| ' $F # expect 11: header plus ten edit rows grep -c 'nine duties fall due' $F # expect 1 grep -c 'on a design spec or a technical design, that each pointer' $F # expect 1 +grep -c 'none missing, none extra' $F # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'as well as at design specs, technical designs and plans' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'as well as at specs and plans' # expect 0 grep -c 'keyed by the ten edits' $F # expect 1 @@ -1337,8 +1390,9 @@ with: names both documents, so declining it releases the chain debt of the two it named and nothing besides. Where the audit runs instead, it discharges that debt for both documents it read. The other arm stays - per-document: a full-document architect round discharges the debt of - the one document it read. The brief + per-document: choosing it dispatches one full-document architect + round on each document, each discharging the debt of the one it read, + and plan-writing waits for both verdicts. The brief confirms the auditor's two preconditions: every edit from the ``` @@ -1412,6 +1466,7 @@ tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document whose LGTM' # expec tr -s '[:space:]' ' ' < $F | grep -c 'For a spec whose LGTM' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'every document the stamp names' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c "spec's recomputed body hash" # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'and plan-writing waits for both verdicts' # expect 1 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill ``` @@ -1453,9 +1508,17 @@ Expected: `0`. - [ ] **Step 2: Write the authoring step** -The offer paragraph is the block Task 10 Step 2 inserted; its last line -is ` state.`, directly above the paragraph opening ` Where the -technical-design offer is accepted`. Add after that line: +The offer paragraph is the block Task 10 Step 2 inserted, directly above +the paragraph opening ` Where the technical-design offer is accepted`. + +After the lines: + +``` + implementer no new responsibility split, placement, or ownership of + state. +``` + +which close it, add: ``` Accepted, the session opens the `system-designer-session` skill when @@ -1469,9 +1532,11 @@ technical-design offer is accepted`. Add after that line: agent is available, whose card admits any judged document dispatched standalone, with the propagation audit gating that dispatch as it gates every verdict dispatch. The plan-adversary stays on plans. - The plan written afterwards carries both `spec:` and - `technical-design:`, the second copied from its design spec, so the - plan-adversary always has the design to read. + The plan written afterwards carries, beside its `spec:`, a + `technical-design:` entry for every design spec it descends from that + names a technical design — the same document, its path rewritten + relative to the plan's own directory — so the plan-adversary always + has each design to read. ``` Both tool mentions are conditional, as every neighbouring step in this @@ -1486,7 +1551,8 @@ Run: F=plugins/working-process/rules/workflow.md tr -s '[:space:]' ' ' < $F | grep -c 'writes both ends of the pair in the same turn' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'The plan-adversary stays on plans' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'the second copied from its design spec' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "its path rewritten relative to the plan's own directory" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'the second copied from its design spec' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'a path-scoped rule loads only when a matching file is read' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'following the technical-design rule that loads there' # expect 0 ``` @@ -1608,8 +1674,10 @@ round — and never an offer followed by a re-offer of the option just declined. When the `integrity-auditor` agent is absent the offer carries the full-document round alone. For an audit pair the gate makes one pair offer for both documents: its audit arm reads the two -together, while its round arm stays per-document, one architect round -for each document it reviews. The two arms cost differently and the +together, while choosing its round arm dispatches one full-document +architect round on each document, and plan-writing waits until both +verdicts are settled under the rules any verdict follows. The two arms +cost differently and the offer says so: an audit returns material for the dispatcher to dispose of and leaves the verdict alone, while a full-document round on a judged document is a new loop's first round, since that document's @@ -1678,7 +1746,8 @@ grep -c '`both`' $F # expect 0 grep -cF '(`docs/specs/`, `docs/technical-designs/`, `docs/plans/`) so the' $F # expect 1 grep -cF '(`docs/specs/`, `docs/plans/`) so the' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document, the consumption gate' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c 'its round arm stays per-document' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'dispatches one full-document architect round on each document' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'its round arm stays per-document' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'For a spec, the consumption gate' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'round on a spec is a new loop' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'bind design specs, technical designs, plans' # expect 1 @@ -1705,7 +1774,8 @@ git commit -m "feat(working-process): hold a finding from either judged document ### Task 13: The architect card takes the class name **Files:** -- Modify: `plugins/working-process/agents/architect.md:3` +- Modify: `plugins/working-process/agents/architect.md:3` and the + stamping sentence at `:56` **Interfaces:** - Consumes: `Judged document` from Task 1. @@ -1736,7 +1806,22 @@ with: a grilled design spec (primary target) or any judged document dispatched standalone ``` -- [ ] **Step 3: Verify** +- [ ] **Step 3: Name the class in the stamping sentence** + +The card's stamping sentence enumerates two classes. Replace: + +``` +convention (a YAML block with a `status` field) — spec and plan alike. A +``` + +with: + +``` +convention (a YAML block with a `status` field) — judged document and +plan alike. A +``` + +- [ ] **Step 4: Verify** Run: @@ -1745,9 +1830,11 @@ F=plugins/working-process/agents/architect.md grep -c 'design document' $F # expect 0 grep -c 'any judged document' $F # expect 1 grep -c 'a grilled design spec' $F # expect 1 +grep -c 'spec and plan alike' $F # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'judged document and plan alike' # expect 1 ``` -- [ ] **Step 4: Commit** +- [ ] **Step 5: Commit** ```bash git add plugins/working-process/agents/architect.md @@ -2051,11 +2138,12 @@ which close dimension 4, add: ``` ### 5. Coverage of the technical design's parts -Where the plan's `technical-design:` names a document — or, failing -that, where its design spec's does — read that document's Parts table. -A plan whose design spec names a technical design the plan does not -carry is itself an Important finding whose `origin` is -`implementation-plan`. The plan must cover every part marked `new`, +Read the Parts table of every technical design the plan's design +specs name, reaching each through the plan's `technical-design:` +entries or, where an entry is missing, through the design spec's own +pointer. A missing or extra entry is itself an Important finding whose +`origin` is `implementation-plan`. The plan must cover every part +marked `new`, `changed` or `retired`; one part may span several tasks and one task several parts, and a part carried as `unchanged` context needs none. An uncovered part is an Important finding whose `origin` is @@ -2087,7 +2175,7 @@ grep -c '^## Judged documents: decline' $F # expect 1 grep -c '^## Specs: decline' $F # expect 0 grep -c 'Specs are out of scope' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'Coverage of the technical design' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c "or, failing that, where its design spec's does" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'A missing or extra entry is itself an Important finding' # expect 1 grep -c '^### [0-9]' $F # expect 5 ``` @@ -2154,11 +2242,13 @@ reads. A plan is scoped differently and must not be read as half a pair. Its `spec:` names a design spec, which never names the plan back; what is -checked there starts from the design spec: where the plan's `spec:` -target names a technical design, the plan must name the same one. A -plan that omits the pointer or names a different design is a hit — the -check reads the design spec's pointer, so a missing field is found -rather than skipped. A plan whose `spec:` is not named back is not. +checked there starts from the design specs: for every `spec:` target +that names a technical design, the plan's `technical-design:` must hold +an entry resolving, from the plan's own directory, to that same +document. A missing entry, an extra one, or one resolving elsewhere is +a hit — the check reads each design spec's pointer, so a missing field +is found rather than skipped. A plan whose `spec:` is not named back is +not. ``` The second paragraph is what keeps this duty from firing on every plan @@ -2207,7 +2297,7 @@ grep -c 'a spec or plan' $F # expect 0 grep -c '^### 9\. The pointer pair' $F # expect 1 grep -c '^### [0-9]' $F # expect 9 tr -s '[:space:]' ' ' < $F | grep -c 'is not named back is not' # expect 1 -tr -s '[:space:]' ' ' < $F | grep -c "the check reads the design spec's pointer" # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c "the check reads each design spec's pointer" # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'never the names it borrows' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'A name the plan uses that the spec never defines' # expect 0 ``` @@ -2612,6 +2702,9 @@ which is the changelog's preamble, add: - The class a design spec and a technical design share is named `judged document` on the three agent cards that carried the old phrase and in the README; that phrase is retired. +- Run a rules re-sync after this update: the plan-adversary's `origin` + values changed, and until the re-sync an installed workflow rule + triages the new values by the old names. ``` - [ ] **Step 3: Set the dogfooding version** @@ -2647,6 +2740,7 @@ Run: ```bash claude plugin validate plugins/working-process grep -c '^## Unreleased$' plugins/working-process/CHANGELOG.md # expect 1 +tr -s '[:space:]' ' ' < plugins/working-process/CHANGELOG.md | grep -c 'triages the new values by the old names' # expect 1 ``` - [ ] **Step 5: Commit** @@ -2679,14 +2773,16 @@ git commit -m "chore(working-process): changelog and dogfooding version for the divergence stands until that separate change lands. Task 20 follows the decision, not the current rule text. -- **A plan's `technical-design:` pointer is mandatory, not - conditional.** The design spec says a plan keeps its own `spec:` and - `technical-design:` and that, where it carries both, they must agree - with the pair. The developer's ruling of 2026-09-24 sharpens that: a - plan written from a design spec that names a technical design carries - that pointer, and its absence is a hit, found from the design spec's - side (Tasks 6, 9, 11, 15 and 16). Where this plan and the spec's - sentence differ on that point, the plan follows the ruling. +- **A plan's `technical-design:` is mandatory, and a list.** The design + spec says a plan keeps its own `spec:` and `technical-design:` and + that, where it carries both, they must agree with the pair. The + developer's rulings of 2026-09-24 and 2026-09-25 sharpen that: a plan + carries a `technical-design:` entry for every design spec it names + that has a technical design — an inline list when several, each path + written relative to the plan — and a missing, extra or misresolving + entry is a hit, found from the design specs' side (Tasks 6, 9, 11, 15 + and 16). Where this plan and the spec's sentence differ, the plan + follows the rulings. - **The technical-design rule words two component mentions conditionally where the design spec states them plainly.** The spec @@ -3296,3 +3392,78 @@ hits, all dismissed: full-document round, expected to close on `LGTM` if the simulation is re-run rather than restated. `blocking` leaves that round to the developer. + +### 2026-09-25 — plan-adversary, fable 5.1, concerns (round 7, full-document) + +The confirming full-document round. No Important, six Minor. Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-7.md`. +An independent review of the same plan version by Codex (GPT-6), +written to +`.claude/working-process/2026-09-17-technical-design-step/codex-plan-review-2026-09-25-06-45-13.md`, +returned `blocking` on two Important findings and one Minor; its lines +below carry its own grades. The two reviewers overlapped on one finding; +Codex alone found the only correctness defect of the round, and its +reproduction was re-run and held. The stamped verdict is the loop +reviewer's. The developer ruled that one more full-document round +follows this wave, since it changes the plan's contract — the plan +pointer's cardinality, coverage completeness and the gate's behaviour — +and an annotation settles findings without assessing a changed contract. + +- fixed 2026-09-25 — [Minor] the architect card's stamping sentence + still read "spec and plan alike"; license: ADR 0004's class; Task 13 + gains a step, with a deletion assertion. +- fixed 2026-09-25 — [Minor] four insertions used an inline-anchor shape + the insertion constraint did not define; license: that constraint's + purpose; all eight insertions of any other shape now quote their + anchor in a fenced block, the constraint covers every one, and the + simulator's handlers for every other shape were removed with no step + left unsimulated. +- fixed 2026-09-25 — [Minor] on an audit pair the round arm read two + ways; ruling: 2026-09-25; choosing it dispatches one full-document + architect round on each document, and plan-writing waits until both + verdicts are settled under the rules any verdict follows (Tasks 10 + and 12). +- fixed 2026-09-25 — [Minor] the `technical-design:` example comment + described the field in design-spec terms on a plan; license: the + pointer semantics in Task 6's own note; the comment names each host. +- fixed 2026-09-25 — [Minor] Fable, and [Important] Codex: a plan may + name several design specs while every sentence about its + `technical-design:` assumed one; ruling: 2026-09-25; a plan's + `technical-design:` is an inline list holding the design of every + design spec it names that has one, and duty 9 checks completeness as + well as correctness — none missing, none extra — with a worked example + of two specs where one has no design (Tasks 6, 9, 11, 15 and 16, and + the Deviations bullet). +- fixed 2026-09-25 — [Minor] a plugin update opens a window in which the + new `origin` values meet an un-synced workflow rule; license: the + README's standing re-sync sentence; the changelog names the re-sync. +- fixed 2026-09-25 — [Important] Codex: the authoring step copied the + design spec's relative pointer into the plan, which names a different + file wherever the two sit at different depths — reproduced with a + nested spec, where the copy resolves outside `docs/`; license: duty 9's + requirement that the entry name that same document, and every pointer + in this process resolving from its own file; each entry is rewritten + relative to the plan's directory, and duty 9 compares resolved + targets. +- held — [Minor] Codex: the code-project validation the spec promises + has neither a task nor an owner, so finishing the tasks could be + mistaken for evidence that the workflow works on a code project; + question: who owns that validation, and where is it tracked?; + options: (a) the developer owns it, tracked as an issue in this + repository, created only on the developer's explicit order — + recommended, since it is durable and visible; (b) the developer owns + it, tracked as a Private-memory idea entry; (c) left unassigned for + now, with the plan saying so. Whichever is chosen, the plan records + the spec's acceptance criteria and states that completing its tasks + does not claim the validation happened. + +The simulation ran after the wave with every insertion handler but the +fenced shape removed: 142 of 142 annotated checks pass, no verify +command left uncompared, no step unsimulated, 91 of 92 non-zero checks +fail on the pre-plan files (the last the deliberate invariant), every +commit stages what its task edits, no new line past 72 columns, both +sweeps silent, the plugin validates. + +- signal 2026-09-25 — the loop reviewer judged another round not worth + its cost and a resolution annotation sufficient after the wave; the + developer ruled otherwise for the reason recorded above. From 9ed9dab3a6b01a162db4d1918cc6c5d3ca114a66 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 16:20:05 +0200 Subject: [PATCH 013/143] docs: record the code-project validation as an open step the plan does not claim --- .../plans/2026-09-17-technical-design-step.md | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index e3538e9..b327c8d 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2811,6 +2811,17 @@ git commit -m "chore(working-process): changelog and dogfooding version for the today — a `CLAUDE.md` note, or a file it points at — and Task 19 uses it for this repository. When the wider question is settled, the declaration moves with every other standing answer. +- **It does not validate the step on a code project.** The design + spec calls its own justification a hypothesis, and its validation + paragraph says what settles it: on one code project, cost — elapsed + time, tokens by model tier, rounds per document, and the interruptions + that needed a developer decision — against the structural decisions + still discovered during implementation and the rework they caused, + read against a comparison arm named before measuring starts. This + repository cannot run that, since it declines the offer. The + validation is a separate, open step the developer owns. Completing + this plan's tasks shows the contract landed; it claims nothing about + whether the step earns its cost. - It renames no existing spec to `-spec.md`, and adds no `-technical-design` skill. Both are out of scope in the spec. - **It changes no plan template.** The `**Interfaces:**` block shape @@ -3445,17 +3456,15 @@ and an annotation settles findings without assessing a changed contract. in this process resolving from its own file; each entry is rewritten relative to the plan's directory, and duty 9 compares resolved targets. -- held — [Minor] Codex: the code-project validation the spec promises - has neither a task nor an owner, so finishing the tasks could be - mistaken for evidence that the workflow works on a code project; - question: who owns that validation, and where is it tracked?; - options: (a) the developer owns it, tracked as an issue in this - repository, created only on the developer's explicit order — - recommended, since it is durable and visible; (b) the developer owns - it, tracked as a Private-memory idea entry; (c) left unassigned for - now, with the plan saying so. Whichever is chosen, the plan records - the spec's acceptance criteria and states that completing its tasks - does not claim the validation happened. +- fixed 2026-09-25 — [Minor] Codex: the code-project validation the + spec promises had neither a task nor an owner, so finishing the tasks + could be mistaken for evidence that the workflow works on a code + project; ruling: 2026-09-25; the developer owns the validation and + tracks it outside this repository, and a not-done bullet records it + as open, restates the spec's acceptance criteria, and says completing + the tasks claims nothing about it. The plan does not say where it is + tracked, since that store is per-user and a committed document must + not point at it. The simulation ran after the wave with every insertion handler but the fenced shape removed: 142 of 142 annotated checks pass, no verify From ed68502b4cb3de9316f2ab9faafe7e96623466d7 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 16:40:39 +0200 Subject: [PATCH 014/143] docs: dispose the plan-adversary round eight findings --- .../plans/2026-09-17-technical-design-step.md | 113 +++++++++++++++--- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index b327c8d..aefd121 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -17,7 +17,7 @@ base: develop **Goal:** Ship the contract for a third document class — the technical design — across the `working-process` plugin: one new path-scoped rule, -six changed rules and skills, four changed agent cards and the plugin +seven changed rules and skills, four changed agent cards and the plugin README, with the glossary and ADR 0004 already landed during spec authoring. @@ -56,6 +56,12 @@ verification. many times a phrase appears. Where a check must prove a block landed once, it counts with `grep -o … | wc -l`, and it anchors on a phrase unique to the block rather than one the file already carries. +- **A width check counts characters, so it sets its locale.** `awk`'s + `length` counts bytes in the C locale, and these files carry + multi-byte dashes and arrows, so a line of seventy characters can + measure past seventy-two. Every check that measures width runs under + `LC_ALL=C.UTF-8`; measured on these files, the same check reads 44 + under the C locale where it reads 20 by character. - **Checks are written for GNU grep and name the behaviour they need.** `-H` wherever a filename prefix is filtered on, since GNU grep prints none for a single named file; `-e` before any pattern that starts with @@ -327,7 +333,7 @@ tr -s '[:space:]' ' ' < $F | grep -c 'new | changed | retired | unchanged' # ex tr -s '[:space:]' ' ' < $F | grep -c 'when that agent is available, judges the boundary' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'personas carry when the working-process plugin is installed' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'the personas already carry' # expect 0 -awk 'length > 72' $F | grep -v '^|' | wc -l # expect 0 +LC_ALL=C.UTF-8 awk 'length > 72' $F | grep -v '^|' | wc -l # expect 0 ``` Each expected value is on its line: `7` skeleton rows (1–6 plus the @@ -785,8 +791,8 @@ git commit -m "feat(working-process): branch the lifecycle on the document class **Interfaces:** - Consumes: the class branches from Task 5. - Produces: `technical-design:` and the widened `spec:`, which Task 9 - makes the propagation auditor check and Task 11 makes the authoring - step write. + makes the author check, Task 16 makes the propagation auditor check, + and Task 11 makes the authoring step write. - [ ] **Step 1: Measure the before state** @@ -828,10 +834,11 @@ which ends the last of them, add: ``` - `technical-design:` is optional and appears once the technical design exists, so its absence means there is none rather than one nobody - linked. It is new in kind: every other pointer records where a - document came from, while this one names a document written later, - and it gives a reader holding the design spec the structure that - develops it. The author who creates the design writes both ends in + linked. On a design spec it is new in kind: every other pointer + records where a document came from, while this one names a document + written later, and it gives a reader holding the design spec the + structure that develops it. The author who creates the design writes + both ends in the same turn — the design's `spec:` and the design spec's `technical-design:`. A plan's `technical-design:` names, for every design spec its `spec:` names that has a technical design, that @@ -857,9 +864,9 @@ After the line: which is Step 3's last line, add: ``` -- Where a plan's `technical-design:` names a document, that document - defines the interfaces. The plan's `**Interfaces:**` blocks reference - the contracts it defines and say which part of one each task +- Where a plan's `technical-design:` names documents, those documents + define the interfaces. The plan's `**Interfaces:**` blocks reference + the contracts they define and say which part of one each task implements or changes; they do not independently redefine those contracts. Where a plan names no technical design, the existing plan convention stands unchanged. This binds how the blocks are filled and @@ -877,9 +884,11 @@ grep -c '^technical-design: ' $F # expect 1 grep -c 'plans and technical designs' $F # expect 1 grep -c 'plans only' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'they do not independently redefine those contracts' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'names documents, those documents define the interfaces' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'One technical design per design spec' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c "its path written relative to the plan's directory rather than copied" # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'contributes no entry' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'On a design spec it is new in kind' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'carries that `technical-design:` as well as its' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'where it carries both they must agree' # expect 0 ``` @@ -904,8 +913,9 @@ git commit -m "feat(working-process): add the technical-design pointer pair" **Interfaces:** - Consumes: the pointers from Task 6. -- Produces: the `with:` value shape, which Task 14 makes the auditor - emit and Task 18 documents in the README's field table. +- Produces: the `with:` value shape, which the dispatcher writes when it + stamps, Task 14 makes the auditor read on a joint target, and Task 18 + documents in the README's field table. - [ ] **Step 1: Measure the before state** @@ -2272,7 +2282,7 @@ with: ``` Diff the names one document uses against the names its sources define — -for a plan, the design spec and the technical design its `spec:` and +for a plan, the design specs and technical designs its `spec:` and `technical-design:` name; for a technical design, its design spec and the domain skills available to its author. A name a plan uses that no source defines is a gap in the source, not a plan error — the invention @@ -2299,6 +2309,7 @@ grep -c '^### [0-9]' $F # expect 9 tr -s '[:space:]' ' ' < $F | grep -c 'is not named back is not' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c "the check reads each design spec's pointer" # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'never the names it borrows' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'the design specs and technical designs its' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'A name the plan uses that the spec never defines' # expect 0 ``` @@ -2741,8 +2752,15 @@ Run: claude plugin validate plugins/working-process grep -c '^## Unreleased$' plugins/working-process/CHANGELOG.md # expect 1 tr -s '[:space:]' ' ' < plugins/working-process/CHANGELOG.md | grep -c 'triages the new values by the old names' # expect 1 +LC_ALL=C.UTF-8 awk 'FNR==1{f=0} /^[`][`][`]/{f=!f; next} !f && length > 72 && !/^[|]/ && !/^ / && !/^description:/' plugins/working-process/rules/*.md plugins/working-process/agents/*.md plugins/working-process/skills/*/SKILL.md plugins/working-process/README.md CLAUDE.md | wc -l # expect 102 ``` +The last check is the 72-column constraint over every file the plan +edits, prose only — fenced blocks, tables, indented literals and +`description:` lines are exempt. It does not read zero: 106 such lines +predate this plan, the plan rewraps four of them and adds none, so a +value above 102 means a line this plan wrote runs past 72 characters. + - [ ] **Step 5: Commit** ```bash @@ -3476,3 +3494,70 @@ sweeps silent, the plugin validates. - signal 2026-09-25 — the loop reviewer judged another round not worth its cost and a resolution annotation sufficient after the wave; the developer ruled otherwise for the reason recorded above. + +### 2026-09-25 — plan-adversary, fable 5.1, concerns (round 8, full-document) + +The full-document round the developer ordered after round seven's wave +changed the plan's contract. One Important, six Minor. Record: +`.claude/working-process/2026-09-17-technical-design-step/plan-adversary-round-8.md`. +The reviewer read the changed contract first and found its producers — +Task 6's field note and comment, Task 11's authoring sentence — and its +consumers — the duty-table row, duty 9, dimension 5 and the auditor's +cases — saying one compatible thing. Every citation held; two phrases +the dispatcher's plain grep missed wrap across a line. + +- held — [Important] the offer has no defeat condition for a design spec + that already names a technical design, so a literal session re-offers + at every later gate pass, and an accepted re-offer would mint a second + design against "one technical design per design spec"; question: does + a design spec already carrying `technical-design:` count as having + had its first pass, so the offer is not re-made?; options: (a) yes — + the pointer is the recorded answer, one clause in Task 10 with a + check and a line in the design spec's ledger — recommended, since + every other offer in these rules is defeated by a recorded state and + this field already exists; (b) no — the offer stands and the session + checks the pointer itself. Origin both: the spec's firing table has + no row for this case. +- held — [Minor] over an audit pair where only one document carries + chain debt, the round arm as ruled dispatches a full-document round on + both, including one that owes nothing; question: does the round arm + dispatch only on the documents that carry the debt?; options: (a) + yes, with plan-writing waiting for every verdict so dispatched — + recommended, since a round on a document with no debt discharges + nothing and costs the top tier; (b) no — both, as ruled on + 2026-09-25. New evidence against that ruling, which did not consider + the mixed case, so held rather than folded. +- fixed 2026-09-25 — [Minor] the Goal counted six changed rules and + skills where the tasks change seven — the grilling-session skill added + at round five never reached it; license: the count's own derivation; + the Goal says seven. +- fixed 2026-09-25 — [Minor] two Interfaces blocks named the wrong + deliverer — the auditor does not emit the `with:` value, and Task 9 + changes the author-facing rule, not the auditor; license: the tasks' + own Files blocks; both name the tasks that deliver. +- fixed 2026-09-25 — [Minor] "It is new in kind" was true on a design + spec and false on a plan, where the field records lineage; license: + the note's own plan clause; the sentence is scoped to the design spec. +- fixed 2026-09-25 — [Minor] two sentences inside the changed contract + still spoke of one design per plan; license: the 2026-09-25 list + ruling; both are plural. +- fixed 2026-09-25 — [Minor] the 72-column constraint bound every edited + file while the plan published a width check for one; license: the + constraint itself; Task 20 publishes a prose-only width check over + every file the plan edits. Measuring it found a locale trap: under the + C locale `awk` counts bytes, and these files' dashes and arrows made + one file read 44 over-wide lines where it has 20. Both width checks + set `LC_ALL=C.UTF-8`, and a Global Constraint says why. + +The simulation ran after the wave: 146 of 146 annotated checks pass, no +verify command left uncompared, no step unsimulated, 95 of 96 non-zero +checks fail on the pre-plan files (the last the deliberate invariant), +every commit stages what its task edits, no new line past 72 characters, +both sweeps silent, the plugin validates. The new width check reads 102 +after the plan and 106 before it, so it cannot pass on an untouched tree. + +- signal 2026-09-25 — the reviewer judges another round not worth its + cost: the confirming full-document read has now been done twice on a + contract that changed only in the pointer's cardinality, and the loop + should close on this round's disposition once the two held questions + are answered. From e7d690c343036b9e6f6effbd044e430732661604 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 16:43:13 +0200 Subject: [PATCH 015/143] docs: close the plan's review loop at concerns resolved, with the transitions checked --- .../plans/2026-09-17-technical-design-step.md | 92 +++++++++++++------ .../specs/2026-09-16-technical-design-step.md | 15 +++ 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index aefd121..f953092 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2,7 +2,7 @@ ticket: none date: 2026-09-17 status: draft -adversary: concerns +adversary: concerns (resolved 2026-09-25) spec: ../specs/2026-09-16-technical-design-step.md branch: feature/technical-design-step base: develop @@ -1359,8 +1359,14 @@ with: which names a background agent here. Where no skill covers the technology the document is still written, from generic knowledge and best effort: a tool that is not installed disables its suggestion, - never the work. A session may propose writing the declaration - and never writes it unasked. A change may skip the document when it + never the work. A design spec that already carries + `technical-design:` has had its first pass: the pointer answers the + offer, and the offer to write a design is not made again. The pointer + confirms the link and nothing more — the design it names still takes + every check it owes — and a pointer that resolves to no file is a + broken link, reported as a finding, never a reason to offer a second + design. A session may propose writing the declaration and never + writes it unasked. A change may skip the document when it sits inside boundaries and contracts already settled and leaves the implementer no new responsibility split, placement, or ownership of state. @@ -1401,8 +1407,9 @@ with: two it named and nothing besides. Where the audit runs instead, it discharges that debt for both documents it read. The other arm stays per-document: choosing it dispatches one full-document architect - round on each document, each discharging the debt of the one it read, - and plan-writing waits for both verdicts. The brief + round on each document that carries chain debt, each discharging the + debt of the one it read, and plan-writing waits for every verdict so + dispatched. The brief confirms the auditor's two preconditions: every edit from the ``` @@ -1476,7 +1483,10 @@ tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document whose LGTM' # expec tr -s '[:space:]' ' ' < $F | grep -c 'For a spec whose LGTM' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'every document the stamp names' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c "spec's recomputed body hash" # expect 0 -tr -s '[:space:]' ' ' < $F | grep -c 'and plan-writing waits for both verdicts' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'has had its first pass: the pointer answers the offer' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'never a reason to offer a second design' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'plan-writing waits for every verdict so dispatched' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'and plan-writing waits for both verdicts' # expect 0 grep -c 'designer session' $F # expect 0 — the glossary bans "session" for a skill ``` @@ -1685,9 +1695,10 @@ declined. When the `integrity-auditor` agent is absent the offer carries the full-document round alone. For an audit pair the gate makes one pair offer for both documents: its audit arm reads the two together, while choosing its round arm dispatches one full-document -architect round on each document, and plan-writing waits until both -verdicts are settled under the rules any verdict follows. The two arms -cost differently and the +architect round on each document that carries the debt, and +plan-writing waits until every verdict so dispatched is settled under +the rules any verdict follows; the joint integrity audit stays a check +of its own. The two arms cost differently and the offer says so: an audit returns material for the dispatcher to dispose of and leaves the verdict alone, while a full-document round on a judged document is a new loop's first round, since that document's @@ -1758,6 +1769,8 @@ grep -cF '(`docs/specs/`, `docs/plans/`) so the' $F # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'For a judged document, the consumption gate' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'dispatches one full-document architect round on each document' # expect 1 tr -s '[:space:]' ' ' < $F | grep -c 'its round arm stays per-document' # expect 0 +tr -s '[:space:]' ' ' < $F | grep -c 'on each document that carries the debt' # expect 1 +tr -s '[:space:]' ' ' < $F | grep -c 'until both verdicts are settled' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'For a spec, the consumption gate' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'round on a spec is a new loop' # expect 0 tr -s '[:space:]' ' ' < $F | grep -c 'bind design specs, technical designs, plans' # expect 1 @@ -3506,27 +3519,26 @@ consumers — the duty-table row, duty 9, dimension 5 and the auditor's cases — saying one compatible thing. Every citation held; two phrases the dispatcher's plain grep missed wrap across a line. -- held — [Important] the offer has no defeat condition for a design spec - that already names a technical design, so a literal session re-offers - at every later gate pass, and an accepted re-offer would mint a second - design against "one technical design per design spec"; question: does - a design spec already carrying `technical-design:` count as having - had its first pass, so the offer is not re-made?; options: (a) yes — - the pointer is the recorded answer, one clause in Task 10 with a - check and a line in the design spec's ledger — recommended, since - every other offer in these rules is defeated by a recorded state and - this field already exists; (b) no — the offer stands and the session - checks the pointer itself. Origin both: the spec's firing table has - no row for this case. -- held — [Minor] over an audit pair where only one document carries - chain debt, the round arm as ruled dispatches a full-document round on - both, including one that owes nothing; question: does the round arm - dispatch only on the documents that carry the debt?; options: (a) - yes, with plan-writing waiting for every verdict so dispatched — - recommended, since a round on a document with no debt discharges - nothing and costs the top tier; (b) no — both, as ruled on - 2026-09-25. New evidence against that ruling, which did not consider - the mixed case, so held rather than folded. +- fixed 2026-09-25 — [Important] the offer had no defeat condition for + a design spec that already names a technical design, so a literal + session would re-offer at every later gate pass and an accepted + re-offer would mint a second design; ruling: 2026-09-25; the pointer + answers the offer, which is not made again, and it confirms the link + and nothing more — the design still takes every check it owes, and a + pointer resolving to no file is a broken link reported as a finding, + never a reason for a second design (Task 10's block, with checks). The + design spec's firing section gains the same sentence, recorded in its + ledger under + `### 2026-09-24 — fix from docs/plans/2026-09-17-technical-design-step.md`. +- fixed 2026-09-25 — [Minor] over an audit pair where only one document + carries chain debt, the round arm dispatched a full-document round on + both; ruling: 2026-09-25; the round arm dispatches only on the + documents that carry the debt, plan-writing waits for every verdict so + dispatched, and the joint integrity audit stays a check of its own + (Tasks 10 and 12). This corrects the ruling of the same day recorded + under round seven — one round on each document — rather than + clarifying it: that ruling did not consider a pair where only one + document is indebted. - fixed 2026-09-25 — [Minor] the Goal counted six changed rules and skills where the tasks change seven — the grilling-session skill added at round five never reached it; license: the count's own derivation; @@ -3561,3 +3573,23 @@ after the plan and 106 before it, so it cannot pass on an untouched tree. contract that changed only in the pointer's cardinality, and the loop should close on this round's disposition once the two held questions are answered. + +The loop closes on this round. The developer resolved the verdict +without a ninth round, on the condition that the transitions the two +rulings touch were checked in the text the plan produces rather than +argued. They were, against the simulated tree after all twenty tasks, +each by the sentence that decides it: + +| case | outcome | decided by | +|---|---|---| +| no technical design | the offer follows the declaration and the markers | workflow step 4: "Without a declaration, a repository carrying a toolchain manifest … gets the offer at every consumption gate … a repository carrying neither gets no offer" | +| a technical design already named | no second offer to write one | workflow step 4: "has had its first pass: the pointer answers the offer, and the offer to write a design is not made again" | +| a pointer resolving to no file | a finding | workflow step 4: "a broken link, reported as a finding, never a reason to offer a second design"; duty 9: "A pointer resolving to a missing file … is a hit" | +| chain debt on the design spec only, the technical design only, or both | one, one or two full-document rounds | the pair offer: "one full-document architect round on each document that carries the debt" | +| no chain debt | no round from this offer | the pair offer exists only "For a judged document whose LGTM came from a diff-scoped chain" | + +With that, and the plan's own width check over every edited file, the +developer's ruling closes the loop at `concerns (resolved 2026-09-25)`: +the verdict stays what the reviewer returned, and every finding of all +eight rounds and of the independent review is disposed above. + diff --git a/docs/specs/2026-09-16-technical-design-step.md b/docs/specs/2026-09-16-technical-design-step.md index 17984b0..9866acd 100644 --- a/docs/specs/2026-09-16-technical-design-step.md +++ b/docs/specs/2026-09-16-technical-design-step.md @@ -79,6 +79,12 @@ both directions. | no declaration, but the repository carries a toolchain manifest | the offer fires at every consumption gate; nothing is written | | no declaration, no marker | no offer | +A design spec that already names a technical design has had its first +pass: the offer to write one is not made again. The pointer confirms the +link and nothing more — the design still takes every check it owes — and +a pointer that resolves to no file is reported as a broken link, never +answered with a second design. + A marker is a file a language or platform toolchain reads to build, test or deploy the repository — the file a developer would point at to say what kind of project this is. The core defines that test and names no @@ -925,6 +931,15 @@ scope is the class, not the ledger's grammar. relative to the design spec's directory, and the example now shows both lines. Raised by the plan-adversary's round four against the plan. +- fixed 2026-09-25 — [Important] the firing table had no row for a + design spec that already names a technical design, so the offer to + write one had no recorded state to defeat it, and an accepted re-offer + would mint a second design against one technical design per design + spec; ruling: 2026-09-25; a sentence after the table says the pointer + answers the offer, confirms the link and nothing more, and that a + pointer resolving to no file is a broken link rather than a reason for + a second design. Raised by the plan-adversary's round eight against + the plan. This heading takes the cross-document fix shape the lifecycle rule defines, which that rule scopes to a document whose loop closed at From 65c3fab4a651e23a96e78ed0926a5e38a44dd3f2 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 16:47:24 +0200 Subject: [PATCH 016/143] docs: approve the technical-design plan --- docs/plans/2026-09-17-technical-design-step.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index f953092..c182de4 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -1,7 +1,7 @@ --- ticket: none date: 2026-09-17 -status: draft +status: approved adversary: concerns (resolved 2026-09-25) spec: ../specs/2026-09-16-technical-design-step.md branch: feature/technical-design-step From 3bee99d59f0056329f3d214cef3fef1be2961d52 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:04:52 +0200 Subject: [PATCH 017/143] feat(working-process): add the technical-design rule --- .../working-process/rules/technical-design.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 plugins/working-process/rules/technical-design.md diff --git a/plugins/working-process/rules/technical-design.md b/plugins/working-process/rules/technical-design.md new file mode 100644 index 0000000..fbaa980 --- /dev/null +++ b/plugins/working-process/rules/technical-design.md @@ -0,0 +1,111 @@ +--- +paths: + - "docs/technical-designs/**" +--- + +# Technical design — what the thing is made of + +A technical design answers what a thing is made of, as its design spec +answers what and why and the implementation plan answers in what order +and verified how. It lives in `docs/technical-designs/`, one file per +design spec, named `-technical-design.md`. + +The boundary against the design spec runs between identity and +placement. The design spec names parts by the name their consumers use +and gives each one a responsibility. The technical design decides what +naming a part leaves open: where it sits, what shape the contract on +its border takes, who writes it and who reads it, what is state and +where that state is authoritative. Where naming a part fixes its +location, the name is the path and the design spec carries it. + +## Section skeleton + +| # | section | status | content | +|---|---|---|---| +| 1 | Scope | required | which design spec and which of its behaviours this structure realizes; the system boundary | +| 2 | Parts | required | `part \| kind \| change \| placement \| owns \| deliberately excludes`; a retirement is a `change: retired` row | +| 3 | Contracts | required | `producer → consumer \| crosses \| guarantee \| breaks when \| check`; a flow crossing three or more parts in fixed order becomes a numbered sequence | +| 4 | State | conditional | `record \| home \| written by \| read by \| lifecycle \| visible through` | +| 5 | Failure and repetition | conditional | what happens when a step does not finish, runs twice, runs beside another, or finds the other side absent | +| 6 | Cuts not taken | conditional | one line per rejected division and its reason | +| 7–8 | Open questions, Review rounds | from the spec-plan-lifecycle rule | as a design spec and a plan carry them today | + +Three sections are required because only those three are never empty, +on prose or on code. A conditional section that is absent is named in +one line — "not applicable, because…" — never silently dropped. + +`kind` answers what a part is; `placement` answers where it belongs — +its layer, module, package, service, or path, at the level the design +decision needs. The two coincide only where a domain convention derives +one from the other, and the cell is filled even then: a cell reading +`rules/` says the convention was applied deliberately, while an empty +one cannot be told apart from an omission. + +`change` takes one value from the closed set `new | changed | retired | +unchanged`, and a domain adds no value to it: the column steers the +process, so its meaning belongs to the core, and a value outside the set +is an error the author corrects rather than a part that quietly escapes +the plan's coverage check. + +Tests are not a section. What verifies a contract is the contract's +`check` column; when that check runs is the plan's business. + +## The three definitions + +A **part** has a resolvable identity, an explicit responsibility with +its exclusions, and a contract that lets its implementation change +without changing its consumers. + +A **contract** is what a consumer may rely on without reading the +producer's body. + +**State** is a record that outlives one interaction and is read by a +later one, with a home, a writer, a reader and an end. + +Part and contract are two questions about one thing rather than +competing labels: a file may be a part, its signature a contract, and +the record it writes state. + +## What earns a part its own row + +Listing a part separately is a design decision. List it when leaving it +out would make an implementer decide a responsibility boundary, a +contract between parts, or the ownership of state. Keep inside a part +the details that merely carry out decisions already made — a public +method is usually a row in its part's contracts rather than a part of +its own. + +Task boundaries never define part boundaries: the plan is organised +around the structure, not the structure around the plan. The grain +comes from the decisions the document exists to settle — the same +question an integrity audit asks of a design spec, which is what this +text would leave an implementer to decide. + +## Domain vocabulary + +The author takes the part vocabulary — kinds of part, placement rules, +kinds of contract, homes of state — from the domain's own skills: +through the Domain expertise duty the personas carry when the +working-process plugin is installed, which has them scan and load those +skills, and by reading the skills directly otherwise. No discovery +convention of its own ships here. + +A kind of part, contract, or home of state that no domain skill names is +recorded in the document as a **vocabulary gap**: the author names the +kind, writes the gap down, and the architect round, when that agent is +available, judges the boundary, which is what it judges in any case. +Accumulated vocabulary gaps are either the specification of a +`-technical-design` skill or the evidence that none is needed. + +Where no skill covers the technology at all, the document is written +from generic knowledge, best effort. That degradation path is what +makes this contract self-sufficient. + +## What the core closes + +The core closes the three definitions above, the table columns, the +contract entry's fields, and the four questions of the failure section. +A domain adds rows and values — outside the closed `change` column — +never columns, and never redefines what qualifies: a kind of part it +names must still have a resolvable identity, an explicit +responsibility, and a contract its implementation can change behind. From 9e0b81a44c8d3c7072460d890384f79bfe12db9a Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:07:10 +0200 Subject: [PATCH 018/143] feat(working-process): count docs/technical-designs among Process directories --- plugins/working-process/rules/process-artifacts.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/rules/process-artifacts.md b/plugins/working-process/rules/process-artifacts.md index 420822e..d39023b 100644 --- a/plugins/working-process/rules/process-artifacts.md +++ b/plugins/working-process/rules/process-artifacts.md @@ -2,6 +2,7 @@ paths: - "docs/specs/**" - "docs/plans/**" + - "docs/technical-designs/**" - "docs/domain/**" - "docs/code-review/**" - ".superpowers/**" @@ -10,8 +11,9 @@ paths: # Process directories and artifacts A Process directory is a directory the working process creates in a -project repo to hold work artifacts: `docs/specs/`, `docs/plans/`, -`docs/domain/`, `docs/code-review/`, `docs/memory/` (Team memory — when the +project repo to hold work artifacts: `docs/specs/`, +`docs/technical-designs/`, `docs/plans/`, `docs/domain/`, +`docs/code-review/`, `docs/memory/` (Team memory — when the project-memory plugin's rules are installed), and the `.superpowers/` family at the repo root. From 03a91afaae16cc7f1808bb47572bf039b6b56914 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:09:22 +0200 Subject: [PATCH 019/143] feat(working-process): give technical designs the process field set --- plugins/working-process/rules/ticket-frontmatter.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/rules/ticket-frontmatter.md b/plugins/working-process/rules/ticket-frontmatter.md index 3595fef..48e6293 100644 --- a/plugins/working-process/rules/ticket-frontmatter.md +++ b/plugins/working-process/rules/ticket-frontmatter.md @@ -11,7 +11,8 @@ GitLab, or anything else; the field is always `ticket`. ## Field sets -- Specs and plans (`docs/specs/`, `docs/plans/`): `ticket` + `date` + +- Design specs, technical designs and plans (`docs/specs/`, + `docs/technical-designs/`, `docs/plans/`): `ticket` + `date` + `status` + the process and branch fields — details in the spec-plan-lifecycle rule. - Review reports (`docs/code-review/`): the review-reports rule's @@ -51,7 +52,8 @@ GitLab, or anything else; the field is always `ticket`. For a NEW document: branch name (`feature/ABC-123-...`) → conversation context → ask the developer once; no answer means `none`. Never ask twice -for one unit of work — a plan inherits its spec's ticket, and artifacts +for one unit of work — a technical design and a plan inherit their +design spec's ticket, and artifacts of the same session reuse the established value. The branch naming convention that produces the names this order reads is the workflow rule's, which is where a branch is cut — before any `docs/` file of that From 4f294f9529d2fac26278e9c86df9f734c8c06f91 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:12:25 +0200 Subject: [PATCH 020/143] feat(working-process): branch the lifecycle on the document class --- .../rules/spec-plan-lifecycle.md | 53 ++++++++++++++----- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index fbd4987..a9d5d82 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -1,12 +1,14 @@ --- paths: - "docs/specs/**" + - "docs/technical-designs/**" - "docs/plans/**" --- -# Specs and plans — frontmatter and lifecycle +# Judged documents and plans — frontmatter and lifecycle -Specs live in `docs/specs/`, plans in `docs/plans/`. Both open with a +Design specs live in `docs/specs/`, technical designs in +`docs/technical-designs/`, plans in `docs/plans/`. All three open with a YAML frontmatter block: ```yaml @@ -71,8 +73,9 @@ base: master # optional: branch the topic branch was cut from when the developer deliberately dispatched below the prescribed tier before any refusal. A dispatch at the prescribed tier gets no field. - Both tokens carry a re-review offer at the document's next consumption - gate — before plan-writing for a spec, before implementation for a - plan. Accepted: a fresh round at the prescribed tier replaces the + gate — before plan-writing for a judged document, before + implementation for a plan. Accepted: a fresh round at the prescribed + tier replaces the verdict and removes the field (a fresh round that is itself below the prescribed tier refreshes the field's date instead, and the offer re-arms at the same gate). Declined: the field gains `, waived `. @@ -97,10 +100,12 @@ base: master # optional: branch the topic branch was cut from its last edit, and that answer is a comparison rather than a clock. A recomputed hash differing from the stamped one means unaudited, same-day edits included; any change re-arms the stamp, a typo fix included. -- A spec's consumption gate owns the recomputation: before plan-writing it +- A judged document's consumption gate owns the recomputation: before + plan-writing it recomputes the body hash and compares, a match meaning the standing stamp satisfies the gate and a mismatch firing the audit offer. Those - are spec-gate semantics — on a plan, a permitted target on explicit + are judged-document semantics — on a plan, a permitted target on + explicit request, the stamp is informational and goes stale silently. Staleness joins no Unfinished-work entry: it is a recomputation, not a grep. - Verdict agents self-report the model they ran on (family plus version); @@ -227,7 +232,8 @@ their condition holds. Every terminal line carries exactly one authorizer. A fix one review licenses can land in a document other than the -reviewed one — a plan review's finding carrying `origin: spec` is the +reviewed one — a plan review's finding whose `origin` names a judged +document is the case the workflow rule names. The disposition line then lands in the ledger of the document that changed, and the reviewed document's line points at it in its `` clause, naming that document and @@ -444,7 +450,8 @@ leg, and the entries below that do so say it there. Scope: a hit counts only inside a `## Review rounds` section — the second entry to re-scope the default guard, for the reason the first one does, and carrying `-n` for the same reason. - Owner: for a spec, the consumption gate's pair offer; for a plan, the + Owner: for a judged document, the consumption gate's pair offer; for a + plan, the confirming full-document round. On a document already at `status: implemented` the debt is discharged by recorded decline without any dispatch: completed work is not re-reviewed, so the @@ -482,11 +489,13 @@ close is a rewrite or an appended annotation: `open` or `held` becomes Each an offer the developer may decline, and each made only when the tool is available: grill a fresh spec (grilling-session); -architect-review a grilled spec (architect agent dispatch); +architect-review a grilled design spec, and a technical design, which +is never grilled (architect agent dispatch); adversary-review a plan before implementation (plan-adversary agent dispatch); offer the pending re-review of a fallback-recorded verdict at its consumption gate (fresh round at the prescribed tier); and when a -spec or plan moves to `implemented` and the memory-review-session skill +judged document or plan moves to `implemented` and the +memory-review-session skill is available, offer a Project memory review — released work-state notes close, resolved entries sweep to the archive. After any review round, relay the report to the developer, then stamp the @@ -501,12 +510,28 @@ subsection: a plan's diff-scoped LGTM is relayed and its round record written, while only the frontmatter stamp waits for the confirming full-document round. -A document's consumption gate is the backstop for its ledger: a spec -does not pass to plan-writing, nor a plan to implementation, while +A technical design is a judged document and takes the design spec's side +of every branch in this rule: the same fields, the same +consumption-gate semantics for a stale stamp, the same owner for an +unresolved verdict, and the same pair offer against chain debt. It is +not grilled — grilling stress-tests terminology against the project +glossary, and a technical design mints none: its vocabulary comes from +its design spec, which was grilled, and from the domain's own skills. +The names it does mint are part and component names, checked against +those skills where one exists and recorded as a vocabulary gap where +none does. + +One mechanism it shares rather than owns: the integrity check is due on +it like any judged document, and one audit of the audit pair — the +design spec together with the technical design it names — discharges it, +so the `integrity:` stamp sits on the design spec and names both. + +A document's consumption gate is the backstop for its ledger: no judged +document passes to plan-writing, nor a plan to implementation, while `held` lines stay open — an LGTM can leave the frontmatter clean while a decision question still pends, so the gate asks those questions at the -latest. Writing a plan from a spec is that same seam: the held spec -questions are asked before the plan is written, whoever writes it. +latest. Writing a plan from a judged document is that same seam: its +held questions are asked before the plan is written, whoever writes it. The process suggests committing the work's documents under `docs/` at exactly one point — the implementation-ready gate: the developer has From 038bc698c75ec42309511d1092eda2418d99b1e9 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:20:11 +0200 Subject: [PATCH 021/143] feat(working-process): add the technical-design pointer pair --- .../rules/spec-plan-lifecycle.md | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index a9d5d82..0533e76 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -22,7 +22,8 @@ adversary: LGTM # optional: latest plan-adversary verdict (LGTM | concerns | architect-fallback: (degraded ) # optional: verdict above produced below the prescribed tier (adversary-fallback: for plans) integrity: (sha: ) # optional: date of the last integrity audit, plus the body hash it certifies revises: ./.md # optional: documents this one departs from; inline list when several -spec: ../specs/.md # plans only: the spec this plan implements; inline list when several +spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several, on a plan +technical-design: ../technical-designs/.md # optional: on a design spec, the technical design that develops it; on a plan, the technical design of each design spec it descends from that has one — inline list when several, each path relative to the plan branch: feature/ABC-123-short-name # optional: topic branch of the work base: master # optional: branch the topic branch was cut from --- @@ -115,6 +116,30 @@ base: master # optional: branch the topic branch was cut from placeholders (as above) so they never match the list below. - `branch` and `base` appear once the topic branch exists — never guessed up front, omitted entirely when there is no topic branch. +- `technical-design:` is optional and appears once the technical design + exists, so its absence means there is none rather than one nobody + linked. On a design spec it is new in kind: every other pointer + records where a document came from, while this one names a document + written later, and it gives a reader holding the design spec the + structure that develops it. The author who creates the design writes + both ends in + the same turn — the design's `spec:` and the design spec's + `technical-design:`. A plan's `technical-design:` names, for every + design spec its `spec:` names that has a technical design, that + technical design — an inline list when several, each entry the same + document the design spec's own pointer names, its path written + relative to the plan's directory rather than copied. A design spec + with no technical design contributes no entry, so a plan from specs + `a` and `b`, where only `a` names a design, carries that one design. + One technical design per design spec. +- Where a plan's `technical-design:` names documents, those documents + define the interfaces. The plan's `**Interfaces:**` blocks reference + the contracts they define and say which part of one each task + implements or changes; they do not independently redefine those + contracts. Where a plan names no technical design, the existing plan + convention stands unchanged. This binds how the blocks are filled and + changes no plan template — the template belongs to the tool that + writes plans. ## Finding what revises a document From b713677f3a8f562041666728fe6aed0759f6c80f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:24:20 +0200 Subject: [PATCH 022/143] feat(working-process): record a pair audit in one integrity stamp --- .../rules/spec-plan-lifecycle.md | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index 0533e76..696e378 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -20,7 +20,7 @@ grilled: grilling # optional: `grilling` while outcomes are pending; the ISO d architect: LGTM # optional: latest architect verdict (LGTM | concerns | blocking) adversary: LGTM # optional: latest plan-adversary verdict (LGTM | concerns | blocking) architect-fallback: (degraded ) # optional: verdict above produced below the prescribed tier (adversary-fallback: for plans) -integrity: (sha: ) # optional: date of the last integrity audit, plus the body hash it certifies +integrity: (sha: [; with: @]) # optional: date of the last integrity audit, the body hash it certifies, and — where a technical design was audited with it — that document and its hash revises: ./.md # optional: documents this one departs from; inline list when several spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several, on a plan technical-design: ../technical-designs/.md # optional: on a design spec, the technical design that develops it; on a plan, the technical design of each design spec it descends from that has one — inline list when several, each path relative to the plan @@ -101,6 +101,21 @@ base: master # optional: branch the topic branch was cut from its last edit, and that answer is a comparison rather than a clock. A recomputed hash differing from the stamped one means unaudited, same-day edits included; any change re-arms the stamp, a typo fix included. + A design spec that names a technical design is audited with it as one + target — an **audit pair** — and one stamp records it. The stamp + lives on the design spec, names both documents and both body hashes, + and the technical + design carries no `integrity:` of its own — it owes the check like any + judged document and discharges it jointly. The value takes the form + `integrity: (sha: ; with: @)`, + where `` is exactly the value the design spec's + `technical-design:` pointer carries, read relative to the design + spec's directory, so the gate can check that the recorded value still + equals the pointer. Changing either document unsettles the pair, and + the gate + recomputes both hashes to see it; because the two pointers are what + identify the pair, an added, removed or repointed `technical-design:` + unsettles it too. - A judged document's consumption gate owns the recomputation: before plan-writing it recomputes the body hash and compares, a match meaning the standing @@ -307,8 +322,17 @@ by a different actor, about a different event. Three paths discharge the debt, all three write the same token, and the dispatcher writes it in every case: the developer declining the gate's pair offer, written in the decline turn before the work that decline -licenses begins; an integrity audit, written once its dispositions are -applied and before the `integrity:` stamp; and any later full-document +licenses begins, and written for every document that offer named — a +declined offer over an audit pair discharges the chain debt of both. +What a decline never does is stand in for the audit itself: it writes +no `integrity:` stamp, leaves every other open finding open, and closes +no `blocking` verdict. "Do not run the audit" is a release from the +chain debt the offer named and from nothing else; an integrity audit, +written once its dispositions are +applied and before the `integrity:` stamp — and where that audit read an +audit pair, it discharges the debt of both documents it read, annotating +each document's own unannotated diff-scoped `LGTM` headings; and any +later full-document round, at its stamping turn, whatever its verdict — the debt is discharged by the reading, not by the grade. A full-document round reads the whole document, so it annotates every unannotated diff-scoped `LGTM` From 2a8f12d39f4fb6f42e8c02088ea5221e8f6b6768 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:25:37 +0200 Subject: [PATCH 023/143] fix(working-process): separate the pair clause from the integrity note --- plugins/working-process/rules/spec-plan-lifecycle.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index 696e378..5b8431a 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -101,6 +101,7 @@ base: master # optional: branch the topic branch was cut from its last edit, and that answer is a comparison rather than a clock. A recomputed hash differing from the stamped one means unaudited, same-day edits included; any change re-arms the stamp, a typo fix included. + A design spec that names a technical design is audited with it as one target — an **audit pair** — and one stamp records it. The stamp lives on the design spec, names both documents and both body hashes, From 0236205399405a038cd96c2b04d425d7542b1bab Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:29:50 +0200 Subject: [PATCH 024/143] feat(working-process): run the consumption gate in passes --- .../rules/spec-plan-lifecycle.md | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index 5b8431a..8ee5e9e 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -583,11 +583,33 @@ decision question still pends, so the gate asks those questions at the latest. Writing a plan from a judged document is that same seam: its held questions are asked before the plan is written, whoever writes it. +A technical design's own consumption gate is plan-writing, the same +moment as its design spec's, and one ordering binds the two: writing +the design and applying its review dispositions precedes the joint +integrity audit, because the design is the last producer of changes to +the design spec and certifying that body before the design's questions +are answered stamps a body about to change. The dependency reaches no +further — every other item of the two gates stays unordered. + +The gate therefore runs in passes. The first pass decides whether a +technical design is written at all. Where the offer is accepted, the +gate suspends until the design exists and its review dispositions are +applied, then resumes and collects the questions the documents' current +state raises. Answers already given stay binding unless the basis they +rested on changed. Each pass asks its questions in one batch, as a +review round does — a batch being every question answerable at that +pass, never every question the gate will ever ask. + +A technical design's `status` reaches `implemented` with its plan's, in +the same turn and by the same hand, since otherwise the rule freezing an +implemented document's body never reaches it. + The process suggests committing the work's documents under `docs/` at exactly one point — the implementation-ready gate: the developer has approved the plan (the `status` flip to `approved`) and implementation -is about to start. During authoring — spec drafting, grilling, review -rounds, plan writing — it never makes that suggestion; the documents' +is about to start. During authoring — spec drafting, grilling, +technical-design writing, review rounds, plan writing — it never makes +that suggestion; the documents' uncommitted state is deliberate, not dirt in the process-artifacts sense, and the developer may commit sooner on their own call. The suggestion covers only paths git tracks or would track; deliberately @@ -618,8 +640,9 @@ does; the suffix takes a dot because the branch convention already spends hyphens on name parts, where `-docs` would read as a topic about documenting. -The loop runs on that branch for the whole authoring phase — the spec's -rounds and the plan's alike — and the topic branch takes it at the +The loop runs on that branch for the whole authoring phase — the design +spec's rounds, the technical design's and the plan's alike — and the +topic branch takes it at the implementation-ready gate: by fast-forward where the history is wanted whole, by squash where it is not. That choice belongs to the project rather than the session, so a `CLAUDE.md` note — at the repo root or From 54a42035613693312391efd752548f6681c7940f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:32:17 +0200 Subject: [PATCH 025/143] feat(working-process): make the author check the pointer pair --- plugins/working-process/rules/propagation-duties.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/working-process/rules/propagation-duties.md b/plugins/working-process/rules/propagation-duties.md index 91a4359..959e58e 100644 --- a/plugins/working-process/rules/propagation-duties.md +++ b/plugins/working-process/rules/propagation-duties.md @@ -1,21 +1,22 @@ --- paths: - "docs/specs/**" + - "docs/technical-designs/**" - "docs/plans/**" - "docs/domain/**" --- # Propagation duties — the author's checklist -Before a document goes to an expensive reader, eight duties fall due, -keyed by the nine edits that trigger them — one duty answers two +Before a document goes to an expensive reader, nine duties fall due, +keyed by the ten edits that trigger them — one duty answers two different edits. This rule lists them by the edit an author has just made, so the enumeration can be done at the desk instead of paid for at the gate. The list below is complete as it stands and needs nothing else to be usable. When the `propagation-auditor` agent is available it walks the same -eight as a gate, and its card is their definition and keeps the +nine as a gate, and its card is their definition and keeps the measurement behind each; the numbers in the last column are that card's, so a hit it reports can be read back to the row that would have caught it. Without the agent the rows still hold — what is lost is the second @@ -32,11 +33,13 @@ pair of eyes, not the duties. | reported another document's state | that sentence, against that document | 6 | | written a verification command | the command, run against your own replacement text | 7 | | copied a citation out of a review report | the file and line it names, read at the source | 8 | +| added or repointed a `spec:` or `technical-design:` pointer | both targets; on a design spec or a technical design, that each pointer names the document that names it; on a plan, that its `technical-design:` entries resolve, from the plan's own directory, to exactly the technical designs its design specs name — none missing, none extra | 9 | Two of these fire where an author does not expect them. A newly minted glossary `_Avoid_` ban is a changed interface, so duty 1 reaches every shipped occurrence of the banned term — which is why this rule loads at -the domain directory as well as at specs and plans. And a count asserted +the domain directory as well as at design specs, technical designs and +plans. And a count asserted about another document is duties 4 and 6 at once: re-derive it, then read it back at its source. From 17f034d7badc5035b2f5c5383f68ef0cb5972a17 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:35:50 +0200 Subject: [PATCH 026/143] feat(working-process): offer the technical design at the consumption gate --- plugins/working-process/rules/workflow.md | 63 ++++++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/plugins/working-process/rules/workflow.md b/plugins/working-process/rules/workflow.md index 90d2d77..e3e3bc1 100644 --- a/plugins/working-process/rules/workflow.md +++ b/plugins/working-process/rules/workflow.md @@ -35,15 +35,62 @@ disables its suggestion — never the work itself. carry it. 4. **Spec → plan.** At the spec's consumption gate, before the plan is written, offer an integrity audit when the `integrity-auditor` agent - is available: a fresh-context read of the whole spec, returning - defects and the questions an implementer would have to ask. The - offer fires unless a standing `integrity:` stamp still matches the - spec's recomputed body hash — a match means the standing stamp + is available: a fresh-context read of the whole design spec, and of + its technical design where it names one, returning defects and the + questions an implementer would have to ask. The offer fires unless a + standing `integrity:` stamp still matches the recomputed body hash of + every document the stamp names — a match means the standing stamp satisfies the gate, and the spec-plan-lifecycle rule owns that - comparison. For a spec whose LGTM came from a diff-scoped chain the - offer takes the pair form the verdict-agent dispatch subsection - defines, and narrows as that definition says when the auditor is - absent. The brief + comparison. For a judged document whose LGTM came from a diff-scoped + chain the offer takes the pair-offer form the verdict-agent dispatch + subsection defines — one pair offer for an audit pair, never one per + document — and narrows as that definition says when the auditor is + absent. + + At the same gate, and before the integrity audit above, offer the + technical design when the repository is one that has code. A + declaration the project records binds and is never re-asked, in + either direction; a session reads it from the project instructions + Claude Code loads at session start — a `CLAUDE.md` at the repository + root or in `.claude/` — or from the file such a note points at, which + is the shape available until a standing home for a project's process + answers exists. Without a declaration, a repository carrying a + toolchain manifest — a file a language or platform toolchain reads + to build, test or deploy it — gets the offer at every consumption + gate and nothing is written; a repository carrying neither gets no + offer. The core names no closed list of markers, and a domain's own + skill may name the markers of its technology. A marker proves the + repository holds code, never that this change needs decomposing, so + the declaration is the signal and the marker only raises the + question. The offer reads *open the `system-designer-session` skill, + when available, and write the technical design?* — never *dispatch*, + which names a background agent here. Where no skill covers the + technology the document is still written, from generic knowledge and + best effort: a tool that is not installed disables its suggestion, + never the work. A design spec that already carries + `technical-design:` has had its first pass: the pointer answers the + offer, and the offer to write a design is not made again. The pointer + confirms the link and nothing more — the design it names still takes + every check it owes — and a pointer that resolves to no file is a + broken link, reported as a finding, never a reason to offer a second + design. A session may propose writing the declaration and never + writes it unasked. A change may skip the document when it + sits inside boundaries and contracts already settled and leaves the + implementer no new responsibility split, placement, or ownership of + state. + + Where the technical-design offer is accepted, the audit waits: the + design is the last producer of changes to its design spec, and the + two are then audited as one target — an audit pair. The gate makes + one offer for that audit pair rather than one per document, and the + offer + names both documents, so declining it releases the chain debt of the + two it named and nothing besides. Where the audit runs instead, it + discharges that debt for both documents it read. The other arm stays + per-document: choosing it dispatches one full-document architect + round on each document that carries chain debt, each discharging the + debt of the one it read, and plan-writing waits for every verdict so + dispatched. The brief confirms the auditor's two preconditions: every edit from the conversation is written to disk, since one unsaved decision manufactures a run of false defects; and the propagation gate below From 952b193a98e4307c7553090e6519dd26eb0a1a98 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:41:31 +0200 Subject: [PATCH 027/143] feat(working-process): name the technical design's author and reviewer --- plugins/working-process/rules/workflow.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/working-process/rules/workflow.md b/plugins/working-process/rules/workflow.md index e3e3bc1..d581838 100644 --- a/plugins/working-process/rules/workflow.md +++ b/plugins/working-process/rules/workflow.md @@ -79,6 +79,23 @@ disables its suggestion — never the work itself. implementer no new responsibility split, placement, or ownership of state. + Accepted, the session opens the `system-designer-session` skill when + it is available and reads the technical-design rule, installed beside + this one, before drafting — a path-scoped rule loads only when a + matching file is read, and none exists yet — then writes the document + to `docs/technical-designs/` following it. It writes both + ends of the pair in the same turn — the design's `spec:` and the + design spec's `technical-design:` — so no audit ever meets a + half-written pair. The reviewer is the `architect` agent when that + agent is available, whose card admits any judged document dispatched + standalone, with the propagation audit gating that dispatch as it + gates every verdict dispatch. The plan-adversary stays on plans. + The plan written afterwards carries, beside its `spec:`, a + `technical-design:` entry for every design spec it descends from that + names a technical design — the same document, its path rewritten + relative to the plan's own directory — so the plan-adversary always + has each design to read. + Where the technical-design offer is accepted, the audit waits: the design is the last producer of changes to its design spec, and the two are then audited as one target — an audit pair. The gate makes From 5a19bcc7721093f5ae1ccdb786fb217b824a2812 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:45:40 +0200 Subject: [PATCH 028/143] feat(working-process): hold a finding from either judged document --- plugins/working-process/rules/workflow.md | 66 +++++++++++++---------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/plugins/working-process/rules/workflow.md b/plugins/working-process/rules/workflow.md index d581838..1fac87e 100644 --- a/plugins/working-process/rules/workflow.md +++ b/plugins/working-process/rules/workflow.md @@ -188,12 +188,13 @@ thing in a contribution the developer must decide and the digest exists to raise decisions rather than bury them. When `docs/domain/glossary.md` exists in the project, its canonical -terms and `_Avoid_` bans bind specs, plans, code identifiers, and -reviews. +terms and `_Avoid_` bans bind design specs, technical designs, plans, +code identifiers, and reviews. When the `elements-of-style:writing-clearly-and-concisely` skill is -available, prose artifacts under `docs/` — specs, plans, ADRs, the -glossary — get its pass: invoke it before drafting a new document, and +available, prose artifacts under `docs/` — design specs, technical +designs, plans, ADRs, the glossary — get its pass: invoke it before +drafting a new document, and run an explicit editing pass over the changed prose of an existing one. The pass binds wording, never decisions. Without the skill there is no substitute pass and no install nagging — the work proceeds normally. @@ -210,8 +211,9 @@ convention fixes the shape and not the spelling. The branch of a worktree created with a generated name is renamed to this shape before its first commit, so the branch a reader sees is the branch the convention names; the worktree's own directory is a separate -name and this convention does not govern it. The work's spec and plan -record the result in their `branch:` field — the topic branch, not the +name and this convention does not govern it. The work's design spec, +technical design and plan record the result in their `branch:` field — +the topic branch, not the `.docs` branch a review loop's per-round commits use, which the spec-plan-lifecycle rule names and which is a sibling of it rather than a second topic branch. The ticket rule's sourcing order reads the @@ -231,8 +233,8 @@ available: already-stamped document leaves no signal and is accepted as lost — never corruption, only a missing re-run. - Before dispatch, resolve any undecided Process directory - (`docs/specs/`, `docs/plans/`) so the first-create question cannot - interrupt the stamp turn. + (`docs/specs/`, `docs/technical-designs/`, `docs/plans/`) so the + first-create question cannot interrupt the stamp turn. - At dispatch, tell the developer the round is running in the background and its result will arrive as a task notification, with progress visible in the session's task list. @@ -394,14 +396,16 @@ decision. Consequences the loop states outright: - Only written decisions license fixes. A decision settled in conversation becomes citable by being written into the document, which the fix itself accomplishes. -- A finding whose `origin` names the spec is held unless a written - decision licenses the edit, since editing a spec from inside a plan - review is design work; `both` holds the same way, and its `held` - line names in `options:` which half is fixable at once. A licensed - spec-origin fix lands in the spec's own ledger and the plan's line - points at it, by the cross-document clause the spec-plan-lifecycle - rule defines — which also leaves the spec's `integrity:` stamp - stale, as any body edit does. +- A finding whose `origin` names a judged document — a design spec or a + technical design — is held unless a written decision licenses the + edit, since changing either from inside a plan review is design work. + `origin` is a list, so a finding naming more than one holds the same + way, and its `held` line names in `options:` which part is fixable at + once. A licensed fix lands in the ledger of the document that + changed, and the plan's line points at it, by the cross-document + clause the spec-plan-lifecycle rule defines — which also leaves the + design spec's `integrity:` stamp stale, as any body edit to either + document of an audit pair does. A fix wave that deviates from a reviewer's suggestion records the deviation and its rationale beside the text they concern — not only in @@ -587,18 +591,24 @@ read: round 1 read the whole document, and every later wave was reviewed by the round that followed it. What that chain still owes differs by document. -For a spec, the consumption gate before plan-writing offers the pair as -one question — an integrity audit or a full-document round — and never -an offer followed by a re-offer of the option just declined. When the -`integrity-auditor` agent is absent the offer carries the full-document -round alone. The two arms cost differently and the offer says so: an -audit returns material for the dispatcher to dispose of and leaves the -verdict alone, while a full-document round on a spec is a new loop's -first round, since the spec's LGTM already closed its loop — it mints -its own verdict and stamps it, so a `concerns` there flips the field -back while plan-writing waits. The full-document-round arm therefore -blocks plan-writing; the audit arm does not, and plan-writing follows -its dispositions. +For a judged document, the consumption gate before plan-writing makes +the pair offer as one question — an integrity audit or a full-document +round — and never an offer followed by a re-offer of the option just +declined. When the `integrity-auditor` agent is absent the offer +carries the full-document round alone. For an audit pair the gate makes +one pair offer for both documents: its audit arm reads the two +together, while choosing its round arm dispatches one full-document +architect round on each document that carries the debt, and +plan-writing waits until every verdict so dispatched is settled under +the rules any verdict follows; the joint integrity audit stays a check +of its own. The two arms cost differently and the +offer says so: an audit returns material for the dispatcher to dispose +of and leaves the verdict alone, while a full-document round on a +judged document is a new loop's first round, since that document's +LGTM already closed its loop — it mints its own verdict and stamps it, +so a `concerns` there flips the field back while plan-writing waits. +The full-document-round arm therefore blocks plan-writing; the audit +arm does not, and plan-writing follows its dispositions. Declining is the developer discharging the chain debt by release rather than by performance, and the discharge is recorded rather than From 41ff529d5a38f411ce7b307b94a542e700af0a6c Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:52:09 +0200 Subject: [PATCH 029/143] feat(working-process): let the architect card name the judged-document class --- plugins/working-process/agents/architect.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/agents/architect.md b/plugins/working-process/agents/architect.md index fb884b5..94251c9 100644 --- a/plugins/working-process/agents/architect.md +++ b/plugins/working-process/agents/architect.md @@ -1,6 +1,6 @@ --- name: architect -description: "Architect reviewing design quality — a grilled spec (primary target) or any design document dispatched standalone; its report always ends in a verdict. For a verdict-free second opinion on a question, dispatch architect-consult instead. Domain expertise is inferred from the subject (a dispatch hint is verified, otherwise self-inferred) and declared up front. Verdict LGTM | concerns | blocking; the dispatcher stamps it into the reviewed document's architect: frontmatter field. Not for failure-mode hunting on plans — that is plan-adversary. Dispatch on the most capable available model. Runs in the background; the verdict arrives as a task notification, and the dispatcher stamps after relay, not before." +description: "Architect reviewing design quality — a grilled design spec (primary target) or any judged document dispatched standalone; its report always ends in a verdict. For a verdict-free second opinion on a question, dispatch architect-consult instead. Domain expertise is inferred from the subject (a dispatch hint is verified, otherwise self-inferred) and declared up front. Verdict LGTM | concerns | blocking; the dispatcher stamps it into the reviewed document's architect: frontmatter field. Not for failure-mode hunting on plans — that is plan-adversary. Dispatch on the most capable available model. Runs in the background; the verdict arrives as a task notification, and the dispatcher stamps after relay, not before." background: true --- @@ -53,7 +53,8 @@ rework if built as designed; `Minor` — naming, clarity, convention. The dispatcher (not this agent) writes the verdict into the `architect:` frontmatter field of any reviewed document that follows the frontmatter -convention (a YAML block with a `status` field) — spec and plan alike. A +convention (a YAML block with a `status` field) — judged document and +plan alike. A bare question has nothing to stamp. Neither consultation surface — the `architect-session` skill or the `architect-consult` agent — ever writes this field; it is stamped only after THIS agent's review. From ed6f74336935ba436a2746a9e5d67937162a4f9f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 17:56:21 +0200 Subject: [PATCH 030/143] feat(working-process): audit a design spec and its technical design as one target --- .../agents/integrity-auditor.md | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/plugins/working-process/agents/integrity-auditor.md b/plugins/working-process/agents/integrity-auditor.md index 6511d87..326f3b1 100644 --- a/plugins/working-process/agents/integrity-auditor.md +++ b/plugins/working-process/agents/integrity-auditor.md @@ -1,6 +1,6 @@ --- name: integrity-auditor -description: "Judgment audit of a churned design document, read on a fresh context — primarily a spec at the consumption gate before plan-writing: reads the document against itself, verifies in both directions every rule the document declares about itself, and reads it once more as an implementer who must build from this text and has no other context. Returns defects each proved by two quotes, plus a separate section of ranked implementer questions, plus the coverage tell that exposes a partial read. Verdict-free and persona-free: it grades nothing and stamps nothing, so an audit whose dispositions are applied is a precondition for the work that follows, never a judgment on the design. Dispatch on the most capable available tier, named explicitly, and in a fresh context — the auditor must not inherit the session that churned the document. Runs in the background; the report arrives as a task notification." +description: "Judgment audit of a churned judged document, read on a fresh context — primarily a design spec at the consumption gate before plan-writing, together with its technical design where it names one: reads the document against itself, verifies in both directions every rule the document declares about itself, and reads it once more as an implementer who must build from this text, the documents audited with it, and the documents its `spec:` and `technical-design:` name, and has no other context. Returns defects each proved by two quotes, plus a separate section of ranked implementer questions, plus the coverage tell that exposes a partial read. Verdict-free and persona-free: it grades nothing and stamps nothing, so an audit whose dispositions are applied is a precondition for the work that follows, never a judgment on the design. Dispatch on the most capable available tier, named explicitly, and in a fresh context — the auditor must not inherit the session that churned the document. Runs in the background; the report arrives as a task notification." background: true --- @@ -73,11 +73,24 @@ tier and returns defects the developer has already fixed. ## Target and moment -Your primary target is a spec, audited at the consumption gate before -plan-writing: after the architect round and after its dispositions are -applied, which is where the churn accumulates. A plan is a permitted -target on explicit request, never a gated one. Read the whole target, -first line to last. +Your primary target is a design spec, audited at the consumption gate +before plan-writing: after the architect round and after its +dispositions are applied, which is where the churn accumulates. Four +cases fix what you read: + +A design spec with no technical design is a single target. A design +spec that names one is audited together with it, both documents the +target and neither the other's context. A plan, a permitted target on +explicit request, never a gated one, is the target alone and reads the +documents its `spec:` and `technical-design:` name as context. A +technical design is never a target alone: its audit is its design +spec's. + +No other field carries context. `revises:` names a document whose +design no longer matches what shipped, and reading an archive as +authority is how a stale claim re-enters a live one. + +Read the whole target, first line to last — every document of it. ## Duties — two lenses; walk both @@ -99,8 +112,10 @@ no longer does: ### 2. Sufficiency for an implementer -Read the document again as a careful implementer who must build from this -text and has no other context: no conversation, no author to ask. Report +Read the document again as a careful implementer who must build from +this text, the documents audited with it, and the documents its `spec:` +and `technical-design:` name, and has no other context: no +conversation, no author to ask. Report the places that text leaves underspecified — a named surface with no defined shape, a behaviour prescribed for one case and silent on its obvious sibling, a value whose source is never named. @@ -117,7 +132,9 @@ Open with the self-report, one line: model: -Then the defects, one entry each: +Then the defects, one entry each — on a joint target the entry opens +with the file, `:
`, so a defect found in either +document of an audit pair says which one carries it: — quote A: "" @@ -132,10 +149,13 @@ first — separate from the defects and never mixed into them. Close with the coverage tell, one line: - coverage: lines; highest line cited + coverage: lines, highest line cited [; …] The tell is how a partial read exposes itself, so report both numbers -even when they embarrass the run. +even when they embarrass the run. A joint target reports a pair per +document: one document read whole while the other was skimmed is the +one risk peculiar to a pair, and a single pair of numbers cannot show +it. Defects are never graded and never counted as Findings. Critical, Important, Minor, and every other severity word stay out of your report: From 9feb97430bc29f47cc2c969739c6d6244f31de05 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:02:34 +0200 Subject: [PATCH 031/143] feat(working-process): read origin as a list and cover the design's parts --- .../working-process/agents/plan-adversary.md | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/plugins/working-process/agents/plan-adversary.md b/plugins/working-process/agents/plan-adversary.md index 2525901..65adb59 100644 --- a/plugins/working-process/agents/plan-adversary.md +++ b/plugins/working-process/agents/plan-adversary.md @@ -1,6 +1,6 @@ --- name: plan-adversary -description: Adversarial reviewer for implementation plans. Hunts the most likely ways the plan is wrong, mis-scoped, or will break silently. Loads domain *-plan-review checklist skills for the domains the plan touches. Severity-graded findings with evidence. Use before implementing any non-trivial plan. Specs are out of scope — design review of a spec belongs to the architect agent. Dispatch on a model scaled to the plan's size and risk — most capable for complex or risky plans, one family below for small mechanical ones; never the cheapest family. Runs in the background; the verdict arrives as a task notification, and the dispatcher stamps after relay, not before. +description: Adversarial reviewer for implementation plans. Hunts the most likely ways the plan is wrong, mis-scoped, or will break silently. Loads domain *-plan-review checklist skills for the domains the plan touches. Severity-graded findings with evidence. Use before implementing any non-trivial plan. Judged documents — a design spec or a technical design — are out of scope; design review of one belongs to the architect agent. Dispatch on a model scaled to the plan's size and risk — most capable for complex or risky plans, one family below for small mechanical ones; never the cheapest family. Runs in the background; the verdict arrives as a task notification, and the dispatcher stamps after relay, not before. background: true --- @@ -41,18 +41,20 @@ without a checklist get the generic dimensions only, with the duty's remaining sources (other skills, then verified model knowledge) covering the expertise. -## Specs: decline +## Judged documents: decline -Handed a spec (a design document, not an implementation plan)? Decline +Handed a judged document (a design spec or a technical design, not an +implementation plan)? Decline the review and point the dispatcher at the `architect` agent. Plan mechanics — named tests, per-phase commits, concrete paths — do not apply -to a design document and would misfire as findings. +to a judged document and would misfire as findings. -Naming a document is not reviewing it. A finding whose `origin` is -`spec` or `both` says where the defect traces to and proposes no change -to the spec, so this boundary holds: what to do about a spec-origin -finding is the dispatcher's, and its own rules hold one for the -developer unless a written decision licenses the edit. +Naming a document is not reviewing it. A finding whose `origin` names a +judged document — a design spec or a technical design — says where the +defect traces to and proposes no change to that document, so this +boundary holds: what to do about such a finding is the dispatcher's, +and its own rules hold one for the developer unless a written decision +licenses the edit. ## Generic dimensions — walk every one; nothing passes by default @@ -89,6 +91,29 @@ developer unless a written decision licenses the edit. broke?* Name it — the plan should have pre-empted it. Record it as a finding. +### 5. Coverage of the technical design's parts + +Read the Parts table of every technical design the plan's design +specs name, reaching each through the plan's `technical-design:` +entries or, where an entry is missing, through the design spec's own +pointer. A missing or extra entry is itself an Important finding whose +`origin` is `implementation-plan`. The plan must cover every part +marked `new`, +`changed` or `retired`; one part may span several tasks and one task +several parts, and a part carried as `unchanged` context needs none. An +uncovered part is an Important finding whose `origin` is +`implementation-plan`. A `change` value outside +`new | changed | retired | unchanged` is an Important finding whose +`origin` is `technical-design`: the set is closed, and an unknown value +would otherwise slip past this dimension unchecked. + +The `**Interfaces:**` blocks are read here too. Where a technical design +applies to the plan, those blocks reference the contracts that design +defines and say which part of one each task implements or changes; a +block that redefines a contract independently is an Important finding +whose `origin` is `implementation-plan`. Where no technical design is +named, the existing plan convention stands and this paragraph is silent. + ## Output { @@ -99,7 +124,7 @@ developer unless a written decision licenses the edit. { "severity": "Critical" | "Important" | "Minor", "section": "", - "origin": "plan" | "spec" | "both", + "origin": ["design-spec" | "technical-design" | "implementation-plan", …], "claim": "", "evidence": "", "suggestion": "" From 85d84015438fa2378d1e5c0d90ed01ed94b14b76 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:06:42 +0200 Subject: [PATCH 032/143] feat(working-process): check the pointer pair in the propagation audit --- .../agents/propagation-auditor.md | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index b30ad42..e9c9b35 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -1,6 +1,6 @@ --- name: propagation-auditor -description: "Mechanical propagation audit of a spec or plan before an expensive dispatch: parses changed interfaces to enumerate their consumers, diffs every prescribed block against the file it targets — a landed change against what shipped, a promised one against the anchor its edit needs — re-derives every counter, and returns located hits with their derivation — or the single line CLEAN. Verdict-free and persona-free: it stamps nothing and grades nothing, so a passing gate is a precondition for the dispatch that follows, never a judgment on the design. Dispatch before every verdict-agent dispatch, after a fix wave, before an integrity audit, and after any multi-site edit during authoring. Run it on the cheapest available family, named explicitly — every duty is procedural, and the never-cheapest rule governs reviews, which an audit is not. Runs in the background; the report arrives as a task notification." +description: "Mechanical propagation audit of a design spec, a technical design or a plan before an expensive dispatch: parses changed interfaces to enumerate their consumers, diffs every prescribed block against the file it targets — a landed change against what shipped, a promised one against the anchor its edit needs — re-derives every counter, and returns located hits with their derivation — or the single line CLEAN. Verdict-free and persona-free: it stamps nothing and grades nothing, so a passing gate is a precondition for the dispatch that follows, never a judgment on the design. Dispatch before every verdict-agent dispatch, after a fix wave, before an integrity audit, and after any multi-site edit during authoring. Run it on the cheapest available family, named explicitly — every duty is procedural, and the never-cheapest rule governs reviews, which an audit is not. Runs in the background; the report arrives as a task notification." background: true --- @@ -111,9 +111,16 @@ claims. ### 5. Cross-document identifier diff -Diff the names one document uses against the names its sources define. A -name the plan uses that the spec never defines is a spec gap, not a plan -error — the invention is the symptom, and report it as the gap it is. +Diff the names one document uses against the names its sources define — +for a plan, the design specs and technical designs its `spec:` and +`technical-design:` name; for a technical design, its design spec and +the domain skills available to its author. A name a plan uses that no +source defines is a gap in the source, not a plan error — the invention +is the symptom, and report it as the gap it is. A technical design mints +part names by design: a name it introduces as its own, or records as a +vocabulary gap, is never a hit. A name it presents as coming from its +design spec or a domain skill is checked at that source like any other — +the exception covers what the design mints, never the names it borrows. Measured: one such gap survived round after round as the cycle's deepest Important. @@ -151,6 +158,25 @@ Read the source, never the finding's summary of it. Where a citation names something outside the repository, report that you could not check it rather than assuming either way. +### 9. The pointer pair + +Where a design spec carries `technical-design:`, or a technical design +carries `spec:`, open both targets and confirm each names the document +that names it. A pointer resolving to a missing file, or to a document +pointing elsewhere, is a hit — the pair identifies the integrity +audit's target, so a broken half silently narrows what the next audit +reads. + +A plan is scoped differently and must not be read as half a pair. Its +`spec:` names a design spec, which never names the plan back; what is +checked there starts from the design specs: for every `spec:` target +that names a technical design, the plan's `technical-design:` must hold +an entry resolving, from the plan's own directory, to that same +document. A missing entry, an extra one, or one resolving elsewhere is +a hit — the check reads each design spec's pointer, so a missing field +is found rather than skipped. A plan whose `spec:` is not named back is +not. + ## Output Open with the self-report, one line: From 17f06aa5032082e53c188b087cc26234ebe82fa1 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:09:32 +0200 Subject: [PATCH 033/143] feat(working-process): cover technical designs in the status pass --- plugins/working-process/skills/process-status/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/working-process/skills/process-status/SKILL.md b/plugins/working-process/skills/process-status/SKILL.md index c62175f..2e275d5 100644 --- a/plugins/working-process/skills/process-status/SKILL.md +++ b/plugins/working-process/skills/process-status/SKILL.md @@ -1,6 +1,6 @@ --- name: process-status -description: "Report what the working process left unfinished in this repo — a pending grilling, an unresolved verdict, a re-review nobody ran, a stamp in the wrong place. Use when the developer asks what is unfinished, what the process still owes, or for a status pass over docs/specs and docs/plans." +description: "Report what the working process left unfinished in this repo — a pending grilling, an unresolved verdict, a re-review nobody ran, a stamp in the wrong place. Use when the developer asks what is unfinished, what the process still owes, or for a status pass over docs/specs, docs/technical-designs and docs/plans." --- # process-status — what the process left unfinished From 4fff48dd3d9582f69653f4240ca57f00add25ffa Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:12:47 +0200 Subject: [PATCH 034/143] docs(working-process): put the technical design in the README and grilling flow --- plugins/working-process/README.md | 40 +++++++++++-------- .../skills/grilling-session/SKILL.md | 16 +++++--- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/plugins/working-process/README.md b/plugins/working-process/README.md index a06efbc..46815b2 100644 --- a/plugins/working-process/README.md +++ b/plugins/working-process/README.md @@ -3,8 +3,9 @@ Tech-agnostic tooling for a spec-driven working process on top of the `superpowers` plugin: -idea → brainstorming (spec) → grilling-session → architect review → -integrity audit → writing-plans (plan) → plan-adversary → +idea → brainstorming (design spec) → grilling-session → architect +review → technical design (when the repository has code) → integrity +audit → writing-plans (plan) → plan-adversary → implementation → code review — with a propagation audit gating every verdict dispatch and the integrity audit itself. @@ -15,7 +16,7 @@ verdict dispatch and the integrity audit itself. (`docs/domain/glossary.md`), sharpens terminology, and records decisions as ADRs. Triggers: "grill me" / "grilling session". - **`architect` agent** — formal design-quality review of a grilled spec - or any design document dispatched standalone; verdict + or any judged document dispatched standalone; verdict `LGTM | concerns | blocking`, stamped into the reviewed document's `architect:` frontmatter field by the dispatcher. Dispatched in the background on the most capable available model; the verdict arrives @@ -45,13 +46,15 @@ verdict dispatch and the integrity audit itself. `architect` dispatch. Triggers: "ask the designer" / "system designer session". - **`plan-adversary` agent** — adversarial review of implementation - plans (plans only; handed a spec it declines toward the `architect` - agent). Generic failure-mode dimensions live here; domain specifics + plans (plans only; handed a judged document it declines toward the + `architect` agent). Generic failure-mode dimensions live here; domain + specifics come from `*-plan-review` checklist skills. Dispatched in the background, scaled to the plan's size and risk; the verdict arrives as a task notification and is stamped after relay. -- **`propagation-auditor` agent** — the mechanical audit of a spec or - plan: it parses every changed interface to enumerate its consumers, +- **`propagation-auditor` agent** — the mechanical audit of a design + spec, a technical design or a plan: it parses every changed interface + to enumerate its consumers, diffs every prescribed block against the file it targets — a landed change against what shipped, a promised one against the anchor its edit needs — re-derives every counter, and runs the document's own @@ -65,7 +68,8 @@ verdict dispatch and the integrity audit itself. offers the same audit at authoring time after any multi-site edit. - **`integrity-auditor` agent** — the judgment audit of a churned document, read on a fresh context: the document against itself, then - the document as an implementer who must build from that text alone. + the document as an implementer who must build from that text and + whatever was audited with it. It reports defects, each proved by two located quotes, beside a ranked list of the questions an implementer would have to ask; it grades nothing and ends in no verdict. Dispatched in the background on the @@ -130,7 +134,7 @@ containing a `status` field: | `architect` | `LGTM` \| `concerns` \| `blocking` | latest architect verdict | | `adversary` | `LGTM` \| `concerns` \| `blocking` | latest plan-adversary verdict | | `architect-fallback` / `adversary-fallback` | ` (degraded )` \| ` (chosen )` \| `…, waived ` | verdict produced below the prescribed tier (`degraded` = unchosen, `chosen` = deliberate); re-review pending until re-reviewed or waived | -| `integrity` | ` (sha: )` | last integrity audit — the date for the reader, the body hash for the check; the dispatcher writes it once the audit's dispositions land, and a spec's consumption gate recomputes the hash to decide whether the stamp still holds | +| `integrity` | ` (sha: [; with: @])` | last integrity audit — the date for the reader, the body hash for the check; the dispatcher writes it once the audit's dispositions land, and a judged document's consumption gate recomputes the hash of every document the stamp names to decide whether the stamp still holds; where a design spec names a technical design the two are audited as one target — an audit pair — and one stamp on the design spec records both | A round ending in `concerns` or `blocking` records its findings in the document body. Concerns later resolved without a fresh round keep the @@ -181,11 +185,13 @@ new gates merely stay silent. ## Process rules -The plugin ships six rule files in `rules/` — the preferred workflow -(always loaded once installed), spec/plan frontmatter and lifecycle, -Process directory conventions, ticket frontmatter, the propagation -duties keyed by the edit that triggers them (`propagation-duties.md`, -loaded while a spec, plan or domain document is open), and the +The plugin ships seven rule files in `rules/` — the preferred workflow +(always loaded once installed), frontmatter and lifecycle for judged +documents and plans, what a technical design must contain +(`technical-design.md`, loaded while one is open), Process directory +conventions, ticket frontmatter, the propagation duties keyed by the +edit that triggers them (`propagation-duties.md`, loaded while a design +spec, technical design, plan or domain document is open), and the review-report contract (`review-reports.md`: where a code-review run writes its Review report and what shape it takes; domain review skills locate the installed contract via its contract probe — the @@ -240,8 +246,10 @@ needs no allow entry. ## Process directories -This plugin creates two directories in a project repo: `docs/domain/` -(glossary + ADRs) and `docs/code-review/` (Review reports — one per +This plugin creates three directories in a project repo: +`docs/domain/` (glossary + ADRs), `docs/technical-designs/` (technical +designs, written when a project accepts the offer) and +`docs/code-review/` (Review reports — one per code-review run, shape defined by the review-reports rule). On first creation the developer is asked whether the directory should be git-ignored (a `.gitignore` containing exactly `*`) or committed; an diff --git a/plugins/working-process/skills/grilling-session/SKILL.md b/plugins/working-process/skills/grilling-session/SKILL.md index 26a85aa..a111db0 100644 --- a/plugins/working-process/skills/grilling-session/SKILL.md +++ b/plugins/working-process/skills/grilling-session/SKILL.md @@ -5,12 +5,16 @@ description: Grilling session that stress-tests a spec (the primary target), pla ## Place in the flow -idea → brainstorming (spec) → **grilling-session on the spec** → -architect review (an `architect` agent dispatch) → writing-plans (plan) → -plan-adversary on the plan → implementation → code review. Offer a -grilling once a spec -exists and before its implementation plan is written. Specs are the -primary target; plans and raw ideas are in scope too. +idea → brainstorming (design spec) → **grilling-session on the spec** → +architect review (an `architect` agent dispatch) → technical design +(when the repository has code) → writing-plans (plan) → plan-adversary +on the plan → implementation → code review. Offer a grilling once a +design spec exists and before its implementation plan is written. +Design specs are the primary target; plans and raw ideas are in scope +too. A technical design is not a grilling target: it mints no +terminology, its vocabulary coming from the design spec, which was +grilled, and from the domain's own skills; the part and component names +it does mint are checked against those skills. ## Glossary first From d87c0b3b13b565033624862a6d76142a30ff915a Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:16:18 +0200 Subject: [PATCH 035/143] docs: declare that this repository skips the technical-design offer --- CLAUDE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index d42d776..0b3e862 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,15 @@ happen (see the plugin-versioning rule). When an isolated workspace is used, its git worktree lives at `.claude/worktrees//` — one per topic branch, git-ignored. +## Technical designs + +This repository does not get the technical-design offer by default. Its +product is prose: a part's name is its path and behaviour is already the +shape of the contract, so a technical design here would restate the +design spec and the plan. The declaration switches the default offer +off; it is not a ban. Ask for a technical design explicitly and the +step runs as it would anywhere else. + ## Authoring skills When creating or editing a skill in any plugin, use the `skill-creator` From a95514923868a395ce55ecd3cec2a7156983b50d Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:19:07 +0200 Subject: [PATCH 036/143] chore(working-process): changelog and dogfooding version for the technical-design step --- .../.claude-plugin/plugin.json | 2 +- plugins/working-process/CHANGELOG.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/working-process/.claude-plugin/plugin.json b/plugins/working-process/.claude-plugin/plugin.json index 20e1255..21050f6 100644 --- a/plugins/working-process/.claude-plugin/plugin.json +++ b/plugins/working-process/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "working-process", "description": "Spec-driven working process on top of superpowers: grilling-session, architect-session, system-designer-session, process-status and sync-rules skills, architect and plan-adversary verdict agents, architect-consult and system-designer-consult consultation agents, propagation-auditor and integrity-auditor audit agents, and process rules distributed as a Rules payload; domain plugins hook in via *-plan-review checklist skills and their own rules/ payloads", - "version": "0.17.0", + "version": "0.18.0-dev.1.technical-design-step", "author": { "name": "Missing Bits (Jacek Nakonieczny)" }, "license": "MIT", "keywords": ["process", "workflow", "spec", "plan", "review", "glossary", "adr", "architecture"], diff --git a/plugins/working-process/CHANGELOG.md b/plugins/working-process/CHANGELOG.md index 2a44099..5ae98fe 100644 --- a/plugins/working-process/CHANGELOG.md +++ b/plugins/working-process/CHANGELOG.md @@ -2,6 +2,25 @@ Released versions of this plugin, newest first. +## Unreleased + +- New `technical-design` rule: a third document class between the design + spec and the plan, saying what a thing is made of — section skeleton, + the definitions of part, contract and state, and the closed `change` + value set. +- The consumption gate offers the document where the repository has + code, and runs in passes when the offer is accepted. +- A design spec and its technical design are audited as one target, with + one `integrity:` stamp on the design spec naming both. +- `origin` on a plan-adversary finding is a list of named documents; + `both` retires. +- The class a design spec and a technical design share is named + `judged document` on the three agent cards that carried the old + phrase and in the README; that phrase is retired. +- Run a rules re-sync after this update: the plan-adversary's `origin` + values changed, and until the re-sync an installed workflow rule + triages the new values by the old names. + ## 0.17.0 — 2026-09-15 - A plan's review loop closes only on a full-document round: a From 94f225d18378a64e6d819dd80cd96e2c279c2e5f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:35:07 +0200 Subject: [PATCH 037/143] fix(working-process): apply the final review's wording fixes --- docs/domain/glossary.md | 17 ++-- .../plans/2026-09-17-technical-design-step.md | 77 +++++++++++++++++-- .../working-process/agents/plan-adversary.md | 17 ++-- .../agents/propagation-auditor.md | 2 +- .../working-process/rules/technical-design.md | 2 +- plugins/working-process/rules/workflow.md | 8 +- 6 files changed, 93 insertions(+), 30 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index 02ade7f..47c5d73 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -137,9 +137,10 @@ _Avoid_: severity tier **Origin**: The documents a review finding traces to, as a list naming one or more of the process's document classes — named by the reviewer that found it -rather than derived at triage. A finding originating in the design spec -is held unless a written decision licenses the edit, since editing one -from inside a plan review is design work. +rather than derived at triage. A finding originating in a judged +document — a design spec or a technical design — is held unless a +written decision licenses the edit, since editing one from inside a +plan review is design work. _Avoid_: owner (for a document), source, `both` **Rule tag**: @@ -317,10 +318,12 @@ _Avoid_: sub-tier record **Consumption gate**: The workflow step at which a document's review verdict is about to be -relied on as the basis of further work — plan-writing for a spec, -implementation for a plan. Five things fire or come due there: the -re-review offer on a fallback-recorded verdict, the integrity audit -offered when a document's body, or that of the document audited with it, +relied on as the basis of further work: for a judged document — a +design spec or a technical design — the gate precedes plan-writing; for +a plan, it precedes implementation. Five things fire or come due +there: the re-review offer on a fallback-recorded verdict, the +integrity audit offered when a document's body, or that of the +document audited with it, no longer matches its stamp, the pair question a diff-scoped chain earns, the chain debt itself, and the offer of a technical design. Only one ordering is fixed: writing the technical design and applying its review dispositions diff --git a/docs/plans/2026-09-17-technical-design-step.md b/docs/plans/2026-09-17-technical-design-step.md index c182de4..cf14bc3 100644 --- a/docs/plans/2026-09-17-technical-design-step.md +++ b/docs/plans/2026-09-17-technical-design-step.md @@ -2815,16 +2815,19 @@ git commit -m "chore(working-process): changelog and dogfooding version for the and 16). Where this plan and the spec's sentence differ, the plan follows the rulings. -- **The technical-design rule words two component mentions +- **The technical-design rule words three component mentions conditionally where the design spec states them plainly.** The spec says the author takes vocabulary from "the Domain expertise duty the - personas already carry" and that "the architect round judges the - boundary". The spec describes a design; the rule Task 2 writes is a - committed project-level rule that loads for people without the - plugin, and the repository's plugin-authoring rule requires skill and - agent mentions in such text to be conditional. The rule says "when the - working-process plugin is installed" and "when that agent is - available"; the design is unchanged. + personas already carry", that "the architect round judges the + boundary", and that the Cuts not taken section is "the material the + architect grades". The spec describes a design; the rule Task 2 + writes is a committed project-level rule that loads for people + without the plugin, and the repository's plugin-authoring rule + requires skill and agent mentions in such text to be conditional. + The rule says "when the working-process plugin is installed" and, + twice, "when that agent is available"; the design is unchanged. + Task 2 dropped the third clause instead of wording it conditionally; + the implementation's final review restored it. - **The grilling-session skill is edited though the spec's Parts table names no row for it.** It carries a copy of the flow line the README carries, and once Task 18 changes one copy the two would disagree, so @@ -2853,6 +2856,14 @@ git commit -m "chore(working-process): changelog and dogfooding version for the validation is a separate, open step the developer owns. Completing this plan's tasks shows the contract landed; it claims nothing about whether the step earns its cost. +- **It gives the `system-designer-session` skill no hand-off to the + technical design.** The workflow rule names the skill as the author, + and it is the always-on rule that tells the session to read the + technical-design rule — which carries the skeleton — and to write to + `docs/technical-designs/`. The skill's own Handing off list does not + mention the step, so a session that opens the skill from its trigger + phrase learns of it only from the workflow rule. The final review + found the gap; a hand-off bullet in the skill is owed and left open. - It renames no existing spec to `-spec.md`, and adds no `-technical-design` skill. Both are out of scope in the spec. - **It changes no plan template.** The `**Interfaces:**` block shape @@ -3593,3 +3604,53 @@ developer's ruling closes the loop at `concerns (resolved 2026-09-25)`: the verdict stays what the reviewer returned, and every finding of all eight rounds and of the independent review is disposed above. +### 2026-09-25 — fix from the implementation's final review + +The subagent-driven implementation ran each task against its own +review and its tree against the simulation, then a whole-branch review +(fable 5.1, `With fixes`). The developer ruled on its findings on +2026-09-25; the fixes land in one commit. + +- fixed 2026-09-25 — [Important] the plan-adversary card's dimension 5 + read `origin` as a scalar, "whose `origin` is …", at four sites while + the schema makes it a list; ruling: 2026-09-25; all four say "names", + departing from Task 15's prescribed text. +- fixed 2026-09-25 — [Minor] the glossary's Origin entry held only a + finding from the design spec, narrower than the triage rule Task 12 + widened; ruling: 2026-09-25; it holds a finding from any judged + document. +- fixed 2026-09-25 — [Minor] the glossary's Consumption gate entry named + plan-writing for a spec alone; ruling: 2026-09-25; it names both + judged documents and says what the gate precedes for each class. +- fixed 2026-09-25 — [Minor] the workflow's triage bullet said "which + part is fixable", colliding with the canonical Part; ruling: + 2026-09-25; it says "which named document". +- fixed 2026-09-25 — [Minor] the workflow's authoring block ended + "following it", where "it" read as the document; ruling: 2026-09-25; + it says "following that rule". +- fixed 2026-09-25 — [Minor] the propagation auditor's duty 9 ended on + an elided predicate, "is not named back is not"; ruling: 2026-09-25; + it says "is not a hit" and why. +- fixed 2026-09-25 — [Minor] the technical-design rule's section 6 + dropped the spec's "the material the architect grades" without a + record; ruling: 2026-09-25; the clause is restored, conditionally, and + the Deviations bullet on conditional mentions counts three. +- declined 2026-09-25 — [Minor] the `system-designer-session` skill + carries no hand-off to the technical design; ruling: 2026-09-25; the + plan stands because the workflow rule already carries the instruction + and the pointer to the skeleton, and the gap is recorded under *What + this plan does not do*. + +Corrected after implementation: the simulation paragraphs of rounds +four to eight each say "no step unsimulated", and each is wrong. Task +14 Step 5 ends "and extend the sentence below it:", a shape that is +neither a replacement nor an insertion; the simulator applied nothing +for it and reported nothing. Every after-check still passed, because +none of them reads the extended sentence. The implementation caught it: +each task's tree was byte-compared with the simulator's snapshot of +that task, and Task 14's differed by exactly that sentence. The +implementer had applied the step as written. The snapshots of Tasks 14 +to 20 were patched with the sentence, and every task from 14 on then +matched byte for byte. That comparison, each task's own verify checks +and Task 20's width check are what was re-verified; the counts the +round paragraphs report are left as run. diff --git a/plugins/working-process/agents/plan-adversary.md b/plugins/working-process/agents/plan-adversary.md index 65adb59..28a6688 100644 --- a/plugins/working-process/agents/plan-adversary.md +++ b/plugins/working-process/agents/plan-adversary.md @@ -97,21 +97,20 @@ Read the Parts table of every technical design the plan's design specs name, reaching each through the plan's `technical-design:` entries or, where an entry is missing, through the design spec's own pointer. A missing or extra entry is itself an Important finding whose -`origin` is `implementation-plan`. The plan must cover every part -marked `new`, -`changed` or `retired`; one part may span several tasks and one task -several parts, and a part carried as `unchanged` context needs none. An -uncovered part is an Important finding whose `origin` is -`implementation-plan`. A `change` value outside +`origin` names `implementation-plan`. The plan must cover every part +marked `new`, `changed` or `retired`; one part may span several tasks +and one task several parts, and a part carried as `unchanged` context +needs none. An uncovered part is an Important finding whose `origin` +names `implementation-plan`. A `change` value outside `new | changed | retired | unchanged` is an Important finding whose -`origin` is `technical-design`: the set is closed, and an unknown value -would otherwise slip past this dimension unchecked. +`origin` names `technical-design`: the set is closed, and an unknown +value would otherwise slip past this dimension unchecked. The `**Interfaces:**` blocks are read here too. Where a technical design applies to the plan, those blocks reference the contracts that design defines and say which part of one each task implements or changes; a block that redefines a contract independently is an Important finding -whose `origin` is `implementation-plan`. Where no technical design is +whose `origin` names `implementation-plan`. Where no technical design is named, the existing plan convention stands and this paragraph is silent. ## Output diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index e9c9b35..cc39bbd 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -175,7 +175,7 @@ an entry resolving, from the plan's own directory, to that same document. A missing entry, an extra one, or one resolving elsewhere is a hit — the check reads each design spec's pointer, so a missing field is found rather than skipped. A plan whose `spec:` is not named back is -not. +not a hit: no design spec names its plans. ## Output diff --git a/plugins/working-process/rules/technical-design.md b/plugins/working-process/rules/technical-design.md index fbaa980..30dbc54 100644 --- a/plugins/working-process/rules/technical-design.md +++ b/plugins/working-process/rules/technical-design.md @@ -27,7 +27,7 @@ location, the name is the path and the design spec carries it. | 3 | Contracts | required | `producer → consumer \| crosses \| guarantee \| breaks when \| check`; a flow crossing three or more parts in fixed order becomes a numbered sequence | | 4 | State | conditional | `record \| home \| written by \| read by \| lifecycle \| visible through` | | 5 | Failure and repetition | conditional | what happens when a step does not finish, runs twice, runs beside another, or finds the other side absent | -| 6 | Cuts not taken | conditional | one line per rejected division and its reason | +| 6 | Cuts not taken | conditional | one line per rejected division and its reason — the material the architect grades, when that agent is available | | 7–8 | Open questions, Review rounds | from the spec-plan-lifecycle rule | as a design spec and a plan carry them today | Three sections are required because only those three are never empty, diff --git a/plugins/working-process/rules/workflow.md b/plugins/working-process/rules/workflow.md index 1fac87e..e78244f 100644 --- a/plugins/working-process/rules/workflow.md +++ b/plugins/working-process/rules/workflow.md @@ -83,7 +83,7 @@ disables its suggestion — never the work itself. it is available and reads the technical-design rule, installed beside this one, before drafting — a path-scoped rule loads only when a matching file is read, and none exists yet — then writes the document - to `docs/technical-designs/` following it. It writes both + to `docs/technical-designs/` following that rule. It writes both ends of the pair in the same turn — the design's `spec:` and the design spec's `technical-design:` — so no audit ever meets a half-written pair. The reviewer is the `architect` agent when that @@ -400,9 +400,9 @@ decision. Consequences the loop states outright: technical design — is held unless a written decision licenses the edit, since changing either from inside a plan review is design work. `origin` is a list, so a finding naming more than one holds the same - way, and its `held` line names in `options:` which part is fixable at - once. A licensed fix lands in the ledger of the document that - changed, and the plan's line points at it, by the cross-document + way, and its `held` line names in `options:` which named document is + fixable at once. A licensed fix lands in the ledger of the document + that changed, and the plan's line points at it, by the cross-document clause the spec-plan-lifecycle rule defines — which also leaves the design spec's `integrity:` stamp stale, as any body edit to either document of an audit pair does. From 84f1df5794981ed55468caaff6b9a27a8c08f6bc Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 18:44:59 +0200 Subject: [PATCH 038/143] docs: give develop ownership of the dev prerelease counter --- .claude/rules/plugin-versioning.md | 43 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/.claude/rules/plugin-versioning.md b/.claude/rules/plugin-versioning.md index 8f248cb..684e19c 100644 --- a/.claude/rules/plugin-versioning.md +++ b/.claude/rules/plugin-versioning.md @@ -30,22 +30,35 @@ paths: the PR body is assembled from the entries that changed. - Dogfooding unreleased content needs a changed version string — the plugin cache keys content by version. A topic branch that dogfoods a - plugin sets `X.Y.Z-dev.` on it — the issue number the - branch name carries, or the branch short-name when the topic has no - issue (e.g. `-dev.design-personas`). The suffix hangs off a version - above the one `develop` currently carries — never a re-suffix of a - version already minted. Widening the discriminator instead of minting - another channel keeps one channel for one purpose; the discriminator - only needs to be unique among parallel topics. The suffix flows into - develop as-is. A topic that does not dogfood never - touches the version. On a version-line merge conflict between parallel - topics, the merging topic's own `-dev.` wins — both - strings are provisional. The release PR strips every `-dev` suffix - while minting the final numbers; the `release-guard` workflow fails - any PR to master that carries a prerelease version or a changed plugin + plugin sets `X.Y.Z-dev..` on it. The discriminator + is the issue number the branch name carries, or the branch short-name + when the topic has no issue (e.g. `-dev.4.design-personas`); it only + needs to be unique among parallel topics. The suffix hangs off a + version above the one `develop` currently carries — never a re-suffix + of a version already minted. A topic that does not dogfood never + touches the version. The release PR strips every `-dev` suffix while + minting the final numbers; the `release-guard` workflow fails any PR + to master that carries a prerelease version or a changed plugin without a bump. -- Prerelease grammar: `-.`. Defined channels: - `dev.` (topic-branch dogfooding, above) and +- `` separates the successive dogfood releases of successive topics, + and `develop` owns it: `n` is one above the highest `-dev..` the + plugin carries in develop's history, and the increment happens as a + topic merges. A topic takes the number optimistically when it starts + dogfooding and re-takes it at merge if develop moved meanwhile, which + is what makes two parallel topics safe — they may hold the same + number while unmerged, and the merge order settles it. Deriving the + counter from develop rather than from a topic is what gives it an + owner; a counter nobody owns walks backwards. + On a version-line merge conflict between parallel topics, the merging + topic's own string wins and takes the next free number — both strings + are provisional until merge. + The number is its own dot-separated identifier on purpose. Semver + compares numeric identifiers numerically and alphanumeric ones as + text, so `dev.1.x` < `dev.2.x` < `dev.10.x` sorts correctly, while + `dev.-` would fold the number into an alphanumeric + identifier and sort `1 < 10 < 2`. Measured, not assumed. +- Prerelease grammar: `-.`. Defined channels: + `dev..` (topic-branch dogfooding, above) and `rc.` (release candidate — a freeze of develop dogfooded as one bundle when a release warrants whole-unit validation; minted by a release-prep commit and stripped by the release PR like any From 41cae57d2d1f9f2eb5fab23e85cfa455c0be3217 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 23:31:24 +0200 Subject: [PATCH 039/143] docs(working-process): plan-coverage spec, architect round 1 --- docs/specs/2026-09-25-plan-coverage-design.md | 596 ++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 docs/specs/2026-09-25-plan-coverage-design.md diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md new file mode 100644 index 0000000..68066f8 --- /dev/null +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -0,0 +1,596 @@ +--- +ticket: none +date: 2026-09-25 +status: draft +grilled: 2026-09-25 +architect: blocking +decisions: registered +branch: feature/plan-coverage +base: develop +--- + +# Plan coverage — every registered decision has an owner + +A plan can drop a decision its design spec made, and today no reader +notices. This spec gives the design spec an enumerable register of the +decisions that need realization, puts each decision's identifier on the +plan tasks that realize it, and has the propagation auditor derive the +coverage from those two lists. The judgment of whether a cited task +really realizes its decision stays with the plan-adversary. + +## Problem + +Measured in an outside project: a spec carried an eight-row revisions +table, the plan implemented six rows, and no task owned row 6. Two +plan-adversary rounds, three propagation audits and the author's +self-review all passed the plan, because each read the plan against +itself — internal consistency, invented identifiers, wrong anchors — and +none carried "every decision has an owning task" in its brief. The gap +surfaced on the next live walk and cost two extra tasks. + +What stands today: + +- The plan-adversary's dimension 5 judges whether a plan covers every + `new`, `changed` or `retired` **part** of a technical design. Nothing + checks the design spec's decisions — with a technical design or + without one. +- The propagation auditor walks nine mechanical duties. None is a + coverage check, and none checks that a column naming another table's + rows resolves. +- Design specs carry no uniform decision list: some have a numbered + `## Decisions`, some `## Settled decisions`, some none. A numbered + Markdown list is positional, so its "decision 5" moves when an item + above it is added. + +## Decisions + +- **D1** — A design spec that sets `decisions: registered` in its + frontmatter carries a `## Decisions` section: the decision register. + Argued in *The register*. +- **D2** — The register is an index, never a copy: an entry is one + identity paragraph — the identifier and a short statement of the + decision — and may point at the section that argues it. Argued in + *The register*. +- **D3** — Identifiers are stable, unique within the spec, and never + positional or reused. An element of a list that needs realization on + its own takes a child identifier (`D4.1`); a parent with children is a + group and is never itself covered. Argued in *The register*. +- **D4** — The register lists every decision that needs realization, + cross-cutting constraints and "leave X unchanged" rulings included — + the latter realized as constraints. Rejected alternatives stay out. + Argued in *What enters the register*. +- **D5** — An entry carries at most one of two state tokens: + `deferred from [ to ], ruling: ` or + `withdrawn , ruling: [; replaced by ]`. A + deferral excludes the decision from the named plan's count alone. + Only a leaf carries a token. The session writes either only on the + developer's explicit decision. Argued in *Entry states*. +- **D6** — The spec author creates the register, with or without a + grilling session; the grilling session updates it as decisions land. + Argued in *Who writes the register*. +- **D7** — A plan descending from at least one registered spec carries + `**Realizes:**` on every task, with identifiers or `none`, and + identifiers on the specific Global Constraints entries that realize a + constraint. Argued in *The plan's annotations*. +- **D8** — The lifecycle rule's sentence "changes no plan template" + is revised: working-process adds exactly the D7 annotations, and the + tool that writes plans keeps the rest of the template. Argued in *The + plan's annotations*. +- **D9** — A new propagation duty derives coverage: every leaf + identifier of every registered spec that is neither withdrawn nor + deferred from the audited plan needs a realizing task or constraint, + and the register itself must be well formed. Argued in *The coverage + duty*. +- **D10** — A second new propagation duty checks table closure: a + column naming another table's rows names only rows that exist. + Argued in *Table closure*. +- **D11** — The auditor's report gains one `decision-coverage:` block + per spec the audited plan names — a summary line and, where the + register could be counted, the map from identifier to citing sites — + before the closing token, on clean runs too; `CLEAN` + means no hit in the checks that ran. Argued in *The report*. +- **D12** — A coverage hit stays a hit, but its fix is triaged by + license, and a fix needing a decision the spec does not make waits + for the developer. The exception to "hits never wait" covers coverage + hits alone. Argued in *Disposing of a coverage hit*. +- **D13** — Four gate-line shapes record that wait: `hit held`, and + its three terminal rewrites `hit fixed …; ruling:`, + `hit deferred …; ruling:` and `hit withdrawn …; ruling:`. `hit held` + joins the Unfinished-work list. Argued in *Gate lines for a held + hit*. +- **D14** — The plan-adversary gains a dimension judging each + decision's realization: do the tasks and constraints citing it + realize it in full together, and does a cited constraint actually + bind. Argued in *Readers*. +- **D15** — The integrity auditor checks the register's completeness + through its existing first lens, prompted by one sentence in its card. + Argued in *Readers*. +- **D16** — `propagation-duties.md`, the author's checklist, changes in + three independent ways. Argued in *The author's checklist*. + - **D16.1** — It gains a row each for the D9 and D10 duties. + - **D16.2** — Its duty and edit counters are recounted. + - **D16.3** — Its duty-2 row gains the anchor case the card states + and the row omits. +- **D17** — A spec without the `decisions:` field is reported as + `not checked` and yields no hit; existing specs migrate at a + substantive revision, never in a sweep. Argued in *Legacy specs*. + +## The register + +The register is the one enumerable source this check needs. Without it, +the list of decisions exists only in the reader's head, and the author +who dropped row 6 from the plan drops it from any list they write +afterwards. + +An entry opens with its identity paragraph: + + - **D3** — . [state token] + + + +The identity paragraph is the list item's first paragraph: its opening +line and the continuation lines that follow it, up to the first of: a +blank line, a nested list item, the next list item at the same or a +higher level, or the end of the section. The auditor joins those lines, +normalising whitespace, and parses the result, so a sentence wrapped +across lines keeps its state token, which stands at the paragraph's +end. Prose beneath, after the blank line, is free. + +The identifier is the literal token `**D**` or, for a child, +`**D.**`, at the start of a list item under `## Decisions` — a +child's item nested under its parent's, where it opens the child's own +identity paragraph. A Markdown numbered list is refused on purpose: its +numbers are positions, and a positional reference breaks when an item +is inserted above it. + +An entry states the decision briefly and, where the argument is long, +names the section that makes it. The grammar enforces the paragraph's +shape, never a sentence count: brevity is authoring guidance with no +mechanical reader. Restating the argument in the register would give +the decision two homes, and the first fix wave would leave the register +describing the decision's old form — the defect an integrity audit +exists to catch, manufactured by the register itself. + +Identifiers are never renumbered and never reused. A decision added +mid-loop takes the next free number. A list whose elements each need +realization — the eight-row table of the measured failure — gives each +element a child identifier. Registering that table as one decision +would let one task citing it pass as 8/8 while realizing six rows, +which is the failure this spec exists to catch. The parent of children +is a group: it enters no count, citing it covers none of its children, +and it carries no state token. A deferral or a withdrawal of the whole +group is written on each of its leaves, since the leaves are what the +count reads. + +## What enters the register + +Every decision that needs realization enters, cross-cutting constraints +included. A ruling that something stays as it is — "leave the gate +order unchanged" — needs realization too, since an implementer can break +it; its realization is a Global Constraints entry. There is therefore no +exempt kind of entry, and no way to reach full coverage by declaring +decisions exempt. + +A rejected alternative never enters: nobody realizes it, and it already +has homes — a spec's out-of-scope section, a technical design's *Cuts +not taken*. + +## Entry states + +An entry without a token is active. Two state tokens exist, a leaf +carries at most one, and a group carries none. Whether a leaf enters a +plan's count is decided against the audited plan, not by the token's +presence alone: + +- `deferred from [ to ], ruling: ` — the developer + decided the decision stands but the named plan does not realize it. + `` is that plan's path relative to the spec, an inline list + where several plans defer the same decision; ``, optional, + names the later plan, spec or ticket expected to take it. The + exclusion is scoped to the named plans: auditing any other plan, the + decision counts as active. A later plan that takes the decision over + therefore meets it in its own count with no token to remove, and one + that should take it over and does not gets the coverage hit. +- `withdrawn , ruling: [; replaced by ]` — the + decision no longer stands. The entry stays as a tombstone that + reserves its identifier; `replaced by` names the successor where one + exists. + +A tombstone keeps the identifier visibly taken, so a reuse shows up as +a duplicate, and a plan still citing it gets a hit that says why. Two +constraints bind `replaced by`: it names an identifier that exists in +the same register and differs from its own, and a chain of successors +never forms a cycle. A successor may itself be withdrawn. + +Deferral and withdrawal never combine: a decision deferred and later +dropped is rewritten from `deferred` to `withdrawn`. Neither is a +disposition of a hit — both change what the spec decides, so the session +writes either token only on the developer's explicit decision, and the +edit lands in the spec's own ledger. That edit leaves the spec's +`integrity:` stamp stale, as any body edit does, and correctly so. + +Each token's `ruling:` is a Ruling in the glossary's sense, dated with +the developer's decision rather than with the edit that recorded it. +It protects that decision from a reviewer's re-raise, never from the +developer: a later ruling may rewrite a deferral as a withdrawal, or +remove a deferral and return the decision to its active state, each +change recorded in the spec's ledger like the first. + +The duplicate check does not catch an author who overwrites a tombstone +with a new active decision under the same identifier; only a comparison +against an earlier version could. Never reusing an identifier stays an +authoring rule, and this spec claims no more for the tombstone than the +duplicate check gives. + +## Who writes the register + +The spec author creates the register while writing the spec, so a +design that never meets a grilling session still gets one. A grilling +session updates it as its decisions land, the way it already updates +the glossary and the ADRs. + +The lifecycle rule states, for every spec carrying +`decisions: registered`, the rule the spec thereby declares about +itself: *the register lists every decision in this spec that needs +realization.* Declared rules are what the integrity auditor's first lens +verifies in both directions, which is how the register's completeness +gets a reader (see *Readers*). + +## The plan's annotations + +A plan whose `spec:` names at least one registered spec carries, on +every task, beside its `**Interfaces:**` block: + + **Realizes:** D3, D5 + **Realizes:** none + +`none` is a value, not an omission. Without it the auditor cannot tell a +housekeeping task from an author who forgot, and it would either report +a false hit or miss a real one. + +A cross-cutting constraint is realized by the Global Constraints entry +that states it, and that entry carries the identifier in its first +line — `(realizes D9)`. Citing the section as a whole realizes nothing. + +A plan descending from one spec uses bare identifiers. A plan descending +from several qualifies each identifier with that spec's path exactly as +the plan's `spec:` writes it — `../specs/.md#D3` — since two specs +may share a basename, and a bare identifier in a multi-spec plan is a +hit. + +The annotation sits on the task rather than in a table at the top of +the plan, for two reasons. Ownership is per task, which is the shape of +the measured failure. And an implementer who sees only their own task +learns from the annotation which decision it realizes. + +This extends the plan template, and the lifecycle rule currently says +the plugin does not: "This binds how the blocks are filled and changes +no plan template — the template belongs to the tool that writes plans." +The sentence is revised to name the exception: working-process adds the +`**Realizes:**` line and the constraint identifiers, and the rest of the +template stays with the tool that writes plans. + +The plan's author writes the annotations, whoever that author is, and +the requirement lives in the lifecycle rule; the tool that writes plans +is not changed. A session writing the plan itself meets the rule when +it reads the design spec, since the rule loads on `docs/specs/**`. A +plan delegated to a separate context cannot count on that load, so the +delegating brief names the lifecycle rule and requires reading it +before the plan is written. A plan that lacks the annotations anyway is +caught at its first gate, and the missing lines take the triage of +*Disposing of a coverage hit*: an identifier is added by fix (a) only +where the task's text actually realizes the decision, never +mechanically. + +## The coverage duty + +A tenth propagation duty runs on a plan. For each spec the plan's +`spec:` names: + +1. Read the spec's `decisions:` field. Absent, report `not checked` + (see *The report*) and stop for that spec. Present with any value + other than `registered`, report a hit and `not counted`, and stop. +2. Check the register is well formed. Each of these is a hit: the field + set with no `## Decisions` section; an identity paragraph that does + not parse; a duplicate identifier; an entry carrying both state + tokens; a group carrying a state token; a `replaced by` naming a + missing identifier, its own, or closing a cycle. Where any of them + fires, the counted set cannot be trusted: report `not counted` and + stop for that spec. +3. Collect the counted set: the leaf identifiers that are neither + withdrawn nor deferred from the audited plan. A deferral naming + another plan does not exclude. +4. Collect every identifier the plan's `**Realizes:**` lines and + constraint entries cite. A cited withdrawn identifier and a cited + group are hits, and so is a cited identifier the register does not + define. That last is an error in the plan, never a gap in the spec: + identifiers are minted only in the register, so a plan citing one the + register lacks has mistyped or invented it. Duty 5 does not take it, + since duty 5 reads an undefined name as a gap in its source. +5. Report every counted identifier that no task and no constraint + cites: one hit per identifier. + +The coverage list is derived, never tallied. The auditor writes the map +from identifier to citing sites, and the fraction is that map's summary; +a bare count would pass one omission offset by one duplicate. A task +carrying no `**Realizes:**` line in a plan the convention binds is a +hit. An identifier cited by several tasks is legal — one decision, many +tasks — and a task split or merged in a fix wave passes as long as the +union of its identifiers survives. + +On a registered design spec audited on its own — the gate before its +architect round — the duty runs steps 1 and 2 alone, so a malformed +register is found before any plan depends on it. + +The duty proves that every *declared* decision has an owner. Whether +the register faithfully lists the spec's decisions is judgment, and +belongs to the integrity auditor (see *Readers*). + +## Table closure + +An eleventh propagation duty, independent of coverage: where a document +writes a table whose column names rows of another table, every name +resolves to a row. It answers "does the named element exist?", where +coverage answers "does every required element have an owner?" — +correct references can coexist with a missed decision, so neither +subsumes the other. Measured on 2026-09-16: walked duty by duty, none of +the nine current duties checks referential integrity between two tables +of one document. The duty generalises past its first case: it also +reads the task-to-part mapping the plan-adversary's dimension 5 relies +on. + +## The report + +The auditor's report today is either located hits or exactly two lines, +`model:` and `CLEAN`, with nothing after the token. Coverage adds a +block per spec the audited plan names, written after the hits and +before the token, and present whether or not any hit fired: + + decision-coverage: 7/8 covered; deferred [D6]; uncovered [D4.2] + D1 → Task 2 + D3 → Task 4, Task 6 + D9 → Global Constraints: No code + … + decision-coverage: not counted — malformed register + decision-coverage: not checked — no decision register + +The summary line opens the block, and under a counted spec the map +follows it, one indented line per counted identifier, naming every task +and constraint that cites it; an uncovered identifier maps to nothing +and is also a hit above. The map is what the fraction summarises, so a +reader can check the one against the other. The fraction's denominator +is the counted set of step 3; identifiers deferred from the audited +plan are listed apart and never counted as covered, while a deferral +naming another plan leaves its identifier in the count. `not counted` +follows a malformed register, whose hits stand above it, and carries no +map, since its denominator cannot be derived. + +A clean audit of a plan therefore runs to the self-report, one block +per spec, and `CLEAN`. `CLEAN` means no hit in the checks that ran, +and a `not checked` line bounds that guarantee in the report itself, so +a dispatcher never re-reads a spec to learn what the audit covered. + +The contract changes in two places edited together: the card's +sentence "a clean audit runs to exactly two lines", and the workflow +rule's paragraph saying a report's body governs, never its closing +token — which gains that a `decision-coverage:` line is neither a hit +nor a violation of the token's position. + +The name differs from the integrity auditor's `coverage:` line on +purpose: that line reports how much of a document the audit read, and +two report lines sharing one token for two facts would invite a +dispatcher to read one as the other. + +## Disposing of a coverage hit + +The detection is mechanical and runs where the measured failure slipped +through: at the gate, on the cheapest family, before the plan-adversary. +The fix is not always mechanical. "Handle retries" does not settle the +limit or the idempotency a task realizing it must state, so writing the +missing task can require a decision nobody has made. A coverage hit +therefore stays a hit — located, ungraded, never a finding — and the +dispatcher classifies its fix by license: + +- **(a)** A task already does the work and lacks only its annotation. + The task's text licenses adding the identifier; the session fixes it. +- **(b)** No task realizes the decision, and the decision's text + settles every choice writing that task requires. The decision + licenses the new task; the session writes it, and the next round's + brief names it among the previous wave's fixes, which the round + attacks first. +- **(c)** Writing the task needs a choice the decision does not make. + The question is that missing decision — not the coverage gap, which + is already established. The hit is held for the developer. + +A held hit blocks the dispatch its gate guards, as a held finding +already blocks a fresh round. The answer lands in the spec — a new or a +sharpened register entry — or, where the developer defers or drops the +decision, as its `deferred` or `withdrawn` token. The plan is then +fixed and the gate re-run, and the plan-adversary dispatches only once +the gate passes. + +The workflow rule's "hits never wait for the developer" gains one named +exception, for coverage hits alone. Every other hit keeps a fix +licensed by its own derivation. + +## Gate lines for a held hit + +The lifecycle rule's two gate-line shapes gain a third, and three +terminal rewrites of it: + + - hit held — ; question: ; options: + - hit fixed — ; ; ruling: + - hit deferred — ; ruling: ; # + - hit withdrawn — ; ruling: ; # + +A `hit held` line is rewritten in place to one of the three terminal +shapes, so the state always has one home. The rewrite happens after the +gate re-runs over the changed spec and plan, never on the developer's +answer alone. `hit fixed … ruling:` records that the developer settled +the missing decision and the plan now realizes it. `hit deferred` +records an approved deferral, and `hit withdrawn` a decision the +developer dropped, its register entry now a tombstone. Neither is +`hit dismissed`, because the gap was real when the hit fired. The +ordinary `hit fixed` shape, without `ruling:`, stays as it is for every +hit whose fix its derivation licensed. + +`ruling:` on a gate line means what it means on a disposition line: the +developer decided. A diff-scoped round treats such a line as settled, +as it treats any line carrying `ruling:`. + +Placement follows the existing rule, with one exception. A gate after a +round writes its lines under that round's heading. A gate before a +plan's first round has no heading to write under, and the existing rule +makes its lines wait for that round's stamp — which a held hit cannot +do, since the round it waits for cannot start. A pre-round gate +episode that holds at least one hit therefore writes every one of its +lines — the `hit held` line and its siblings alike — in the +`## Review rounds` section before the first round heading, and they +stay there, rewrites included: no round produced them, and one episode +keeps one home. A pre-round episode that holds nothing keeps the +existing rule. + +`hit held` joins the Unfinished-work list by widening the *Unfinished +review-loop ledger* entry's command to match it, within that entry's +existing scope — inside a `## Review rounds` section, where the +pre-round line also sits. Its owner is the developer, as for any `held` +line. The three terminal shapes stop matching, which is their closed +state. + +## Readers + +Each check has one owner: + +| check | owner | kind | +|---|---|---| +| every cited identifier exists | propagation auditor, coverage duty (new) | mechanical | +| every counted identifier is cited; the register is well formed | propagation auditor, coverage duty (new) | mechanical | +| a column naming rows of another table resolves | propagation auditor, table-closure duty (new) | mechanical | +| the sites citing a decision realize it in full together; a cited constraint binds | plan-adversary, new dimension | judgment | +| the register lists every decision in the spec that needs realization | integrity auditor, lens 1 (existing) | judgment | +| a technical design's parts are covered by tasks | plan-adversary, dimension 5 (unchanged) | judgment | + +The plan-adversary's new dimension sits beside dimension 5. It reads +the annotations in the plan itself, never the auditor's report, which +reaches the dispatcher rather than the adversary; the gate has already +established that every counted identifier is cited. The unit it judges +is the decision, never the single site: one decision split across +several tasks is legal, so the question is whether all the tasks and +constraints citing it realize it in full together. A decision they +realize only in part — six rows of eight — is an Important finding with +`origin` naming the plan. A decision the developer deferred from the +plan under review is not the adversary's to judge; one deferred from +another plan is judged like any other. + +The integrity auditor needs no new lens. Its first lens verifies every +rule a document declares about itself, in both directions, and a +registered spec declares one (see *Who writes the register*): each entry +rests on the spec's text, and each decision in the text that needs +realization has an entry. A rejected alternative without an entry is +therefore no defect. +One sentence in the card names the register as such a declared rule. A +decision left only in a paragraph is then a defect the audit proves with +two quotes, at the consumption gate, right before plan-writing. + +The architect gains nothing. A decision missing from the register is an +integrity-class defect, which the architect persona already hands to +the integrity audit; a second owner would duplicate that check at the +most expensive tier. + +## The author's checklist + +`propagation-duties.md` keys the duties by the edit an author has just +made, and gains three things: + +- a row for the coverage duty — *added, changed, deferred or withdrawn a + register entry, or added, split or merged a plan task* → the register + against every `**Realizes:**` line and constraint entry; +- a row for the table-closure duty — *written a column naming rows of + another table* → every name against that table; +- the third anchor case the duty-2 row omits, which the card states: + "one an earlier task in the same document has already rewritten". + +The rule's counters — "nine duties, keyed by the ten edits" — are +recounted as part of the same edit. + +## Legacy specs + +A spec without the `decisions:` field is legacy: the audit reports +`decision-coverage: … not checked` for it and raises no hit. A field +carrying any other value than `registered` is not legacy but a +malformed register (see *The coverage duty*). A plan descending from +legacy specs alone carries no `**Realizes:**` lines. An existing spec +migrates when it is next substantively revised, never in a sweep; +its plan then meets coverage hits at its next gate. + +`not checked` is visible in every report, but nothing forces a new spec +to set the field. A project-level statement that the convention is +adopted — under which a new spec without a register would be a hit — +needs a durable home for a project's standing process answers, and none +exists yet. Until that home exists, the field is expected in every new +design spec and its absence is reported, not refused. + +## Out of scope + +- The technical design's side of coverage — a Scope table mapping + decisions to parts, contracts or state. It needs row keys the + Contracts table and the Failure section do not have, and no measured + failure calls for it yet. Dimension 5 stays as it is. +- Enforcing the register project-wide (see *Legacy specs*). +- A shape for a hit left outstanding when the gate's re-dispatch bound + stops an episode. The lifecycle rule names that gap already; `hit + held` is scoped to coverage hits by D12 and does not close it. The + two share a shape but not a question: a held coverage hit waits on a + decision the spec lacks, while an outstanding hit marks a gate that + failed to converge, and folding the second in would widen D12's + exception beyond the case it was argued for. +- Migrating this repository's existing specs. + +## Changes by file + +All under `plugins/working-process/` unless noted. + +- `agents/propagation-auditor.md` — duties 10 (coverage) and 11 (table + closure); the `decision-coverage:` output line; the "exactly two + lines" sentence; the coverage exception in *What becomes of your + hits*. +- `agents/plan-adversary.md` — the realization-site dimension. +- `agents/integrity-auditor.md` — one sentence naming the register as a + declared rule for lens 1. +- `rules/spec-plan-lifecycle.md` — the `decisions:` field in the + frontmatter contract; the register's grammar, states and declared + rule; the revised template sentence; the four gate-line shapes and + the pre-round placement; the widened *Unfinished review-loop ledger* + command. +- `rules/workflow.md` — the coverage exception to "hits never wait"; + the `decision-coverage:` line beside the closing-token paragraph; in + step 4, the duty of a brief delegating plan-writing to name the + lifecycle rule and require reading it. +- `rules/propagation-duties.md` — the rows and counters of *The + author's checklist*. +- `skills/grilling-session/SKILL.md` — updating the register as + decisions land. +- `docs/domain/glossary.md` (repo) — applied at the grilling of + 2026-09-25: new **Decision register** and **Decision coverage** + entries; **Ruling** widened to held hits and register state changes; + **Hit** gained the held coverage hit. +- `README.md`, `CHANGELOG.md` — the new duties and the convention. + +## Open questions + +None at the time of writing. + +## Review rounds + +### 2026-09-25 — architect, fable 5.1, blocking (round 1, full-document) + +- held — [Important] F1: the deferral lives in the spec, against the token's own meaning and the pointer direction; an implemented spec's frozen body leaves a later plan's deferral nowhere to land; question: where does a deferral live — the spec's register entry, or the plan that does not realize the decision?; options: (a) keep the `deferred from ` token in the spec, argued by ADR 0004, and accept that an implemented spec cannot take a later plan's deferral; (b) move it to the plan as `**Defers:** D6 — ; ruling: `, scoped to that plan by construction, the spec naming no plans — recommended: (b) +- held — [Important] F2: the table-closure duty names no detector for a column naming another table's rows; question: what detects a column that names another table's rows?; options: (a) a column whose cells match the key column of another table in the same document, a cell matching none being the hit; (b) a column whose header names another table; (c) both, either sufficing — recommended: (a), since it needs no naming convention and derives by comparison +- fixed 2026-09-25 — [Important] F3: an undefined cited identifier goes to duty 5, whose disposition treats it as a gap in the spec; license: *The register* (identifiers are the register's literal tokens, so none is minted elsewhere) and the propagation-auditor card's duty 5 ("a gap in the source, not a plan error"); step 4 of *The coverage duty* now reports it as a plan error of the coverage duty, and the *Readers* table's first row names that duty +- fixed 2026-09-25 — [Minor] F4: adding `decisions` to the Misplaced stamp command contradicts the glossary's definition of that term; license: glossary **Misplaced stamp** ("a process field … where the stamping steps put it"); the addition is dropped from *Changes by file* +- fixed 2026-09-25 — [Minor] F5: D2's "one sentence" has no reader; license: *The register*'s identity-paragraph grammar; D2 and *The register* now say "a short statement" and that the grammar enforces the paragraph's shape, not a sentence count +- fixed 2026-09-25 — [Minor] F6: a pre-round gate episode's lines land in two homes; license: *Gate lines for a held hit* ("no round produced it"); a pre-round episode holding a hit now writes all its lines before the first round heading +- fixed 2026-09-25 — [Minor] F7: the re-dispatch-bound hit shares `hit held`'s shape and the spec does not say why it stays out; license: D12 (the exception covers coverage hits alone); *Out of scope* now gives the reason +- held — [Minor] F8: two grammars carry one fact — `**Realizes:**` on a task and `(realizes D9)` on a constraint; question: one shape for both sites?; options: (a) `**Realizes:** D9` as the constraint entry's first clause too; (b) keep `(realizes D9)` on constraints; (c) `Realizes: D9` without bold in both — recommended: (a), one pattern for the cheapest family to parse +- signal 2026-09-25 — another round pays only after F1 is decided; then one diff-scoped round should suffice, and the Minors are worth a fix wave rather than a round of their own From f8f1578793f444d25e91ba4165f262faabb5460d Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 23:39:59 +0200 Subject: [PATCH 040/143] docs(working-process): plan-coverage spec, round 1 fix wave --- docs/specs/2026-09-25-plan-coverage-design.md | 232 +++++++++++------- 1 file changed, 144 insertions(+), 88 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 68066f8..42f9091 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -59,12 +59,10 @@ What stands today: cross-cutting constraints and "leave X unchanged" rulings included — the latter realized as constraints. Rejected alternatives stay out. Argued in *What enters the register*. -- **D5** — An entry carries at most one of two state tokens: - `deferred from [ to ], ruling: ` or - `withdrawn , ruling: [; replaced by ]`. A - deferral excludes the decision from the named plan's count alone. - Only a leaf carries a token. The session writes either only on the - developer's explicit decision. Argued in *Entry states*. +- **D5** — An entry carries one state token or none: + `withdrawn , ruling: [; replaced by ]`. Only a leaf + carries it, and the session writes it only on the developer's + explicit decision. Argued in *Entry states*. - **D6** — The spec author creates the register, with or without a grilling session; the grilling session updates it as decisions land. Argued in *Who writes the register*. @@ -73,17 +71,19 @@ What stands today: identifiers on the specific Global Constraints entries that realize a constraint. Argued in *The plan's annotations*. - **D8** — The lifecycle rule's sentence "changes no plan template" - is revised: working-process adds exactly the D7 annotations, and the + is revised: working-process adds exactly the D7 and D18 annotations, + and the tool that writes plans keeps the rest of the template. Argued in *The plan's annotations*. - **D9** — A new propagation duty derives coverage: every leaf identifier of every registered spec that is neither withdrawn nor - deferred from the audited plan needs a realizing task or constraint, + deferred by the audited plan needs a realizing task or constraint, and the register itself must be well formed. Argued in *The coverage duty*. -- **D10** — A second new propagation duty checks table closure: a - column naming another table's rows names only rows that exist. - Argued in *Table closure*. +- **D10** — A second new propagation duty checks table closure: where + the rule defining a table declares that a column names rows of + another table, every name resolves to exactly one row. Argued in + *Table closure*. - **D11** — The auditor's report gains one `decision-coverage:` block per spec the audited plan names — a summary line and, where the register could be counted, the map from identifier to citing sites — @@ -114,6 +114,10 @@ What stands today: - **D17** — A spec without the `decisions:` field is reported as `not checked` and yields no hit; existing specs migrate at a substantive revision, never in a sweep. Argued in *Legacy specs*. +- **D18** — A plan that leaves a registered decision unrealized says so + in a `**Defers:**` line, one per decision, carrying the developer's + ruling; the deferral binds that plan alone, and the spec names no + plan. Argued in *Deferral*. ## The register @@ -159,8 +163,8 @@ element a child identifier. Registering that table as one decision would let one task citing it pass as 8/8 while realizing six rows, which is the failure this spec exists to catch. The parent of children is a group: it enters no count, citing it covers none of its children, -and it carries no state token. A deferral or a withdrawal of the whole -group is written on each of its leaves, since the leaves are what the +and it carries no state token. A withdrawal or a deferral of the whole +group is written for each of its leaves, since the leaves are what the count reads. ## What enters the register @@ -178,24 +182,16 @@ not taken*. ## Entry states -An entry without a token is active. Two state tokens exist, a leaf -carries at most one, and a group carries none. Whether a leaf enters a -plan's count is decided against the audited plan, not by the token's -presence alone: - -- `deferred from [ to ], ruling: ` — the developer - decided the decision stands but the named plan does not realize it. - `` is that plan's path relative to the spec, an inline list - where several plans defer the same decision; ``, optional, - names the later plan, spec or ticket expected to take it. The - exclusion is scoped to the named plans: auditing any other plan, the - decision counts as active. A later plan that takes the decision over - therefore meets it in its own count with no token to remove, and one - that should take it over and does not gets the coverage hit. -- `withdrawn , ruling: [; replaced by ]` — the - decision no longer stands. The entry stays as a tombstone that - reserves its identifier; `replaced by` names the successor where one - exists. +An entry without a token is active. One state token exists, a leaf +carries it or not, and a group never does: + + withdrawn , ruling: [; replaced by ] + +The decision no longer stands. The entry stays as a tombstone that +reserves its identifier; `replaced by` names the successor where one +exists. A decision that still stands but one plan does not realize is +not a state of the entry at all — that is a deferral, and the plan +records it (see *Deferral*). A tombstone keeps the identifier visibly taken, so a reuse shows up as a duplicate, and a plan still citing it gets a hit that says why. Two @@ -203,19 +199,18 @@ constraints bind `replaced by`: it names an identifier that exists in the same register and differs from its own, and a chain of successors never forms a cycle. A successor may itself be withdrawn. -Deferral and withdrawal never combine: a decision deferred and later -dropped is rewritten from `deferred` to `withdrawn`. Neither is a -disposition of a hit — both change what the spec decides, so the session -writes either token only on the developer's explicit decision, and the -edit lands in the spec's own ledger. That edit leaves the spec's -`integrity:` stamp stale, as any body edit does, and correctly so. +A withdrawal is not a disposition of a hit: it changes what the spec +decides, so the session writes the token only on the developer's +explicit decision, and the edit lands in the spec's own ledger. That +edit leaves the spec's `integrity:` stamp stale, as any body edit does, +and correctly so. Withdrawing a decision of a spec already +`implemented` is no edit of its frozen body but a revision: a newer +design spec carries `revises:` and the register that stands. -Each token's `ruling:` is a Ruling in the glossary's sense, dated with +The token's `ruling:` is a Ruling in the glossary's sense, dated with the developer's decision rather than with the edit that recorded it. It protects that decision from a reviewer's re-raise, never from the -developer: a later ruling may rewrite a deferral as a withdrawal, or -remove a deferral and return the decision to its active state, each -change recorded in the spec's ledger like the first. +developer, whose later ruling may revise it like any other. The duplicate check does not catch an author who overwrites a tombstone with a new active decision under the same identifier; only a comparison @@ -250,8 +245,11 @@ housekeeping task from an author who forgot, and it would either report a false hit or miss a real one. A cross-cutting constraint is realized by the Global Constraints entry -that states it, and that entry carries the identifier in its first -line — `(realizes D9)`. Citing the section as a whole realizes nothing. +that states it, and that entry opens with the same annotation, as its +first clause — `**Realizes:** D9`. One shape serves both sites: the +values, the qualification of identifiers and the wrapping rules are +shared, and only the place differs. Citing the section as a whole +realizes nothing. A plan descending from one spec uses bare identifiers. A plan descending from several qualifies each identifier with that spec's path exactly as @@ -268,8 +266,9 @@ This extends the plan template, and the lifecycle rule currently says the plugin does not: "This binds how the blocks are filled and changes no plan template — the template belongs to the tool that writes plans." The sentence is revised to name the exception: working-process adds the -`**Realizes:**` line and the constraint identifiers, and the rest of the -template stays with the tool that writes plans. +`**Realizes:**` annotation on tasks and constraint entries and the +`**Defers:**` lines of *Deferral*, and the rest of the template stays +with the tool that writes plans. The plan's author writes the annotations, whoever that author is, and the requirement lives in the lifecycle rule; the tool that writes plans @@ -283,6 +282,34 @@ caught at its first gate, and the missing lines take the triage of where the task's text actually realizes the decision, never mechanically. +## Deferral + +A decision that stands, but that this plan does not realize, is +deferred by the plan, not by the spec: + + **Defers:** D6 — ; ruling: + +The line sits beside the Global Constraints section, one line per +deferred identifier, qualified as the plan's `**Realizes:**` +annotations are. It carries the developer's ruling, and the session +writes it only on the developer's explicit decision. + +The plan is the right home for three reasons. The deferral is a fact +about this plan — the decision still stands — so it binds this plan +alone by construction: auditing any other plan descending from the same +spec, the decision counts, and a later plan meant to take it over and +not doing so gets the coverage hit. The spec names no plan, which keeps +pointers running from the plan to the spec as they always do. And an +`implemented` spec, whose body is frozen, never needs editing for a +later plan to defer one of its decisions. + +A `**Defers:**` line naming an identifier the register does not define, +a withdrawn one or a group is a hit. So is one identifier both deferred +and cited by a `**Realizes:**` annotation in the same plan: the plan +cannot both realize and defer it. The plan-adversary does not judge a +deferral the developer ruled; it judges a plan whose other tasks quietly +depend on the deferred decision anyway. + ## The coverage duty A tenth propagation duty runs on a plan. For each spec the plan's @@ -293,21 +320,22 @@ A tenth propagation duty runs on a plan. For each spec the plan's other than `registered`, report a hit and `not counted`, and stop. 2. Check the register is well formed. Each of these is a hit: the field set with no `## Decisions` section; an identity paragraph that does - not parse; a duplicate identifier; an entry carrying both state - tokens; a group carrying a state token; a `replaced by` naming a + not parse; a duplicate identifier; an unknown state token; a group + carrying a state token; a `replaced by` naming a missing identifier, its own, or closing a cycle. Where any of them fires, the counted set cannot be trusted: report `not counted` and stop for that spec. -3. Collect the counted set: the leaf identifiers that are neither - withdrawn nor deferred from the audited plan. A deferral naming - another plan does not exclude. -4. Collect every identifier the plan's `**Realizes:**` lines and - constraint entries cite. A cited withdrawn identifier and a cited - group are hits, and so is a cited identifier the register does not - define. That last is an error in the plan, never a gap in the spec: - identifiers are minted only in the register, so a plan citing one the - register lacks has mistyped or invented it. Duty 5 does not take it, - since duty 5 reads an undefined name as a gap in its source. +3. Collect the counted set: the leaf identifiers that are not + withdrawn, less those the audited plan's `**Defers:**` lines name. + The `**Defers:**` lines are checked as *Deferral* says. +4. Collect every identifier the plan's `**Realizes:**` annotations + cite, on tasks and constraint entries. A cited withdrawn identifier + and a cited group are hits, and so is a cited identifier the + register does not define. That last is an error in the plan, never + a gap in the spec: identifiers are minted only in the register, so a + plan citing one the register lacks has mistyped or invented it. + Duty 5 does not take it, since duty 5 reads an undefined name as a + gap in its source. 5. Report every counted identifier that no task and no constraint cites: one hit per identifier. @@ -329,16 +357,40 @@ belongs to the integrity auditor (see *Readers*). ## Table closure -An eleventh propagation duty, independent of coverage: where a document -writes a table whose column names rows of another table, every name -resolves to a row. It answers "does the named element exist?", where -coverage answers "does every required element have an owner?" — -correct references can coexist with a missed decision, so neither -subsumes the other. Measured on 2026-09-16: walked duty by duty, none of -the nine current duties checks referential integrity between two tables -of one document. The duty generalises past its first case: it also -reads the task-to-part mapping the plan-adversary's dimension 5 relies -on. +An eleventh propagation duty, independent of coverage. It answers "does +the named element exist?", where coverage answers "does every required +element have an owner?" — correct references can coexist with a missed +decision, so neither subsumes the other. Measured on 2026-09-16: walked +duty by duty, none of the nine current duties checks referential +integrity between two tables of one document. + +The duty checks declared relations only, never guessed ones. Matching +cell values would be a false detector: two unrelated columns can share +names by chance, and a relation whose every reference is wrong would +match nothing and go unseen. The relation is declared by the rule that +defines the table's columns, which is also the rule that closes them, +so the auditor knows the relation before it compares a single cell and +no document needs an annotation of its own. The technical-design rule +declares the first ones: + +| column | names rows of | +|---|---| +| Contracts `producer → consumer`, each side | Parts `part` | +| State `written by` | Parts `part` | +| State `read by` | Parts `part` | + +A declaration says how its cells split: a cell may name several rows, +comma-separated, and `producer → consumer` splits at the arrow before +it splits at commas. It says which values are not references: a party +outside the system is written `external: `, and `—` states that +the cell names nothing; neither is resolved. Every other name resolves +to exactly one row of the named table: a name matching no row is a +hit, and so is a name matching several, since a duplicated key makes +every reference to it ambiguous. + +A table no rule declares a relation for is not checked by this duty, +and nothing says it was: the report states what the duty covers, and a +document's ad-hoc tables are outside it. ## The report @@ -360,9 +412,8 @@ follows it, one indented line per counted identifier, naming every task and constraint that cites it; an uncovered identifier maps to nothing and is also a hit above. The map is what the fraction summarises, so a reader can check the one against the other. The fraction's denominator -is the counted set of step 3; identifiers deferred from the audited -plan are listed apart and never counted as covered, while a deferral -naming another plan leaves its identifier in the count. `not counted` +is the counted set of step 3; identifiers the audited plan defers are +listed apart and never counted as covered. `not counted` follows a malformed register, whose hits stand above it, and carries no map, since its denominator cannot be derived. @@ -404,11 +455,12 @@ dispatcher classifies its fix by license: is already established. The hit is held for the developer. A held hit blocks the dispatch its gate guards, as a held finding -already blocks a fresh round. The answer lands in the spec — a new or a -sharpened register entry — or, where the developer defers or drops the -decision, as its `deferred` or `withdrawn` token. The plan is then -fixed and the gate re-run, and the plan-adversary dispatches only once -the gate passes. +already blocks a fresh round. The answer lands where the decision +belongs: in the spec as a new or a sharpened register entry, or as the +entry's `withdrawn` token where the developer drops the decision; in +the plan as a `**Defers:**` line where the developer defers it. The +plan is then fixed and the gate re-run, and the plan-adversary +dispatches only once the gate passes. The workflow rule's "hits never wait for the developer" gains one named exception, for coverage hits alone. Every other hit keeps a fix @@ -421,7 +473,7 @@ terminal rewrites of it: - hit held — ; question: ; options: - hit fixed — ; ; ruling: - - hit deferred — ; ruling: ; # + - hit deferred — ; ruling: ; - hit withdrawn — ; ruling: ; # A `hit held` line is rewritten in place to one of the three terminal @@ -429,7 +481,8 @@ shapes, so the state always has one home. The rewrite happens after the gate re-runs over the changed spec and plan, never on the developer's answer alone. `hit fixed … ruling:` records that the developer settled the missing decision and the plan now realizes it. `hit deferred` -records an approved deferral, and `hit withdrawn` a decision the +records an approved deferral, now a `**Defers:**` line in the plan, and +`hit withdrawn` a decision the developer dropped, its register entry now a tombstone. Neither is `hit dismissed`, because the gap was real when the hit fired. The ordinary `hit fixed` shape, without `ruling:`, stays as it is for every @@ -479,9 +532,8 @@ is the decision, never the single site: one decision split across several tasks is legal, so the question is whether all the tasks and constraints citing it realize it in full together. A decision they realize only in part — six rows of eight — is an Important finding with -`origin` naming the plan. A decision the developer deferred from the -plan under review is not the adversary's to judge; one deferred from -another plan is judged like any other. +`origin` naming the plan. The plan's own deferrals are judged as +*Deferral* says. The integrity auditor needs no new lens. Its first lens verifies every rule a document declares about itself, in both directions, and a @@ -503,9 +555,9 @@ most expensive tier. `propagation-duties.md` keys the duties by the edit an author has just made, and gains three things: -- a row for the coverage duty — *added, changed, deferred or withdrawn a - register entry, or added, split or merged a plan task* → the register - against every `**Realizes:**` line and constraint entry; +- a row for the coverage duty — *added, changed or withdrawn a register + entry, or added, split, merged or deferred in a plan* → the register + against every `**Realizes:**` annotation and `**Defers:**` line; - a row for the table-closure duty — *written a column naming rows of another table* → every name against that table; - the third anchor case the duty-2 row omits, which the card states: @@ -560,9 +612,13 @@ All under `plugins/working-process/` unless noted. declared rule for lens 1. - `rules/spec-plan-lifecycle.md` — the `decisions:` field in the frontmatter contract; the register's grammar, states and declared - rule; the revised template sentence; the four gate-line shapes and - the pre-round placement; the widened *Unfinished review-loop ledger* + rule; the revised template sentence; the `**Realizes:**` and + `**Defers:**` annotations; the four gate-line shapes and the + pre-round placement; the widened *Unfinished review-loop ledger* command. +- `rules/technical-design.md` — the declared relations of *Table + closure*: which columns name rows of Parts, how their cells split, + and the `external:` and `—` values. - `rules/workflow.md` — the coverage exception to "hits never wait"; the `decision-coverage:` line beside the closing-token paragraph; in step 4, the duty of a brief delegating plan-writing to name the @@ -585,12 +641,12 @@ None at the time of writing. ### 2026-09-25 — architect, fable 5.1, blocking (round 1, full-document) -- held — [Important] F1: the deferral lives in the spec, against the token's own meaning and the pointer direction; an implemented spec's frozen body leaves a later plan's deferral nowhere to land; question: where does a deferral live — the spec's register entry, or the plan that does not realize the decision?; options: (a) keep the `deferred from ` token in the spec, argued by ADR 0004, and accept that an implemented spec cannot take a later plan's deferral; (b) move it to the plan as `**Defers:** D6 — ; ruling: `, scoped to that plan by construction, the spec naming no plans — recommended: (b) -- held — [Important] F2: the table-closure duty names no detector for a column naming another table's rows; question: what detects a column that names another table's rows?; options: (a) a column whose cells match the key column of another table in the same document, a cell matching none being the hit; (b) a column whose header names another table; (c) both, either sufficing — recommended: (a), since it needs no naming convention and derives by comparison +- fixed 2026-09-25 — [Important] F1: the deferral lives in the spec, against the token's own meaning and the pointer direction; ruling: 2026-09-25; option (b) — the deferral moved to the plan as a `**Defers:**` line (D18, new section *Deferral*), the spec keeping `withdrawn` as its only state token (D5, *Entry states*), counted set in step 3, `hit deferred` and the glossary's **Decision coverage** and **Ruling** updated; a plan both deferring and realizing one identifier is a hit +- fixed 2026-09-25 — [Important] F2: the table-closure duty names no detector; ruling: 2026-09-25; deviation: *Table closure* — none of the offered options: relations are declared by the rule that defines the table's columns (the technical-design rule: Contracts and State columns → Parts), with cell splitting, `external:` and `—` values, and a duplicated key as a hit; value matching refused as a false detector; the unsupported task-to-part claim deleted; `rules/technical-design.md` added to *Changes by file* - fixed 2026-09-25 — [Important] F3: an undefined cited identifier goes to duty 5, whose disposition treats it as a gap in the spec; license: *The register* (identifiers are the register's literal tokens, so none is minted elsewhere) and the propagation-auditor card's duty 5 ("a gap in the source, not a plan error"); step 4 of *The coverage duty* now reports it as a plan error of the coverage duty, and the *Readers* table's first row names that duty - fixed 2026-09-25 — [Minor] F4: adding `decisions` to the Misplaced stamp command contradicts the glossary's definition of that term; license: glossary **Misplaced stamp** ("a process field … where the stamping steps put it"); the addition is dropped from *Changes by file* - fixed 2026-09-25 — [Minor] F5: D2's "one sentence" has no reader; license: *The register*'s identity-paragraph grammar; D2 and *The register* now say "a short statement" and that the grammar enforces the paragraph's shape, not a sentence count - fixed 2026-09-25 — [Minor] F6: a pre-round gate episode's lines land in two homes; license: *Gate lines for a held hit* ("no round produced it"); a pre-round episode holding a hit now writes all its lines before the first round heading - fixed 2026-09-25 — [Minor] F7: the re-dispatch-bound hit shares `hit held`'s shape and the spec does not say why it stays out; license: D12 (the exception covers coverage hits alone); *Out of scope* now gives the reason -- held — [Minor] F8: two grammars carry one fact — `**Realizes:**` on a task and `(realizes D9)` on a constraint; question: one shape for both sites?; options: (a) `**Realizes:** D9` as the constraint entry's first clause too; (b) keep `(realizes D9)` on constraints; (c) `Realizes: D9` without bold in both — recommended: (a), one pattern for the cheapest family to parse +- fixed 2026-09-25 — [Minor] F8: two grammars carry one fact; ruling: 2026-09-25; option (a) — a constraint entry opens with `**Realizes:** D9` as its first clause, one shape for both sites - signal 2026-09-25 — another round pays only after F1 is decided; then one diff-scoped round should suffice, and the Minors are worth a fix wave rather than a round of their own From cd9aefbced2e8fad8d15e34a56802e8e9f599493 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 23:48:29 +0200 Subject: [PATCH 041/143] docs(working-process): plan-coverage spec, architect round 2 --- docs/specs/2026-09-25-plan-coverage-design.md | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 42f9091..bcf09a1 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -3,7 +3,7 @@ ticket: none date: 2026-09-25 status: draft grilled: 2026-09-25 -architect: blocking +architect: concerns decisions: registered branch: feature/plan-coverage base: develop @@ -382,15 +382,28 @@ declares the first ones: A declaration says how its cells split: a cell may name several rows, comma-separated, and `producer → consumer` splits at the arrow before it splits at commas. It says which values are not references: a party -outside the system is written `external: `, and `—` states that -the cell names nothing; neither is resolved. Every other name resolves -to exactly one row of the named table: a name matching no row is a -hit, and so is a name matching several, since a duplicated key makes -every reference to it ambiguous. +outside the system is written `external: ` and is not resolved. +An empty cell, or one naming nothing, is a hit in all three relations +above, since the rule's own definitions give every contract a consumer +and every state record a writer and a reader. A later declaration that +admits an empty side says so itself. Every other name resolves to +exactly one row of the named table: a name matching no row is a hit, +and so is a name matching several, since a duplicated key makes every +reference to it ambiguous. + +The declarations have one home, the rule that defines the tables, under +a fixed heading in the shape of the table above. The auditor's card +names that heading and repeats none of it; the rule loads when the +auditor reads a technical design, which is the only document these +relations bind. A project whose rules declare nothing gives the duty +nothing to cover, and it says so. A table no rule declares a relation for is not checked by this duty, and nothing says it was: the report states what the duty covers, and a -document's ad-hoc tables are outside it. +document's ad-hoc tables are outside it. So is a Contracts flow written +as a numbered sequence rather than a table row: the rule gives the +sequence no grammar a parse could split, so its steps are not +references this duty resolves, and the declaration says so. ## The report @@ -473,7 +486,7 @@ terminal rewrites of it: - hit held — ; question: ; options: - hit fixed — ; ; ruling: - - hit deferred — ; ruling: ; + - hit deferred — ; ruling: ; Defers: - hit withdrawn — ; ruling: ; # A `hit held` line is rewritten in place to one of the three terminal @@ -604,7 +617,7 @@ design spec and its absence is reported, not refused. All under `plugins/working-process/` unless noted. - `agents/propagation-auditor.md` — duties 10 (coverage) and 11 (table - closure); the `decision-coverage:` output line; the "exactly two + closure, naming the rule heading that holds the declarations); the `decision-coverage:` output line; the "exactly two lines" sentence; the coverage exception in *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. @@ -617,8 +630,9 @@ All under `plugins/working-process/` unless noted. pre-round placement; the widened *Unfinished review-loop ledger* command. - `rules/technical-design.md` — the declared relations of *Table - closure*: which columns name rows of Parts, how their cells split, - and the `external:` and `—` values. + closure*, under one fixed heading: which columns name rows of Parts, + how their cells split, the `external:` value, and that a numbered + sequence is outside the relation. - `rules/workflow.md` — the coverage exception to "hits never wait"; the `decision-coverage:` line beside the closing-token paragraph; in step 4, the duty of a brief delegating plan-writing to name the @@ -650,3 +664,13 @@ None at the time of writing. - fixed 2026-09-25 — [Minor] F7: the re-dispatch-bound hit shares `hit held`'s shape and the spec does not say why it stays out; license: D12 (the exception covers coverage hits alone); *Out of scope* now gives the reason - fixed 2026-09-25 — [Minor] F8: two grammars carry one fact; ruling: 2026-09-25; option (a) — a constraint entry opens with `**Realizes:** D9` as its first clause, one shape for both sites - signal 2026-09-25 — another round pays only after F1 is decided; then one diff-scoped round should suffice, and the Minors are worth a fix wave rather than a round of their own + +### 2026-09-25 — architect, fable 5.1, concerns (round 2, diff-scoped) + +- held — [Important] F9: a plan taking over from an earlier one gets a coverage hit for every decision the earlier plan already realized, and only `**Defers:**` can silence it; question: how does a plan account for decisions another plan descending from the same spec realizes?; options: (a) one realizing plan per spec — later work is a new spec with `revises:`, and the "later plan" sentence goes; (b) the counted set excludes identifiers cited by the other plans whose `spec:` names the same spec, found by the auditor itself and listed as "realized by " in the map; (c) the later plan names its predecessor and excludes what that plan cites — recommended: (b), mechanical and needing no annotation +- held — [Minor] F11: "beside the Global Constraints section" is no exact place for a `**Defers:**` line; question: where exactly?; options: (a) the last lines of the Global Constraints section; (b) a block of its own directly after it — recommended: (a), one section a reader already scans for cross-cutting facts +- fixed 2026-09-25 — [Minor] F10: `hit deferred` embeds the whole `**Defers:**` line; license: the `hit withdrawn` shape in *Gate lines for a held hit*, which points by identifier; the shape now ends `Defers: ` +- fixed 2026-09-25 — [Minor] F12: `—` is allowed in relations whose definitions exclude an empty side; license: `rules/technical-design.md`'s definitions of contract and state; an empty cell is now a hit in all three relations, and a later declaration admitting one must say so +- fixed 2026-09-25 — [Minor] F13: the cell split does not cover a numbered sequence; license: *Table closure* ("A table no rule declares a relation for is not checked"); a numbered sequence is now stated outside the relation, since the rule gives it no grammar to split +- fixed 2026-09-25 — [Minor] F14: the spec does not say where the auditor reads the declarations; license: D10 (the relation is "declared by the rule that defines the table"); the rule is now their one home under a fixed heading, the card names the heading and repeats nothing, and *Changes by file* says so +- signal 2026-09-25 — another round pays only after F9 is decided; the Minors are one fix wave, and if one realizing plan per spec is chosen an integrity audit may replace the round From d511922b1e472e250c26c9a91fbbb1b4570e2493 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Fri, 25 Sep 2026 23:51:22 +0200 Subject: [PATCH 042/143] docs(working-process): plan-coverage spec, round 2 fix wave --- docs/specs/2026-09-25-plan-coverage-design.md | 92 ++++++++++++++----- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index bcf09a1..1266100 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -71,9 +71,9 @@ What stands today: identifiers on the specific Global Constraints entries that realize a constraint. Argued in *The plan's annotations*. - **D8** — The lifecycle rule's sentence "changes no plan template" - is revised: working-process adds exactly the D7 and D18 annotations, - and the - tool that writes plans keeps the rest of the template. Argued in *The + is revised: working-process adds exactly the D7, D18 and D19 + annotations, and the tool that writes plans keeps the rest of the + template. Argued in *The plan's annotations*. - **D9** — A new propagation duty derives coverage: every leaf identifier of every registered spec that is neither withdrawn nor @@ -118,6 +118,10 @@ What stands today: in a `**Defers:**` line, one per decision, carrying the developer's ruling; the deferral binds that plan alone, and the spec names no plan. Argued in *Deferral*. +- **D19** — A plan continuing work an `implemented` plan began names + that plan in a `**Follows:**` line and inherits the decisions its + `**Realizes:**` annotations cite, never its deferrals. Argued in + *Sequential plans*. ## The register @@ -266,9 +270,10 @@ This extends the plan template, and the lifecycle rule currently says the plugin does not: "This binds how the blocks are filled and changes no plan template — the template belongs to the tool that writes plans." The sentence is revised to name the exception: working-process adds the -`**Realizes:**` annotation on tasks and constraint entries and the -`**Defers:**` lines of *Deferral*, and the rest of the template stays -with the tool that writes plans. +`**Realizes:**` annotation on tasks and constraint entries, the +`**Defers:**` lines of *Deferral* and the `**Follows:**` lines of +*Sequential plans*, and the rest of the template stays with the tool +that writes plans. The plan's author writes the annotations, whoever that author is, and the requirement lives in the lifecycle rule; the tool that writes plans @@ -289,17 +294,20 @@ deferred by the plan, not by the spec: **Defers:** D6 — ; ruling: -The line sits beside the Global Constraints section, one line per -deferred identifier, qualified as the plan's `**Realizes:**` -annotations are. It carries the developer's ruling, and the session -writes it only on the developer's explicit decision. +The lines close the Global Constraints section, after its last entry, +one line per deferred identifier, qualified as the plan's +`**Realizes:**` annotations are. They state facts about the whole plan, +never about the constraint entry above them. Each carries the +developer's ruling, and the session writes it only on the developer's +explicit decision. The plan is the right home for three reasons. The deferral is a fact about this plan — the decision still stands — so it binds this plan alone by construction: auditing any other plan descending from the same spec, the decision counts, and a later plan meant to take it over and -not doing so gets the coverage hit. The spec names no plan, which keeps -pointers running from the plan to the spec as they always do. And an +not doing so gets the coverage hit (see *Sequential plans*). The spec +names no plan, which keeps pointers running from the plan to the spec +as they always do. And an `implemented` spec, whose body is frozen, never needs editing for a later plan to defer one of its decisions. @@ -310,6 +318,42 @@ cannot both realize and defer it. The plan-adversary does not judge a deferral the developer ruled; it judges a plan whose other tasks quietly depend on the deferred decision anyway. +## Sequential plans + +Work on one spec can run through several plans in sequence: a later +plan picks up where an earlier one stopped. The later plan names each +such predecessor on a line of its own, closing the Global Constraints +section beside its `**Defers:**` lines: + + **Follows:** ../plans/.md + +The path is written relative to the plan. A predecessor must name the +same spec in its own `spec:` and carry `status: implemented`: an +implemented plan's body is frozen, so what it realized is settled, and +a draft cannot lend coverage it may still lose. A `**Follows:**` line +naming a missing file, a plan of another spec, or a plan not yet +implemented is a hit. + +The later plan inherits every identifier its predecessors' +`**Realizes:**` annotations cite, and nothing else: a predecessor's +`**Defers:**` lines are never inherited, so a decision an earlier plan +deferred counts in the later one until that plan realizes or defers it +itself. The coverage map shows an inherited identifier with the plan it +comes from — `D3 → ../plans/.md (Task 4)`. + +The automatic alternative — excluding whatever any other plan of the +same spec cites — was refused: two draft plans could each exempt the +other from one decision, and a plan's result would change when an +unrelated draft appeared. Splitting one spec's decisions across plans +written in parallel needs a contract of its own, and this spec does not +design one. + +Inherited coverage is taken as settled, not re-judged. A predecessor +passed its own review and shipped, and the plan-adversary reads plans, +not the code that implemented them, so it cannot re-verify that work. +What it judges is the later plan: a task that changes or undoes what an +inherited decision required is a finding against the plan under review. + ## The coverage duty A tenth propagation duty runs on a plan. For each spec the plan's @@ -327,7 +371,9 @@ A tenth propagation duty runs on a plan. For each spec the plan's stop for that spec. 3. Collect the counted set: the leaf identifiers that are not withdrawn, less those the audited plan's `**Defers:**` lines name. - The `**Defers:**` lines are checked as *Deferral* says. + The `**Defers:**` lines are checked as *Deferral* says, and the + `**Follows:**` lines as *Sequential plans* says; identifiers a + predecessor realized count as cited, from that predecessor. 4. Collect every identifier the plan's `**Realizes:**` annotations cite, on tasks and constraint entries. A cited withdrawn identifier and a cited group are hits, and so is a cited identifier the @@ -569,8 +615,9 @@ most expensive tier. made, and gains three things: - a row for the coverage duty — *added, changed or withdrawn a register - entry, or added, split, merged or deferred in a plan* → the register - against every `**Realizes:**` annotation and `**Defers:**` line; + entry, or added, split, merged, deferred or followed in a plan* → the + register against every `**Realizes:**` annotation, `**Defers:**` line + and `**Follows:**` predecessor; - a row for the table-closure duty — *written a column naming rows of another table* → every name against that table; - the third anchor case the duty-2 row omits, which the card states: @@ -617,16 +664,17 @@ design spec and its absence is reported, not refused. All under `plugins/working-process/` unless noted. - `agents/propagation-auditor.md` — duties 10 (coverage) and 11 (table - closure, naming the rule heading that holds the declarations); the `decision-coverage:` output line; the "exactly two - lines" sentence; the coverage exception in *What becomes of your - hits*. + closure, naming the rule heading that holds the declarations); the + `decision-coverage:` output line; the "exactly two lines" sentence; + the coverage exception in *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. - `agents/integrity-auditor.md` — one sentence naming the register as a declared rule for lens 1. - `rules/spec-plan-lifecycle.md` — the `decisions:` field in the frontmatter contract; the register's grammar, states and declared - rule; the revised template sentence; the `**Realizes:**` and - `**Defers:**` annotations; the four gate-line shapes and the + rule; the revised template sentence; the `**Realizes:**`, + `**Defers:**` and `**Follows:**` annotations; the four gate-line + shapes and the pre-round placement; the widened *Unfinished review-loop ledger* command. - `rules/technical-design.md` — the declared relations of *Table @@ -667,8 +715,8 @@ None at the time of writing. ### 2026-09-25 — architect, fable 5.1, concerns (round 2, diff-scoped) -- held — [Important] F9: a plan taking over from an earlier one gets a coverage hit for every decision the earlier plan already realized, and only `**Defers:**` can silence it; question: how does a plan account for decisions another plan descending from the same spec realizes?; options: (a) one realizing plan per spec — later work is a new spec with `revises:`, and the "later plan" sentence goes; (b) the counted set excludes identifiers cited by the other plans whose `spec:` names the same spec, found by the auditor itself and listed as "realized by " in the map; (c) the later plan names its predecessor and excludes what that plan cites — recommended: (b), mechanical and needing no annotation -- held — [Minor] F11: "beside the Global Constraints section" is no exact place for a `**Defers:**` line; question: where exactly?; options: (a) the last lines of the Global Constraints section; (b) a block of its own directly after it — recommended: (a), one section a reader already scans for cross-cutting facts +- fixed 2026-09-25 — [Important] F9: a plan taking over from an earlier one gets a coverage hit for every decision the earlier plan already realized; ruling: 2026-09-25; option (c), narrowed to `implemented` predecessors — new D19 and section *Sequential plans*: a `**Follows:**` line names each predecessor of the same spec, the later plan inherits its `**Realizes:**` identifiers and never its deferrals, the map names the source plan, and step 3 counts inherited identifiers as cited; option (b) refused in the section, since two drafts could exempt each other; deviation: *Sequential plans* — the plan-adversary judges the later plan against inherited decisions, not the predecessor's implementation, which it cannot read +- fixed 2026-09-25 — [Minor] F11: no exact place for a `**Defers:**` line; ruling: 2026-09-25; option (a) — the lines close the Global Constraints section and state facts about the whole plan, not the entry above them; `**Follows:**` lines sit beside them - fixed 2026-09-25 — [Minor] F10: `hit deferred` embeds the whole `**Defers:**` line; license: the `hit withdrawn` shape in *Gate lines for a held hit*, which points by identifier; the shape now ends `Defers: ` - fixed 2026-09-25 — [Minor] F12: `—` is allowed in relations whose definitions exclude an empty side; license: `rules/technical-design.md`'s definitions of contract and state; an empty cell is now a hit in all three relations, and a later declaration admitting one must say so - fixed 2026-09-25 — [Minor] F13: the cell split does not cover a numbered sequence; license: *Table closure* ("A table no rule declares a relation for is not checked"); a numbered sequence is now stated outside the relation, since the rule gives it no grammar to split From f101d45292d04330ad84bc5e46323cc4b385f7ac Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 00:01:11 +0200 Subject: [PATCH 043/143] docs(working-process): plan-coverage spec, architect round 3 --- docs/specs/2026-09-25-plan-coverage-design.md | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 1266100..06b75dd 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -327,18 +327,25 @@ section beside its `**Defers:**` lines: **Follows:** ../plans/.md -The path is written relative to the plan. A predecessor must name the -same spec in its own `spec:` and carry `status: implemented`: an -implemented plan's body is frozen, so what it realized is settled, and -a draft cannot lend coverage it may still lose. A `**Follows:**` line -naming a missing file, a plan of another spec, or a plan not yet -implemented is a hit. +The path is written relative to the plan. A predecessor must share at +least one spec with the later plan's `spec:` and carry +`status: implemented`: an implemented plan's body is frozen, so what it +cited is settled and the owner of a decision cannot move, while a draft +could lose a citation it lent. Whether that realization still stands +in the code is not coverage's question; the lifecycle rule's diff +against the named surfaces answers it. A predecessor lends identifiers +only for the specs both plans name: in the pass for a spec it does not +name, its `**Follows:**` line is out of scope rather than a hit. A +`**Follows:**` line naming a missing file, a plan sharing no spec with +the later plan, or a plan not yet implemented is a hit. The later plan inherits every identifier its predecessors' `**Realizes:**` annotations cite, and nothing else: a predecessor's `**Defers:**` lines are never inherited, so a decision an earlier plan deferred counts in the later one until that plan realizes or defers it -itself. The coverage map shows an inherited identifier with the plan it +itself. Nor are its `**Follows:**` lines: inheritance is not +transitive, so a plan drawing on a chain names every implemented +predecessor whose citations it relies on. The coverage map shows an inherited identifier with the plan it comes from — `D3 → ../plans/.md (Task 4)`. The automatic alternative — excluding whatever any other plan of the @@ -692,7 +699,10 @@ All under `plugins/working-process/` unless noted. - `docs/domain/glossary.md` (repo) — applied at the grilling of 2026-09-25: new **Decision register** and **Decision coverage** entries; **Ruling** widened to held hits and register state changes; - **Hit** gained the held coverage hit. + **Hit** gained the held coverage hit. Amended by the review rounds: + **Ruling** also covers a plan's deferral, and **Decision coverage** + counts deferrals by the audited plan and citations inherited from an + implemented predecessor. - `README.md`, `CHANGELOG.md` — the new duties and the convention. ## Open questions @@ -722,3 +732,12 @@ None at the time of writing. - fixed 2026-09-25 — [Minor] F13: the cell split does not cover a numbered sequence; license: *Table closure* ("A table no rule declares a relation for is not checked"); a numbered sequence is now stated outside the relation, since the rule gives it no grammar to split - fixed 2026-09-25 — [Minor] F14: the spec does not say where the auditor reads the declarations; license: D10 (the relation is "declared by the rule that defines the table"); the rule is now their one home under a fixed heading, the card names the heading and repeats nothing, and *Changes by file* says so - signal 2026-09-25 — another round pays only after F9 is decided; the Minors are one fix wave, and if one realizing plan per spec is chosen an integrity audit may replace the round + +### 2026-09-26 — architect, fable 5.1, concerns (round 3, diff-scoped) + +- held — [Minor] F18: the `**Defers:**` and `**Follows:**` lines closing Global Constraints fall under the template's "Every task's requirements implicitly include this section"; counter: re-raises the F11 line carrying `ruling: 2026-09-25` with new evidence — round 2 cited only the section heading, not that sentence; question: add a clause exempting both kinds of line from that implicit inclusion, keeping the placement?; options: (a) yes — one clause in the revised template sentence (D8, *The plan's annotations*); (b) move both kinds of line into a block of their own after Global Constraints instead — recommended: (a), placement stays as ruled +- fixed 2026-09-26 — [Minor] F15: inheritance does not say whether it is transitive; license: *Sequential plans* ("and nothing else"); a predecessor's `**Follows:**` lines are now stated as not inherited, so a plan names every predecessor it relies on +- fixed 2026-09-26 — [Minor] F16: "what it realized is settled" claims more than a frozen body gives; license: `rules/spec-plan-lifecycle.md` (an implemented document "is the archive, not the specification of what stands today"); the sentence now rests on what the predecessor cited, and whether the realization still stands is sent to the lifecycle rule's diff +- fixed 2026-09-26 — [Minor] F17: the glossary's **Decision coverage** ignores inherited citations; license: D19; the entry now covers citations in an implemented predecessor, and *Changes by file* records the review-round amendments to the glossary +- fixed 2026-09-26 — [Minor] F19: a multi-spec plan gets a false "plan of another spec" hit in the pass for a spec its predecessor does not name; license: step 3 of *The coverage duty*, which runs per spec; a predecessor now shares at least one spec, lends identifiers only for shared specs, and is out of scope in the other passes +- signal 2026-09-26 — another round does not pay; the five Minors are one fix wave, and an integrity audit at the consumption gate — especially the two integrity-class questions the round named — is worth more than a fourth round From 417e2bd73a62988ec87071295d3bd6ff7559f748 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 00:04:31 +0200 Subject: [PATCH 044/143] docs(working-process): plan-coverage spec, round 3 fix wave --- docs/specs/2026-09-25-plan-coverage-design.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 06b75dd..835d635 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -294,10 +294,12 @@ deferred by the plan, not by the spec: **Defers:** D6 — ; ruling: -The lines close the Global Constraints section, after its last entry, -one line per deferred identifier, qualified as the plan's -`**Realizes:**` annotations are. They state facts about the whole plan, -never about the constraint entry above them. Each carries the +The lines form a block of their own directly after the Global +Constraints section, outside it, one line per deferred identifier, +qualified as the plan's `**Realizes:**` annotations are. They stay out +of that section because the plan template makes every task's +requirements include it, and these lines state facts about the whole +plan rather than requirements of any task. Each carries the developer's ruling, and the session writes it only on the developer's explicit decision. @@ -322,8 +324,8 @@ depend on the deferred decision anyway. Work on one spec can run through several plans in sequence: a later plan picks up where an earlier one stopped. The later plan names each -such predecessor on a line of its own, closing the Global Constraints -section beside its `**Defers:**` lines: +such predecessor on a line of its own, in the same block as its +`**Defers:**` lines, after the Global Constraints section: **Follows:** ../plans/.md @@ -345,8 +347,9 @@ The later plan inherits every identifier its predecessors' deferred counts in the later one until that plan realizes or defers it itself. Nor are its `**Follows:**` lines: inheritance is not transitive, so a plan drawing on a chain names every implemented -predecessor whose citations it relies on. The coverage map shows an inherited identifier with the plan it -comes from — `D3 → ../plans/.md (Task 4)`. +predecessor whose citations it relies on. The coverage map shows an +inherited identifier with the plan it comes from — +`D3 → ../plans/.md (Task 4)`. The automatic alternative — excluding whatever any other plan of the same spec cites — was refused: two draft plans could each exempt the @@ -735,7 +738,7 @@ None at the time of writing. ### 2026-09-26 — architect, fable 5.1, concerns (round 3, diff-scoped) -- held — [Minor] F18: the `**Defers:**` and `**Follows:**` lines closing Global Constraints fall under the template's "Every task's requirements implicitly include this section"; counter: re-raises the F11 line carrying `ruling: 2026-09-25` with new evidence — round 2 cited only the section heading, not that sentence; question: add a clause exempting both kinds of line from that implicit inclusion, keeping the placement?; options: (a) yes — one clause in the revised template sentence (D8, *The plan's annotations*); (b) move both kinds of line into a block of their own after Global Constraints instead — recommended: (a), placement stays as ruled +- fixed 2026-09-26 — [Minor] F18: the `**Defers:**` and `**Follows:**` lines closing Global Constraints fall under the template's "Every task's requirements implicitly include this section"; ruling: 2026-09-26; option (b), which replaces the placement ruled under F11 in round 2, on new evidence — both kinds of line now form a block of their own directly after the Global Constraints section, outside it (*Deferral*, *Sequential plans*) - fixed 2026-09-26 — [Minor] F15: inheritance does not say whether it is transitive; license: *Sequential plans* ("and nothing else"); a predecessor's `**Follows:**` lines are now stated as not inherited, so a plan names every predecessor it relies on - fixed 2026-09-26 — [Minor] F16: "what it realized is settled" claims more than a frozen body gives; license: `rules/spec-plan-lifecycle.md` (an implemented document "is the archive, not the specification of what stands today"); the sentence now rests on what the predecessor cited, and whether the realization still stands is sent to the lifecycle rule's diff - fixed 2026-09-26 — [Minor] F17: the glossary's **Decision coverage** ignores inherited citations; license: D19; the entry now covers citations in an implemented predecessor, and *Changes by file* records the review-round amendments to the glossary From 734558b08845d3587dc2c0e3da1c26e8ca0954b1 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 00:10:06 +0200 Subject: [PATCH 045/143] docs(working-process): close plan-coverage spec review loop --- docs/specs/2026-09-25-plan-coverage-design.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 835d635..229ddec 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -3,7 +3,7 @@ ticket: none date: 2026-09-25 status: draft grilled: 2026-09-25 -architect: concerns +architect: concerns (resolved 2026-09-26) decisions: registered branch: feature/plan-coverage base: develop @@ -744,3 +744,12 @@ None at the time of writing. - fixed 2026-09-26 — [Minor] F17: the glossary's **Decision coverage** ignores inherited citations; license: D19; the entry now covers citations in an implemented predecessor, and *Changes by file* records the review-round amendments to the glossary - fixed 2026-09-26 — [Minor] F19: a multi-spec plan gets a false "plan of another spec" hit in the pass for a spec its predecessor does not name; license: step 3 of *The coverage duty*, which runs per spec; a predecessor now shares at least one spec, lends identifiers only for shared specs, and is out of scope in the other passes - signal 2026-09-26 — another round does not pay; the five Minors are one fix wave, and an integrity audit at the consumption gate — especially the two integrity-class questions the round named — is worth more than a fourth round + +### Loop closed — 2026-09-26 + +The developer closed the loop without a fourth round on 2026-09-26, +after round 3's Minors were fixed, a propagation gate returned clean +and no ledger line stayed `open` or `held`. Round 3's own stop signal +judged another round not worth its cost; the integrity audit at the +consumption gate takes the two integrity-class questions that round +named. From b7116f3c824edae55e42ccd8891cde02d87f6761 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 06:51:34 +0200 Subject: [PATCH 046/143] docs(working-process): plan-coverage spec, integrity audit dispositions --- docs/specs/2026-09-25-plan-coverage-design.md | 306 ++++++++++++------ 1 file changed, 212 insertions(+), 94 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 229ddec..558d8a1 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -15,8 +15,8 @@ A plan can drop a decision its design spec made, and today no reader notices. This spec gives the design spec an enumerable register of the decisions that need realization, puts each decision's identifier on the plan tasks that realize it, and has the propagation auditor derive the -coverage from those two lists. The judgment of whether a cited task -really realizes its decision stays with the plan-adversary. +decision coverage from those two lists. The judgment of whether a cited +task really realizes its decision stays with the plan-adversary. ## Problem @@ -73,9 +73,8 @@ What stands today: - **D8** — The lifecycle rule's sentence "changes no plan template" is revised: working-process adds exactly the D7, D18 and D19 annotations, and the tool that writes plans keeps the rest of the - template. Argued in *The - plan's annotations*. -- **D9** — A new propagation duty derives coverage: every leaf + template. Argued in *The plan's annotations*. +- **D9** — A new propagation duty derives decision coverage: every leaf identifier of every registered spec that is neither withdrawn nor deferred by the audited plan needs a realizing task or constraint, and the register itself must be well formed. Argued in *The coverage @@ -87,8 +86,8 @@ What stands today: - **D11** — The auditor's report gains one `decision-coverage:` block per spec the audited plan names — a summary line and, where the register could be counted, the map from identifier to citing sites — - before the closing token, on clean runs too; `CLEAN` - means no hit in the checks that ran. Argued in *The report*. + before the closing token, on clean runs too; `CLEAN` means no hit in + the checks that ran. Argued in *The report*. - **D12** — A coverage hit stays a hit, but its fix is triaged by license, and a fix needing a decision the spec does not make waits for the developer. The exception to "hits never wait" covers coverage @@ -101,7 +100,8 @@ What stands today: - **D14** — The plan-adversary gains a dimension judging each decision's realization: do the tasks and constraints citing it realize it in full together, and does a cited constraint actually - bind. Argued in *Readers*. + bind. A decision realized only in part is an Important finding. + Argued in *Readers*. - **D15** — The integrity auditor checks the register's completeness through its existing first lens, prompted by one sentence in its card. Argued in *Readers*. @@ -122,6 +122,27 @@ What stands today: that plan in a `**Follows:**` line and inherits the decisions its `**Realizes:**` annotations cite, never its deferrals. Argued in *Sequential plans*. +- **D20** — A brief that delegates plan-writing to a separate context + names the lifecycle rule and requires reading it before the plan is + written. Argued in *The plan's annotations*. +- **D21** — The technical-design rule carries the table-closure + declarations, under one fixed heading, as their only home. Argued in + *Table closure*. +- **D22** — On a registered design spec audited on its own, the coverage + duty checks the register alone and the report says whether it is well + formed. Argued in *The coverage duty* and *The report*. +- **D23** — A pre-round gate episode that holds a hit writes all its + lines before the first round heading, where they stay. Argued in + *Gate lines for a held hit*. +- **D24** — Withdrawing a decision of an `implemented` spec is a + revision through a newer spec's `revises:`, never an edit of the + frozen body. Argued in *Entry states*. +- **D25** — A plan whose `spec:` names two or more specs, registered or + legacy, qualifies every identifier with its spec's path. Argued in + *The plan's annotations*. +- **D26** — Only an uncovered identifier and a task lacking its + `**Realizes:**` line may be held; every other hit of the coverage duty + takes the ordinary path. Argued in *Disposing of a coverage hit*. ## The register @@ -145,12 +166,21 @@ normalising whitespace, and parses the result, so a sentence wrapped across lines keeps its state token, which stands at the paragraph's end. Prose beneath, after the blank line, is free. +A state token is recognized by its reserved opening word alone: the +paragraph's final segment, after the full stop that ends the decision's +statement, beginning with `withdrawn`. A segment so opening that does +not match the token's grammar is a hit, never prose. Any other text is +prose, so a statement whose own words happen to end in "withdrawn" +before its full stop is no token. + The identifier is the literal token `**D**` or, for a child, `**D.**`, at the start of a list item under `## Decisions` — a child's item nested under its parent's, where it opens the child's own identity paragraph. A Markdown numbered list is refused on purpose: its numbers are positions, and a positional reference breaks when an item -is inserted above it. +is inserted above it. A child nested under a parent other than its +own, a child whose parent does not exist, and a register written as a +numbered list are each malformed (see *The coverage duty*). An entry states the decision briefly and, where the argument is long, names the section that makes it. The grammar enforces the paragraph's @@ -174,11 +204,11 @@ count reads. ## What enters the register Every decision that needs realization enters, cross-cutting constraints -included. A ruling that something stays as it is — "leave the gate -order unchanged" — needs realization too, since an implementer can break -it; its realization is a Global Constraints entry. There is therefore no -exempt kind of entry, and no way to reach full coverage by declaring -decisions exempt. +included. A ruling that something stays as it is — "leave the gate order +unchanged" — needs realization too, since an implementer can break it; +its realization is a Global Constraints entry. There is therefore no +exempt kind of entry, and no way to reach full decision coverage by +declaring decisions exempt. A rejected alternative never enters: nobody realizes it, and it already has homes — a spec's out-of-scope section, a technical design's *Cuts @@ -208,8 +238,8 @@ decides, so the session writes the token only on the developer's explicit decision, and the edit lands in the spec's own ledger. That edit leaves the spec's `integrity:` stamp stale, as any body edit does, and correctly so. Withdrawing a decision of a spec already -`implemented` is no edit of its frozen body but a revision: a newer -design spec carries `revises:` and the register that stands. +`implemented` is no edit of its frozen body but a revision (D24): a +newer design spec carries `revises:` and the register that stands. The token's `ruling:` is a Ruling in the glossary's sense, dated with the developer's decision rather than with the edit that recorded it. @@ -239,7 +269,8 @@ gets a reader (see *Readers*). ## The plan's annotations A plan whose `spec:` names at least one registered spec carries, on -every task, beside its `**Interfaces:**` block: +every task, a line beside its `**Files:**` block — after the +`**Interfaces:**` block where the task has one: **Realizes:** D3, D5 **Realizes:** none @@ -252,14 +283,21 @@ A cross-cutting constraint is realized by the Global Constraints entry that states it, and that entry opens with the same annotation, as its first clause — `**Realizes:** D9`. One shape serves both sites: the values, the qualification of identifiers and the wrapping rules are -shared, and only the place differs. Citing the section as a whole -realizes nothing. - -A plan descending from one spec uses bare identifiers. A plan descending -from several qualifies each identifier with that spec's path exactly as -the plan's `spec:` writes it — `../specs/.md#D3` — since two specs -may share a basename, and a bare identifier in a multi-spec plan is a -hit. +shared, and only the place differs. Only an entry that realizes a +decision carries the annotation; an entry without one is no hit, and +`none` is never written there, since a constraint that realizes nothing +needs no statement of it. Citing the section as a whole realizes +nothing. + +A plan whose `spec:` names one spec uses bare identifiers. A plan whose +`spec:` names two or more — registered or legacy alike — qualifies each +identifier with that spec's path exactly as the plan's `spec:` writes it +— `../specs/.md#D3` — since two specs may share a basename, and a +bare identifier there is a hit (D25). The count of entries decides, +never the count of registered ones: otherwise migrating a second spec +out of legacy would change the meaning of annotations already written. +The same qualification applies wherever the plan or its audit names an +identifier — a `**Defers:**` line, a gate line, the report. The annotation sits on the task rather than in a table at the top of the plan, for two reasons. Ownership is per task, which is the shape of @@ -281,7 +319,8 @@ is not changed. A session writing the plan itself meets the rule when it reads the design spec, since the rule loads on `docs/specs/**`. A plan delegated to a separate context cannot count on that load, so the delegating brief names the lifecycle rule and requires reading it -before the plan is written. A plan that lacks the annotations anyway is +before the plan is written (D20); the workflow rule's step 4 carries +that duty. A plan that lacks the annotations anyway is caught at its first gate, and the missing lines take the triage of *Disposing of a coverage hit*: an identifier is added by fix (a) only where the task's text actually realizes the decision, never @@ -309,14 +348,15 @@ alone by construction: auditing any other plan descending from the same spec, the decision counts, and a later plan meant to take it over and not doing so gets the coverage hit (see *Sequential plans*). The spec names no plan, which keeps pointers running from the plan to the spec -as they always do. And an -`implemented` spec, whose body is frozen, never needs editing for a -later plan to defer one of its decisions. +as they always do. And an `implemented` spec, whose body is frozen, +never needs editing for a later plan to defer one of its decisions. A `**Defers:**` line naming an identifier the register does not define, -a withdrawn one or a group is a hit. So is one identifier both deferred -and cited by a `**Realizes:**` annotation in the same plan: the plan -cannot both realize and defer it. The plan-adversary does not judge a +a withdrawn one or a group is a hit. So is an identifier the plan both +defers and counts as cited — by its own `**Realizes:**` annotation or by +one it inherits from a predecessor (see *Sequential plans*): the plan +cannot both realize a decision and defer it, and deferring one already +realized is no deferral at all. The plan-adversary does not judge a deferral the developer ruled; it judges a plan whose other tasks quietly depend on the deferred decision anyway. @@ -330,11 +370,11 @@ such predecessor on a line of its own, in the same block as its **Follows:** ../plans/.md The path is written relative to the plan. A predecessor must share at -least one spec with the later plan's `spec:` and carry -`status: implemented`: an implemented plan's body is frozen, so what it -cited is settled and the owner of a decision cannot move, while a draft -could lose a citation it lent. Whether that realization still stands -in the code is not coverage's question; the lifecycle rule's diff +least one spec with the later plan's `spec:` and carry `status: +implemented`: an implemented plan's body is frozen, so what it cited is +settled and the owner of a decision cannot move, while a draft could +lose a citation it lent. Whether that realization still stands in the +code is not a question decision coverage asks; the lifecycle rule's diff against the named surfaces answers it. A predecessor lends identifiers only for the specs both plans name: in the pass for a spec it does not name, its `**Follows:**` line is out of scope rather than a hit. A @@ -349,7 +389,9 @@ itself. Nor are its `**Follows:**` lines: inheritance is not transitive, so a plan drawing on a chain names every implemented predecessor whose citations it relies on. The coverage map shows an inherited identifier with the plan it comes from — -`D3 → ../plans/.md (Task 4)`. +`D3 → ../plans/.md (Task 4)`. An identifier both realized +locally and inherited counts once, as local, and its map line names +both sites. The automatic alternative — excluding whatever any other plan of the same spec cites — was refused: two draft plans could each exempt the @@ -358,11 +400,12 @@ unrelated draft appeared. Splitting one spec's decisions across plans written in parallel needs a contract of its own, and this spec does not design one. -Inherited coverage is taken as settled, not re-judged. A predecessor -passed its own review and shipped, and the plan-adversary reads plans, -not the code that implemented them, so it cannot re-verify that work. -What it judges is the later plan: a task that changes or undoes what an -inherited decision required is a finding against the plan under review. +Inherited decision coverage is taken as settled, not re-judged. A +predecessor passed its own review and shipped, and the plan-adversary +reads plans, not the code that implemented them, so it cannot re-verify +that work. What it judges is the later plan: a task that changes or +undoes what an inherited decision required is a finding against the plan +under review. ## The coverage duty @@ -374,38 +417,43 @@ A tenth propagation duty runs on a plan. For each spec the plan's other than `registered`, report a hit and `not counted`, and stop. 2. Check the register is well formed. Each of these is a hit: the field set with no `## Decisions` section; an identity paragraph that does - not parse; a duplicate identifier; an unknown state token; a group - carrying a state token; a `replaced by` naming a - missing identifier, its own, or closing a cycle. Where any of them - fires, the counted set cannot be trusted: report `not counted` and - stop for that spec. + not parse, which includes a child nested under a parent other than + its own, a child whose parent does not exist, and a register written + as a numbered list; a duplicate identifier; a segment opening with + `withdrawn` that does not match the token's grammar; a group carrying + a state token; a `replaced by` naming a missing identifier, its own, + or a group, or closing a cycle. Where any of them fires, the counted + set cannot be trusted: report `not counted` and stop for that spec. 3. Collect the counted set: the leaf identifiers that are not withdrawn, less those the audited plan's `**Defers:**` lines name. - The `**Defers:**` lines are checked as *Deferral* says, and the - `**Follows:**` lines as *Sequential plans* says; identifiers a - predecessor realized count as cited, from that predecessor. -4. Collect every identifier the plan's `**Realizes:**` annotations - cite, on tasks and constraint entries. A cited withdrawn identifier - and a cited group are hits, and so is a cited identifier the - register does not define. That last is an error in the plan, never - a gap in the spec: identifiers are minted only in the register, so a - plan citing one the register lacks has mistyped or invented it. - Duty 5 does not take it, since duty 5 reads an undefined name as a - gap in its source. -5. Report every counted identifier that no task and no constraint - cites: one hit per identifier. - -The coverage list is derived, never tallied. The auditor writes the map -from identifier to citing sites, and the fraction is that map's summary; -a bare count would pass one omission offset by one duplicate. A task -carrying no `**Realizes:**` line in a plan the convention binds is a -hit. An identifier cited by several tasks is legal — one decision, many -tasks — and a task split or merged in a fix wave passes as long as the -union of its identifiers survives. +4. Collect the cited set: every identifier the plan's `**Realizes:**` + annotations cite, on tasks and constraint entries, and every + identifier inherited through the plan's `**Follows:**` lines, each + with the plan it comes from. A cited withdrawn identifier and a + cited group are hits, and so is a cited identifier the register does + not define. That last is an error in the plan, never a gap in the + spec: identifiers are minted only in the register, so a plan citing + one the register lacks has mistyped or invented it. Duty 5 does not + take it, since duty 5 reads an undefined name as a gap in its + source. +5. Check the plan's `**Defers:**` lines as *Deferral* says and its + `**Follows:**` lines as *Sequential plans* says, against the cited + set of step 4. +6. Report every counted identifier the cited set lacks: one hit per + identifier. + +The decision coverage map is derived, never tallied. The auditor writes +the map from identifier to citing sites, and the fraction is that map's +summary; a bare count would pass one omission offset by one duplicate. A +task carrying no `**Realizes:**` line in a plan the convention binds is +a hit. An identifier cited by several tasks is legal — one decision, +many tasks — and a task split or merged in a fix wave passes as long as +the union of its identifiers survives. On a registered design spec audited on its own — the gate before its -architect round — the duty runs steps 1 and 2 alone, so a malformed -register is found before any plan depends on it. +architect round — the duty runs steps 1 and 2 alone (D22), so a +malformed register is found before any plan depends on it; *The +report* gives the line it writes. The duty proves that every *declared* decision has an owner. Whether the register faithfully lists the spec's decisions is judgment, and @@ -447,9 +495,9 @@ exactly one row of the named table: a name matching no row is a hit, and so is a name matching several, since a duplicated key makes every reference to it ambiguous. -The declarations have one home, the rule that defines the tables, under -a fixed heading in the shape of the table above. The auditor's card -names that heading and repeats none of it; the rule loads when the +The declarations have one home (D21), the rule that defines the tables, +under a fixed heading in the shape of the table above. The auditor's +card names that heading and repeats none of it; the rule loads when the auditor reads a technical design, which is the only document these relations bind. A project whose rules declare nothing gives the duty nothing to cover, and it says so. @@ -464,12 +512,13 @@ references this duty resolves, and the declaration says so. ## The report The auditor's report today is either located hits or exactly two lines, -`model:` and `CLEAN`, with nothing after the token. Coverage adds a -block per spec the audited plan names, written after the hits and -before the token, and present whether or not any hit fired: +`model:` and `CLEAN`, with nothing after the token. The coverage duty +adds a block per spec the audited plan names, written after the hits +and before the token, and present whether or not any hit fired: - decision-coverage: 7/8 covered; deferred [D6]; uncovered [D4.2] + decision-coverage: 7/8 covered; inherited [D2]; deferred [D6]; uncovered [D4.2] D1 → Task 2 + D2 → ../plans/.md (Task 3) D3 → Task 4, Task 6 D9 → Global Constraints: No code … @@ -481,10 +530,25 @@ follows it, one indented line per counted identifier, naming every task and constraint that cites it; an uncovered identifier maps to nothing and is also a hit above. The map is what the fraction summarises, so a reader can check the one against the other. The fraction's denominator -is the counted set of step 3; identifiers the audited plan defers are -listed apart and never counted as covered. `not counted` -follows a malformed register, whose hits stand above it, and carries no -map, since its denominator cannot be derived. +is the counted set of step 3, and its numerator every counted +identifier in the cited set of step 4. `inherited [..]` lists the +covered identifiers whose only citation comes from a predecessor — an +identifier realized locally as well counts as local and is not listed +there — so the fraction can be checked without the map. Identifiers the +audited plan defers are listed apart and never counted as covered. +Every identifier in the block is qualified as the plan's annotations +are. `not counted` follows a malformed register, whose hits stand above +it, and carries no map, since its denominator cannot be derived. + +On a registered design spec audited on its own, the block is one line +(D22) and no map follows, since no plan cites anything yet: + + decision-coverage: register well formed + decision-coverage: not counted — malformed register + decision-coverage: not checked — no decision register + +`register well formed` is a third outcome the report contract gains, +beside `not counted` and `not checked`, and the card states all three. A clean audit of a plan therefore runs to the self-report, one block per spec, and `CLEAN`. `CLEAN` means no hit in the checks that ran, @@ -504,6 +568,16 @@ dispatcher to read one as the other. ## Disposing of a coverage hit +Two hits of the coverage duty are coverage hits in this sense (D26): an +identifier the cited set lacks, and a task lacking its `**Realizes:**` +line in a plan the convention binds. Only these may be held. Every +other hit of the duty — a malformed register, a bad `**Defers:**` or +`**Follows:**` line, a cited identifier that is withdrawn, a group or +undefined — takes the ordinary path: fixed where its derivation settles +the fix, and put to the developer through the loop's usual questions +where it does not, as when breaking a `replaced by` cycle means +choosing which entry stands. + The detection is mechanical and runs where the measured failure slipped through: at the gate, on the cheapest family, before the plan-adversary. The fix is not always mechanical. "Handle retries" does not settle the @@ -531,6 +605,19 @@ the plan as a `**Defers:**` line where the developer defers it. The plan is then fixed and the gate re-run, and the plan-adversary dispatches only once the gate passes. +A held hit ends its gate episode: the gate has not passed, and the +dispatch it guards waits. The re-run after the developer's answer is a +fresh episode, with its own re-dispatch bound. + +A register edit the answer causes is recorded in the spec's own ledger. +Where the spec's loop is closed, it goes under the cross-document fix +heading the lifecycle rule already defines — +`### — fix from ` — which serves a gate-licensed fix +as it serves a review-licensed one, and the plan's `hit fixed` line +names that heading and the line under it. A spec already `implemented` +takes no such edit: the answer becomes a revision through a newer +spec's `revises:` (D24), and the held hit waits for it. + The workflow rule's "hits never wait for the developer" gains one named exception, for coverage hits alone. Every other hit keeps a fix licensed by its own derivation. @@ -551,8 +638,14 @@ gate re-runs over the changed spec and plan, never on the developer's answer alone. `hit fixed … ruling:` records that the developer settled the missing decision and the plan now realizes it. `hit deferred` records an approved deferral, now a `**Defers:**` line in the plan, and -`hit withdrawn` a decision the -developer dropped, its register entry now a tombstone. Neither is +`hit withdrawn` a decision the developer dropped, its register entry +now a tombstone. + +A `hit held` line lives in the ledger of the document whose gate raised +it — the audited plan. Its leading date is the gate's, as on every gate +line, where `open` and `held` disposition lines carry none; the +terminal rewrite replaces it with the date the line reached its +terminal state. Neither is `hit dismissed`, because the gap was real when the hit fired. The ordinary `hit fixed` shape, without `ruling:`, stays as it is for every hit whose fix its derivation licensed. @@ -570,15 +663,20 @@ episode that holds at least one hit therefore writes every one of its lines — the `hit held` line and its siblings alike — in the `## Review rounds` section before the first round heading, and they stay there, rewrites included: no round produced them, and one episode -keeps one home. A pre-round episode that holds nothing keeps the +keeps one home (D23). A pre-round episode that holds nothing keeps the existing rule. `hit held` joins the Unfinished-work list by widening the *Unfinished review-loop ledger* entry's command to match it, within that entry's existing scope — inside a `## Review rounds` section, where the -pre-round line also sits. Its owner is the developer, as for any `held` -line. The three terminal shapes stop matching, which is their closed -state. +pre-round line also sits. Because a gate line's date stands between its +token and its dash, the widened command gains an alternative rather than +a third token inside the existing group: + + rg -n --no-ignore --crlf '^- (open|held) —|^- hit held ' docs/ + +Its owner is the developer, as for any `held` line. The three terminal +shapes stop matching, which is their closed state. ## Readers @@ -675,8 +773,10 @@ All under `plugins/working-process/` unless noted. - `agents/propagation-auditor.md` — duties 10 (coverage) and 11 (table closure, naming the rule heading that holds the declarations); the - `decision-coverage:` output line; the "exactly two lines" sentence; - the coverage exception in *What becomes of your hits*. + `decision-coverage:` block, its `inherited [..]` slot and the three + outcomes on a spec audited alone (`register well formed`, `not + counted`, `not checked`); the "exactly two lines" sentence; the + coverage exception in *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. - `agents/integrity-auditor.md` — one sentence naming the register as a declared rule for lens 1. @@ -684,9 +784,9 @@ All under `plugins/working-process/` unless noted. frontmatter contract; the register's grammar, states and declared rule; the revised template sentence; the `**Realizes:**`, `**Defers:**` and `**Follows:**` annotations; the four gate-line - shapes and the - pre-round placement; the widened *Unfinished review-loop ledger* - command. + shapes and the pre-round placement; the widened *Unfinished + review-loop ledger* command; the cross-document fix heading serving a + gate-licensed fix; withdrawal from an implemented spec by revision. - `rules/technical-design.md` — the declared relations of *Table closure*, under one fixed heading: which columns name rows of Parts, how their cells split, the `external:` value, and that a numbered @@ -710,7 +810,11 @@ All under `plugins/working-process/` unless noted. ## Open questions -None at the time of writing. +None. The two questions the closing architect round left for the +integrity audit were settled at its disposition on 2026-09-26: an +identifier both inherited and deferred by one plan is a hit +(*Deferral*), and the `decision-coverage:` summary lists inherited +identifiers in their own `inherited [..]` slot (*The report*). ## Review rounds @@ -753,3 +857,17 @@ and no ledger line stayed `open` or `held`. Round 3's own stop signal judged another round not worth its cost; the integrity audit at the consumption gate takes the two integrity-class questions that round named. + +### 2026-09-26 — integrity audit, fable, at the consumption gate + +Coverage tell: 755 lines read, highest line cited 755 — a whole-document +read. Nine defects and fourteen implementer questions, the closing +round's two questions among them; the defects' quotes were checked +against the spec before disposition. + +- fixed 2026-09-26 — six decisions the text makes had no register entry (delegating brief, table-closure declarations, the duty on a spec alone, pre-round placement, withdrawal from an implemented spec, multi-spec qualification); license: the register's declared rule in *Who writes the register*; D20–D25 added +- fixed 2026-09-26 — a step-3 check needed the citations step 4 collects; license: *Deferral* (the deferred-and-cited hit reads the citation set); the duty's steps reordered, the `**Defers:**`/`**Follows:**` checks now step 5 against step 4's cited set +- fixed 2026-09-26 — *Open questions* said "None" while the ledger handed two questions on; license: the document itself; the section now records how the audit's disposition settled both +- fixed 2026-09-26 — bare "coverage" named the map in four places; license: glossary **Decision coverage** `_Avoid_`; reworded to "decision coverage" +- fixed 2026-09-26 — implementer questions 2, 6, 7, 9, 11, 12, 13, 14; license: the existing gate-line grammar, the lifecycle rule's gate episode, D7, step 2's grammar, the annotations' qualification rule, the writing-plans template's `**Files:**` block, D14; the `hit held` date and command, a held hit ending its episode, the spec-alone report outcomes, constraint entries without `none`, the structural register hits, qualification in gate lines and reports, the annotation's place, and the Important grade in D14 are now stated +- fixed 2026-09-26 — implementer questions 1, 3, 4, 5, 8, 10; ruling: 2026-09-26; D26 (only uncovered identifiers and missing `**Realizes:**` lines may be held; a register malformation goes through the loop's ordinary questions where its derivation does not settle it), an identifier both inherited and deferred is a hit, an `inherited [..]` slot in the summary with local citations counting once, gate-licensed register edits under the cross-document fix heading and by revision on an implemented spec, a malformed segment opening with `withdrawn` is a hit, and qualification whenever `spec:` names two or more specs, registered or legacy From 97945b98b8d0509bc8ded2ebd14907fd96496e30 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 06:55:15 +0200 Subject: [PATCH 047/143] docs(working-process): plan-coverage spec, gate fix --- docs/specs/2026-09-25-plan-coverage-design.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 558d8a1..4a6075a 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -387,8 +387,8 @@ The later plan inherits every identifier its predecessors' deferred counts in the later one until that plan realizes or defers it itself. Nor are its `**Follows:**` lines: inheritance is not transitive, so a plan drawing on a chain names every implemented -predecessor whose citations it relies on. The coverage map shows an -inherited identifier with the plan it comes from — +predecessor whose citations it relies on. The decision coverage map +shows an inherited identifier with the plan it comes from — `D3 → ../plans/.md (Task 4)`. An identifier both realized locally and inherited counts once, as local, and its map line names both sites. @@ -871,3 +871,4 @@ against the spec before disposition. - fixed 2026-09-26 — bare "coverage" named the map in four places; license: glossary **Decision coverage** `_Avoid_`; reworded to "decision coverage" - fixed 2026-09-26 — implementer questions 2, 6, 7, 9, 11, 12, 13, 14; license: the existing gate-line grammar, the lifecycle rule's gate episode, D7, step 2's grammar, the annotations' qualification rule, the writing-plans template's `**Files:**` block, D14; the `hit held` date and command, a held hit ending its episode, the spec-alone report outcomes, constraint entries without `none`, the structural register hits, qualification in gate lines and reports, the annotation's place, and the Important grade in D14 are now stated - fixed 2026-09-26 — implementer questions 1, 3, 4, 5, 8, 10; ruling: 2026-09-26; D26 (only uncovered identifiers and missing `**Realizes:**` lines may be held; a register malformation goes through the loop's ordinary questions where its derivation does not settle it), an identifier both inherited and deferred is a hit, an `inherited [..]` slot in the summary with local citations counting once, gate-licensed register edits under the cross-document fix heading and by revision on an implemented spec, a malformed segment opening with `withdrawn` is a hit, and qualification whenever `spec:` names two or more specs, registered or legacy +- hit fixed 2026-09-26 — bare "coverage" still named the map in *Sequential plans*; now "the decision coverage map" From 059aec1fbcb91f110615d09ba3716617da488fb8 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 06:57:32 +0200 Subject: [PATCH 048/143] docs(working-process): stamp plan-coverage spec integrity --- docs/specs/2026-09-25-plan-coverage-design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 4a6075a..294ff12 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -4,6 +4,7 @@ date: 2026-09-25 status: draft grilled: 2026-09-25 architect: concerns (resolved 2026-09-26) +integrity: 2026-09-26 (sha: e66d89e) decisions: registered branch: feature/plan-coverage base: develop From be412587461c5162e9d1e385c24fa0ae9f6b3c99 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 07:06:01 +0200 Subject: [PATCH 049/143] docs(working-process): plan-coverage spec, post-audit contract fixes --- docs/specs/2026-09-25-plan-coverage-design.md | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 294ff12..994faf0 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -334,8 +334,10 @@ deferred by the plan, not by the spec: **Defers:** D6 — ; ruling: -The lines form a block of their own directly after the Global -Constraints section, outside it, one line per deferred identifier, +The lines form a section of their own, headed `## Deferrals and +predecessors` at the level of the Global Constraints heading and placed +directly after that section, so a heading — not a blank line — ends +Global Constraints before them. One line per deferred identifier, qualified as the plan's `**Realizes:**` annotations are. They stay out of that section because the plan template makes every task's requirements include it, and these lines state facts about the whole @@ -365,8 +367,8 @@ depend on the deferred decision anyway. Work on one spec can run through several plans in sequence: a later plan picks up where an earlier one stopped. The later plan names each -such predecessor on a line of its own, in the same block as its -`**Defers:**` lines, after the Global Constraints section: +such predecessor on a line of its own, in the same +`## Deferrals and predecessors` section as its `**Defers:**` lines: **Follows:** ../plans/.md @@ -503,6 +505,17 @@ auditor reads a technical design, which is the only document these relations bind. A project whose rules declare nothing gives the duty nothing to cover, and it says so. +The report carries one line for this duty per audited technical design, +beside the `decision-coverage:` blocks and before the closing token, +on clean runs too: + + table-closure: 3 relations checked + table-closure: no declared relations + +The count names the declared relations the duty resolved in that +document; the hits, if any, stand above. A document other than a +technical design gets no line, since no relation binds it. + A table no rule declares a relation for is not checked by this duty, and nothing says it was: the report states what the duty covers, and a document's ad-hoc tables are outside it. So is a Contracts flow written @@ -620,8 +633,10 @@ takes no such edit: the answer becomes a revision through a newer spec's `revises:` (D24), and the held hit waits for it. The workflow rule's "hits never wait for the developer" gains one named -exception, for coverage hits alone. Every other hit keeps a fix -licensed by its own derivation. +exception, for coverage hits alone. Every other hit is fixed where its +derivation licenses the fix and put to the developer through the loop's +ordinary questions where it does not, as D26 says; only a coverage hit +takes the `hit held` line. ## Gate lines for a held hit @@ -776,7 +791,8 @@ All under `plugins/working-process/` unless noted. closure, naming the rule heading that holds the declarations); the `decision-coverage:` block, its `inherited [..]` slot and the three outcomes on a spec audited alone (`register well formed`, `not - counted`, `not checked`); the "exactly two lines" sentence; the + counted`, `not checked`); the `table-closure:` line; the "exactly + two lines" sentence; the coverage exception in *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. - `agents/integrity-auditor.md` — one sentence naming the register as a @@ -784,7 +800,8 @@ All under `plugins/working-process/` unless noted. - `rules/spec-plan-lifecycle.md` — the `decisions:` field in the frontmatter contract; the register's grammar, states and declared rule; the revised template sentence; the `**Realizes:**`, - `**Defers:**` and `**Follows:**` annotations; the four gate-line + `**Defers:**` and `**Follows:**` annotations and their + `## Deferrals and predecessors` section; the four gate-line shapes and the pre-round placement; the widened *Unfinished review-loop ledger* command; the cross-document fix heading serving a gate-licensed fix; withdrawal from an implemented spec by revision. @@ -873,3 +890,6 @@ against the spec before disposition. - fixed 2026-09-26 — implementer questions 2, 6, 7, 9, 11, 12, 13, 14; license: the existing gate-line grammar, the lifecycle rule's gate episode, D7, step 2's grammar, the annotations' qualification rule, the writing-plans template's `**Files:**` block, D14; the `hit held` date and command, a held hit ending its episode, the spec-alone report outcomes, constraint entries without `none`, the structural register hits, qualification in gate lines and reports, the annotation's place, and the Important grade in D14 are now stated - fixed 2026-09-26 — implementer questions 1, 3, 4, 5, 8, 10; ruling: 2026-09-26; D26 (only uncovered identifiers and missing `**Realizes:**` lines may be held; a register malformation goes through the loop's ordinary questions where its derivation does not settle it), an identifier both inherited and deferred is a hit, an `inherited [..]` slot in the summary with local citations counting once, gate-licensed register edits under the cross-document fix heading and by revision on an implemented spec, a malformed segment opening with `withdrawn` is a hit, and qualification whenever `spec:` names two or more specs, registered or legacy - hit fixed 2026-09-26 — bare "coverage" still named the map in *Sequential plans*; now "the decision coverage map" +- fixed 2026-09-26 — "coverage hit" had two scopes: the glossary named only the uncovered identifier, and *Disposing of a coverage hit* still said every other hit's fix is licensed by its derivation; ruling: 2026-09-26; the glossary's **Decision coverage** names both coverage hits, and the sentence now routes other hits as D26 does +- fixed 2026-09-26 — the `**Defers:**`/`**Follows:**` block had no boundary a Markdown reader sees; ruling: 2026-09-26; it is now the section `## Deferrals and predecessors` at the Global Constraints heading's level (*Deferral*, *Sequential plans*, *Changes by file*) +- fixed 2026-09-26 — the table-closure duty promised a report of what it covered with no line to carry it; ruling: 2026-09-26; *Table closure* now gives the `table-closure:` line per technical design, on clean runs too, and the card's entry in *Changes by file* names it From 48937a8f5f9a915695d6ceee9886bddf1ee3893f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 07:13:27 +0200 Subject: [PATCH 050/143] docs(working-process): plan-coverage spec, gate dispositions --- docs/specs/2026-09-25-plan-coverage-design.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 994faf0..51fa2d5 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -565,7 +565,9 @@ On a registered design spec audited on its own, the block is one line beside `not counted` and `not checked`, and the card states all three. A clean audit of a plan therefore runs to the self-report, one block -per spec, and `CLEAN`. `CLEAN` means no hit in the checks that ran, +per spec, and `CLEAN`; a clean audit of a technical design, to the +self-report, its `table-closure:` line (see *Table closure*), and +`CLEAN`. `CLEAN` means no hit in the checks that ran, and a `not checked` line bounds that guarantee in the report itself, so a dispatcher never re-reads a spec to learn what the audit covered. @@ -893,3 +895,9 @@ against the spec before disposition. - fixed 2026-09-26 — "coverage hit" had two scopes: the glossary named only the uncovered identifier, and *Disposing of a coverage hit* still said every other hit's fix is licensed by its derivation; ruling: 2026-09-26; the glossary's **Decision coverage** names both coverage hits, and the sentence now routes other hits as D26 does - fixed 2026-09-26 — the `**Defers:**`/`**Follows:**` block had no boundary a Markdown reader sees; ruling: 2026-09-26; it is now the section `## Deferrals and predecessors` at the Global Constraints heading's level (*Deferral*, *Sequential plans*, *Changes by file*) - fixed 2026-09-26 — the table-closure duty promised a report of what it covered with no line to carry it; ruling: 2026-09-26; *Table closure* now gives the `table-closure:` line per technical design, on clean runs too, and the card's entry in *Changes by file* names it +- hit dismissed 2026-09-26 — duties 10 and 11 absent from `agents/propagation-auditor.md`; counter: the spec prescribes them — *Changes by file* lists the card as a target not yet edited, and a prescribed change absent from its target is the document working as intended (the card's duty 2) +- hit dismissed 2026-09-26 — the coverage exception absent from `rules/workflow.md`; counter: prescribed, not landed, as above +- hit dismissed 2026-09-26 — `## Deferrals and predecessors` absent from `rules/spec-plan-lifecycle.md`; counter: prescribed, not landed, as above +- hit dismissed 2026-09-26 — the counters in `rules/propagation-duties.md` not yet recounted; counter: D16.2 prescribes the recount with the edit, not landed, as above +- hit dismissed 2026-09-26 — the card's "exactly two lines" sentence not yet updated; counter: prescribed, not landed, as above +- hit fixed 2026-09-26 — the clean-audit sentence in *The report* named only plans; the `table-closure:` line binds technical designs alone, so the claim was sound, and the sentence now states the technical design's clean shape too From daee595a5e944ca9f13da56301f5bd80b902b92b Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 07:30:40 +0200 Subject: [PATCH 051/143] docs(working-process): plan-coverage spec, second audit dispositions --- docs/specs/2026-09-25-plan-coverage-design.md | 209 +++++++++++------- 1 file changed, 133 insertions(+), 76 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 51fa2d5..7d1802c 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -73,8 +73,9 @@ What stands today: constraint. Argued in *The plan's annotations*. - **D8** — The lifecycle rule's sentence "changes no plan template" is revised: working-process adds exactly the D7, D18 and D19 - annotations, and the tool that writes plans keeps the rest of the - template. Argued in *The plan's annotations*. + annotations and the `## Deferrals and predecessors` heading, and the + tool that writes plans keeps the rest of the template. Argued in *The + plan's annotations*. - **D9** — A new propagation duty derives decision coverage: every leaf identifier of every registered spec that is neither withdrawn nor deferred by the audited plan needs a realizing task or constraint, @@ -84,20 +85,33 @@ What stands today: the rule defining a table declares that a column names rows of another table, every name resolves to exactly one row. Argued in *Table closure*. -- **D11** — The auditor's report gains one `decision-coverage:` block - per spec the audited plan names — a summary line and, where the - register could be counted, the map from identifier to citing sites — - before the closing token, on clean runs too; `CLEAN` means no hit in - the checks that ran. Argued in *The report*. +- **D11** — The auditor's report changes in four independent ways. + Argued in *The report*. + - **D11.1** — It gains one `decision-coverage:` block per spec the + audited plan names: a summary line and, where the register could be + counted, the map from identifier to citing sites, before the + closing token, on clean runs too. + - **D11.2** — The summary line carries an `inherited [..]` slot for + identifiers covered only by a predecessor. + - **D11.3** — A design spec audited alone gets a one-line block with + one of three outcomes: `register well formed`, `not counted`, + `not checked`. + - **D11.4** — `CLEAN` means no hit in the checks that ran, and the + card's "exactly two lines" sentence and the workflow rule's + closing-token paragraph change together to admit the report's + non-hit lines. - **D12** — A coverage hit stays a hit, but its fix is triaged by license, and a fix needing a decision the spec does not make waits - for the developer. The exception to "hits never wait" covers coverage - hits alone. Argued in *Disposing of a coverage hit*. -- **D13** — Four gate-line shapes record that wait: `hit held`, and - its three terminal rewrites `hit fixed …; ruling:`, - `hit deferred …; ruling:` and `hit withdrawn …; ruling:`. `hit held` - joins the Unfinished-work list. Argued in *Gate lines for a held - hit*. + for the developer. The exception to "hits never wait" covers the + coverage duty's hits alone. Argued in *Disposing of a coverage hit*. +- **D13** — The lifecycle rule records a held hit in two independent + ways. Argued in *Gate lines for a held hit*. + - **D13.1** — Four gate-line shapes: `hit held`, and its three + terminal rewrites `hit fixed …; ruling:`, `hit deferred …; ruling:` + and `hit withdrawn …; ruling:`. + - **D13.2** — The *Unfinished review-loop ledger* entry widens — its + name, its description and its command — to take the `hit held` + line. - **D14** — The plan-adversary gains a dimension judging each decision's realization: do the tasks and constraints citing it realize it in full together, and does a cited constraint actually @@ -141,9 +155,26 @@ What stands today: - **D25** — A plan whose `spec:` names two or more specs, registered or legacy, qualifies every identifier with its spec's path. Argued in *The plan's annotations*. -- **D26** — Only an uncovered identifier and a task lacking its - `**Realizes:**` line may be held; every other hit of the coverage duty - takes the ordinary path. Argued in *Disposing of a coverage hit*. +- **D26** — A hit of the coverage duty whose fix needs a decision no + derivation settles is held for the developer; every other hit of that + duty is fixed by its derivation. No other duty's hit is ever held. + Argued in *Disposing of a coverage hit*. +- **D27** — The report carries one `table-closure:` line per audited + technical design, stating how many declared relations it resolved or + that none are declared. Argued in *Table closure*. +- **D28** — Four surfaces stay as they are. Argued in *Readers*, + *Out of scope*, *The plan's annotations* and *Gate lines for a held + hit*. + - **D28.1** — The architect agent's card gains nothing. + - **D28.2** — The plan-adversary's dimension 5 is unchanged. + - **D28.3** — The tool that writes plans, `superpowers:writing-plans`, + is not changed. + - **D28.4** — The ordinary `hit fixed` shape, without `ruling:`, stays + for every hit whose fix its derivation licensed. +- **D29** — A held hit's register edit is recorded in the spec's own + ledger; where the spec's loop is closed, under the cross-document fix + heading, which now serves a gate-licensed fix as well. Argued in + *Disposing of a coverage hit*. ## The register @@ -169,8 +200,11 @@ end. Prose beneath, after the blank line, is free. A state token is recognized by its reserved opening word alone: the paragraph's final segment, after the full stop that ends the decision's -statement, beginning with `withdrawn`. A segment so opening that does -not match the token's grammar is a hit, never prose. Any other text is +statement — and after any "Argued in" pointer, which belongs to the +statement — beginning with `withdrawn`. The token runs from that word to +the paragraph's end, so its `` may hold a full stop. A segment +so opening that does not match the token's grammar is a hit, never +prose. Any other text is prose, so a statement whose own words happen to end in "withdrawn" before its full stop is no token. @@ -270,8 +304,9 @@ gets a reader (see *Readers*). ## The plan's annotations A plan whose `spec:` names at least one registered spec carries, on -every task, a line beside its `**Files:**` block — after the -`**Interfaces:**` block where the task has one: +every task, a line after its `**Files:**` block — after the +`**Interfaces:**` block where the task has one — and before the task's +first step: **Realizes:** D3, D5 **Realizes:** none @@ -505,16 +540,17 @@ auditor reads a technical design, which is the only document these relations bind. A project whose rules declare nothing gives the duty nothing to cover, and it says so. -The report carries one line for this duty per audited technical design, -beside the `decision-coverage:` blocks and before the closing token, -on clean runs too: +The report carries one line for this duty per audited technical design +(D27), after any hits and before the closing token, on clean runs too: table-closure: 3 relations checked table-closure: no declared relations The count names the declared relations the duty resolved in that -document; the hits, if any, stand above. A document other than a -technical design gets no line, since no relation binds it. +document — those whose tables it carries; a relation whose table is +absent, as a conditional State section may be, is not applicable there +and is not counted. The hits, if any, stand above. A document other +than a technical design gets no line, since no relation binds it. A table no rule declares a relation for is not checked by this duty, and nothing says it was: the report states what the duty covers, and a @@ -534,15 +570,20 @@ and before the token, and present whether or not any hit fired: D1 → Task 2 D2 → ../plans/.md (Task 3) D3 → Task 4, Task 6 - D9 → Global Constraints: No code + D4.2 → — + D9 → Global Constraints: "No code" + D10 → ../plans/.md (Global Constraints: "No code") … decision-coverage: not counted — malformed register decision-coverage: not checked — no decision register The summary line opens the block, and under a counted spec the map follows it, one indented line per counted identifier, naming every task -and constraint that cites it; an uncovered identifier maps to nothing -and is also a hit above. The map is what the fraction summarises, so a +and constraint that cites it. A task is named by its heading's number; +a Global Constraints entry, which the template gives no key, by its +opening words in quotes; an inherited site, by its plan's path with the +site in parentheses. An uncovered identifier maps to `—` and is also a +hit above. The map is what the fraction summarises, so a reader can check the one against the other. The fraction's denominator is the counted set of step 3, and its numerator every counted identifier in the cited set of step 4. `inherited [..]` lists the @@ -554,8 +595,10 @@ Every identifier in the block is qualified as the plan's annotations are. `not counted` follows a malformed register, whose hits stand above it, and carries no map, since its denominator cannot be derived. -On a registered design spec audited on its own, the block is one line -(D22) and no map follows, since no plan cites anything yet: +On a design spec audited on its own, the block is one line (D22) and no +map follows, since no plan cites anything yet — `register well formed` +or `not counted` for a registered spec, `not checked` for a legacy +one: decision-coverage: register well formed decision-coverage: not counted — malformed register @@ -565,17 +608,18 @@ On a registered design spec audited on its own, the block is one line beside `not counted` and `not checked`, and the card states all three. A clean audit of a plan therefore runs to the self-report, one block -per spec, and `CLEAN`; a clean audit of a technical design, to the -self-report, its `table-closure:` line (see *Table closure*), and -`CLEAN`. `CLEAN` means no hit in the checks that ran, +per spec, and `CLEAN`; of a design spec alone, to the self-report, its +one-line block and `CLEAN`; of a technical design, to the self-report, +its `table-closure:` line (see *Table closure*) and `CLEAN`. `CLEAN` +means no hit in the checks that ran, and a `not checked` line bounds that guarantee in the report itself, so a dispatcher never re-reads a spec to learn what the audit covered. The contract changes in two places edited together: the card's sentence "a clean audit runs to exactly two lines", and the workflow rule's paragraph saying a report's body governs, never its closing -token — which gains that a `decision-coverage:` line is neither a hit -nor a violation of the token's position. +token — which gains that a `decision-coverage:` or `table-closure:` +line is neither a hit nor a violation of the token's position. The name differs from the integrity auditor's `coverage:` line on purpose: that line reports how much of a document the audit read, and @@ -584,15 +628,15 @@ dispatcher to read one as the other. ## Disposing of a coverage hit -Two hits of the coverage duty are coverage hits in this sense (D26): an -identifier the cited set lacks, and a task lacking its `**Realizes:**` -line in a plan the convention binds. Only these may be held. Every -other hit of the duty — a malformed register, a bad `**Defers:**` or -`**Follows:**` line, a cited identifier that is withdrawn, a group or -undefined — takes the ordinary path: fixed where its derivation settles -the fix, and put to the developer through the loop's usual questions -where it does not, as when breaking a `replaced by` cycle means -choosing which entry stands. +Two hits of the coverage duty are coverage hits in the glossary's sense: +an identifier the cited set lacks, and a task lacking its +`**Realizes:**` line in a plan the convention binds. They take the +triage below. Every other hit of the duty — a malformed register, a bad +`**Defers:**` or `**Follows:**` line, a cited identifier that is +withdrawn, a group or undefined — is fixed where its derivation settles +the fix and held where it does not (D26), as when breaking a +`replaced by` cycle means choosing which entry stands. No other duty's +hit is ever held. The detection is mechanical and runs where the measured failure slipped through: at the gate, on the cheapest family, before the plan-adversary. @@ -615,19 +659,21 @@ dispatcher classifies its fix by license: A held hit blocks the dispatch its gate guards, as a held finding already blocks a fresh round. The answer lands where the decision -belongs: in the spec as a new or a sharpened register entry, or as the -entry's `withdrawn` token where the developer drops the decision; in -the plan as a `**Defers:**` line where the developer defers it. The -plan is then fixed and the gate re-run, and the plan-adversary -dispatches only once the gate passes. +belongs: in the spec as a new, a sharpened or a repaired register +entry, or as the entry's `withdrawn` token where the developer drops +the decision; in the plan as a `**Defers:**` line where the developer +defers it. The document the answer changed is then fixed, and the gate +re-runs over it and over whatever depends on it — the plan, where the +spec changed. The dispatch the gate guards goes out only once it +passes. A held hit ends its gate episode: the gate has not passed, and the dispatch it guards waits. The re-run after the developer's answer is a fresh episode, with its own re-dispatch bound. -A register edit the answer causes is recorded in the spec's own ledger. -Where the spec's loop is closed, it goes under the cross-document fix -heading the lifecycle rule already defines — +A register edit the answer causes is recorded in the spec's own ledger +(D29). Where the spec's loop is closed, it goes under the +cross-document fix heading the lifecycle rule already defines — `### — fix from ` — which serves a gate-licensed fix as it serves a review-licensed one, and the plan's `hit fixed` line names that heading and the line under it. A spec already `implemented` @@ -635,10 +681,9 @@ takes no such edit: the answer becomes a revision through a newer spec's `revises:` (D24), and the held hit waits for it. The workflow rule's "hits never wait for the developer" gains one named -exception, for coverage hits alone. Every other hit is fixed where its -derivation licenses the fix and put to the developer through the loop's -ordinary questions where it does not, as D26 says; only a coverage hit -takes the `hit held` line. +exception, for the coverage duty's hits alone (D26); only such a hit +takes the `hit held` line. Every other duty's hit keeps a fix licensed +by its own derivation. ## Gate lines for a held hit @@ -652,21 +697,23 @@ terminal rewrites of it: A `hit held` line is rewritten in place to one of the three terminal shapes, so the state always has one home. The rewrite happens after the -gate re-runs over the changed spec and plan, never on the developer's -answer alone. `hit fixed … ruling:` records that the developer settled -the missing decision and the plan now realizes it. `hit deferred` -records an approved deferral, now a `**Defers:**` line in the plan, and -`hit withdrawn` a decision the developer dropped, its register entry -now a tombstone. +gate re-runs over the changed document and what depends on it, never on +the developer's answer alone. `hit fixed … ruling:` records that the +developer settled the missing decision and the document now carries +it — a plan that realizes the decision, or a register the developer's +ruling repaired. `hit deferred` records an approved deferral, now a +`**Defers:**` line in the plan, and `hit withdrawn` a decision the +developer dropped, its register entry now a tombstone; each is written +only where it names what actually happened. Neither is `hit dismissed`, +because the gap was real when the hit fired. The ordinary `hit fixed` +shape, without `ruling:`, stays as it is for every hit whose fix its +derivation licensed (D28.4). A `hit held` line lives in the ledger of the document whose gate raised -it — the audited plan. Its leading date is the gate's, as on every gate -line, where `open` and `held` disposition lines carry none; the -terminal rewrite replaces it with the date the line reached its -terminal state. Neither is -`hit dismissed`, because the gap was real when the hit fired. The -ordinary `hit fixed` shape, without `ruling:`, stays as it is for every -hit whose fix its derivation licensed. +it — the audited plan, or the design spec where the spec was audited +alone. Its leading date is the gate's, as on every gate line, where +`open` and `held` disposition lines carry none; the terminal rewrite +replaces it with the date the line reached its terminal state. `ruling:` on a gate line means what it means on a disposition line: the developer decided. A diff-scoped round treats such a line as settled, @@ -681,11 +728,17 @@ episode that holds at least one hit therefore writes every one of its lines — the `hit held` line and its siblings alike — in the `## Review rounds` section before the first round heading, and they stay there, rewrites included: no round produced them, and one episode -keeps one home (D23). A pre-round episode that holds nothing keeps the -existing rule. +keeps one home (D23). Once such lines stand there, every later +pre-round episode writes there too, holding or not, so the document's +pre-round gate history stays in one place. A pre-round episode on a +document with no pre-round lines, holding nothing, keeps the existing +rule. A plan that has no `## Review rounds` section yet gains one, at +its end, when its first gate line is written. `hit held` joins the Unfinished-work list by widening the *Unfinished -review-loop ledger* entry's command to match it, within that entry's +review-loop ledger* entry (D13.2): its description becomes "a +disposition line or a held gate line nobody closed", and its command +widens to match the gate line, within that entry's existing scope — inside a `## Review rounds` section, where the pre-round line also sits. Because a gate line's date stands between its token and its dash, the widened command gains an alternative rather than @@ -811,8 +864,9 @@ All under `plugins/working-process/` unless noted. closure*, under one fixed heading: which columns name rows of Parts, how their cells split, the `external:` value, and that a numbered sequence is outside the relation. -- `rules/workflow.md` — the coverage exception to "hits never wait"; - the `decision-coverage:` line beside the closing-token paragraph; in +- `rules/workflow.md` — the coverage duty's exception to "hits never + wait"; the `decision-coverage:` and `table-closure:` lines beside the + closing-token paragraph; in step 4, the duty of a brief delegating plan-writing to name the lifecycle rule and require reading it. - `rules/propagation-duties.md` — the rows and counters of *The @@ -825,7 +879,10 @@ All under `plugins/working-process/` unless noted. **Hit** gained the held coverage hit. Amended by the review rounds: **Ruling** also covers a plan's deferral, and **Decision coverage** counts deferrals by the audited plan and citations inherited from an - implemented predecessor. + implemented predecessor and names both coverage hits. Amended at the + second integrity audit: **Hit** holds any hit of the coverage duty + whose fix needs a decision, and **Audit agent**'s disposed audit + admits a hit closed by the developer's ruling. - `README.md`, `CHANGELOG.md` — the new duties and the convention. ## Open questions From 938c566f6530e4e63fa19e2cae0b7eabadee7c4c Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 07:44:57 +0200 Subject: [PATCH 052/143] docs(working-process): plan-coverage spec, second audit ledger --- docs/specs/2026-09-25-plan-coverage-design.md | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 7d1802c..8e4ad41 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -175,6 +175,10 @@ What stands today: ledger; where the spec's loop is closed, under the cross-document fix heading, which now serves a gate-licensed fix as well. Argued in *Disposing of a coverage hit*. +- **D30** — An inherited citation of an identifier withdrawn after its + predecessor shipped is skipped — neither counted nor a hit — and + listed apart in the report as `withdrawn since`. Argued in + *Sequential plans*. ## The register @@ -431,6 +435,24 @@ shows an inherited identifier with the plan it comes from — locally and inherited counts once, as local, and its map line names both sites. +A predecessor may cite an identifier the spec's register has since +withdrawn — the spec, still open to edits, took the `withdrawn` token +after the predecessor shipped (D30). The frozen predecessor cannot +change, so that inherited citation is skipped: it is not counted, it +covers nothing, and it raises no hit against the later plan. It is not +hidden either — the report lists it after the map, outside the counted +identifiers and the fraction, as `withdrawn since: D4 ← +../plans/.md (Task 2)`. The exception covers inheritance alone: a +`**Realizes:**` in the later plan that cites the withdrawn identifier +directly is still a hit. A `replaced by` successor inherits no coverage; +it is counted and needs its own realization. Whether the later plan +must undo what the predecessor built for the withdrawn decision is not +decided by the withdrawal itself: the plan-adversary judges the later +plan against the decisions that stand, and where neither the withdrawal +nor a successor decision settles it, that is a question for the +developer. A decision withdrawn through a newer spec's `revises:` +(D24) leaves the old register untouched and is not this case. + The automatic alternative — excluding whatever any other plan of the same spec cites — was refused: two draft plans could each exempt the other from one decision, and a plan's result would change when an @@ -468,8 +490,9 @@ A tenth propagation duty runs on a plan. For each spec the plan's annotations cite, on tasks and constraint entries, and every identifier inherited through the plan's `**Follows:**` lines, each with the plan it comes from. A cited withdrawn identifier and a - cited group are hits, and so is a cited identifier the register does - not define. That last is an error in the plan, never a gap in the + cited group are hits — except an inherited citation of an identifier + withdrawn since, which *Sequential plans* skips — and so is a cited + identifier the register does not define. That last is an error in the plan, never a gap in the spec: identifiers are minted only in the register, so a plan citing one the register lacks has mistyped or invented it. Duty 5 does not take it, since duty 5 reads an undefined name as a gap in its @@ -958,3 +981,17 @@ against the spec before disposition. - hit dismissed 2026-09-26 — the counters in `rules/propagation-duties.md` not yet recounted; counter: D16.2 prescribes the recount with the edit, not landed, as above - hit dismissed 2026-09-26 — the card's "exactly two lines" sentence not yet updated; counter: prescribed, not landed, as above - hit fixed 2026-09-26 — the clean-audit sentence in *The report* named only plans; the `table-closure:` line binds technical designs alone, so the claim was sound, and the sentence now states the technical design's clean shape too + +### 2026-09-26 — integrity audit, fable, at the consumption gate (second) + +Coverage tell: 903 lines read, highest line cited 897 — a +whole-document read of the body the first audit's stale stamp left +unaudited. Eight defects and twelve implementer questions. + +- fixed 2026-09-26 — decisions the text makes had no register entry: the `table-closure:` line, the fix heading serving a gate-licensed fix, and four stay-as-is rulings; license: the register's declared rule and D4; D27, D29 and the group D28 added +- fixed 2026-09-26 — D11 and D13 bundled independently realizable changes; license: D3; both split into children (D11.1–D11.4, D13.1–D13.2) +- fixed 2026-09-26 — D8 admitted annotations only while *Deferral* adds a heading, the workflow closing-token paragraph was amended for `decision-coverage:` alone, and the spec-alone block's trigger excluded the `not checked` outcome it lists; license: the document itself; each now matches its section +- fixed 2026-09-26 — the glossary's **Audit agent** required every hit fixed or dismissed; license: D13.1; a hit closed by the developer's ruling now counts as disposed, and *Changes by file* lists the amendment +- fixed 2026-09-26 — a non-coverage hit of the coverage duty needing a decision had no shape to wait in; ruling: 2026-09-26; D26 now holds any hit of the coverage duty whose fix needs a decision no derivation settles, the held line lives in the audited document's ledger, `hit fixed … ruling:` covers a repaired register, and the re-run gate covers the changed document and what depends on it +- fixed 2026-09-26 — implementer question 3, an inherited citation of an identifier withdrawn since; ruling: 2026-09-26; D30 added — skipped, listed apart as `withdrawn since`, never covering, a direct citation still a hit, a successor needing its own realization, undoing judged against the standing decisions, and a `revises:` withdrawal kept out of the case +- fixed 2026-09-26 — implementer questions 4–12; license: the report contract, the lifecycle rule's gate lines and ledger section, and the template's task shape; map line shapes, the clean shape of every audited document class, the relation count, the annotation's place before the first step, the section a plan gains at its first gate line, the widened Unfinished-work entry, the token's place after the pointer, later pre-round episodes and the `table-closure:` placement are now stated From a2d37f630a4a98a60c46927452213643c8753ed4 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 07:45:16 +0200 Subject: [PATCH 053/143] docs(working-process): plan-coverage spec, rewrap --- docs/specs/2026-09-25-plan-coverage-design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 8e4ad41..039037d 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -492,8 +492,8 @@ A tenth propagation duty runs on a plan. For each spec the plan's with the plan it comes from. A cited withdrawn identifier and a cited group are hits — except an inherited citation of an identifier withdrawn since, which *Sequential plans* skips — and so is a cited - identifier the register does not define. That last is an error in the plan, never a gap in the - spec: identifiers are minted only in the register, so a plan citing + identifier the register does not define. That last is an error in + the plan, never a gap in the spec: identifiers are minted only in the register, so a plan citing one the register lacks has mistyped or invented it. Duty 5 does not take it, since duty 5 reads an undefined name as a gap in its source. From 8ff587fbca0525a2777c26b8e67ce3aafebd1e13 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 07:49:21 +0200 Subject: [PATCH 054/143] docs(working-process): restamp plan-coverage spec integrity --- docs/specs/2026-09-25-plan-coverage-design.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 039037d..2afa83e 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -4,7 +4,7 @@ date: 2026-09-25 status: draft grilled: 2026-09-25 architect: concerns (resolved 2026-09-26) -integrity: 2026-09-26 (sha: e66d89e) +integrity: 2026-09-26 (sha: 884a6cf) decisions: registered branch: feature/plan-coverage base: develop @@ -493,10 +493,10 @@ A tenth propagation duty runs on a plan. For each spec the plan's cited group are hits — except an inherited citation of an identifier withdrawn since, which *Sequential plans* skips — and so is a cited identifier the register does not define. That last is an error in - the plan, never a gap in the spec: identifiers are minted only in the register, so a plan citing - one the register lacks has mistyped or invented it. Duty 5 does not - take it, since duty 5 reads an undefined name as a gap in its - source. + the plan, never a gap in the spec: identifiers are minted only in + the register, so a plan citing one the register lacks has mistyped + or invented it. Duty 5 does not take it, since duty 5 reads an + undefined name as a gap in its source. 5. Check the plan's `**Defers:**` lines as *Deferral* says and its `**Follows:**` lines as *Sequential plans* says, against the cited set of step 4. From 69b8dcb0ed73c0833f21c35ed35495041e1d2c41 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 08:02:53 +0200 Subject: [PATCH 055/143] docs(working-process): glossary terms for plan coverage --- docs/domain/glossary.md | 45 +++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index 47c5d73..c90684b 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -291,12 +291,17 @@ Closes a round; the finding-level counterpart is a Ruling. _Avoid_: developer override, manual close **Ruling**: -The developer's authorization of one finding's disposition, written as -the `ruling: ` clause on that finding's ledger line — dated fresh, -or carrying an earlier ruling's date where the line folds a re-raise -against it. On a fold against a line written before the clause existed, -the date on that line's own terminal token is the earlier ruling. Closes -a finding; the round-level counterpart is an Adjudication. +The developer's authorization of a finding's disposition, a held hit's +disposition, a plan's deferral of a registered decision, or a decision +register entry's state change, recorded by a +`ruling: ` clause where that record lives — dated with the +decision, never with the edit that wrote it, and on a finding's line +dated fresh or carrying an earlier ruling's date where the line folds a +re-raise against it. On a fold against a line written before the +clause existed, the date on that line's own terminal token is the +earlier ruling. It closes a finding; a held hit it authorizes closes +only once the gate has rechecked the fix. The round-level counterpart +is an Adjudication. _Avoid_: developer decision (as the name), developer fix **Fallback**: @@ -379,7 +384,8 @@ document and returns material for the dispatcher's disposition — hits (`propagation-auditor`, the mechanical pass) or defects-with-quotes and ranked questions (`integrity-auditor`, the judgment pass). Dispatched as a gate before expensive work: the precondition is a disposed audit — -every hit fixed or dismissed, every defect applied or declined — never +every hit fixed, dismissed, or closed by the developer's ruling, every +defect applied or declined — never an empty one, and never a judgment on the design. The third dispatch category beside Verdict agent and Consultation: an audit agent adopts no persona and its output is never a Contribution. @@ -393,7 +399,10 @@ part company over disposition: a propagation-auditor hit is confirmed or dismissed by the dispatcher and the outcome is written as a gate line, while an Unfinished-work hit has no dismissal at all — its only disposition is ceasing to match, when the state the command anchors is -rewritten or annotated closed. +rewritten or annotated closed. A confirmed hit of the coverage duty +whose fix needs a decision no derivation settles may be held for the +developer; its disposition stays open until that decision is recorded +and the gate rechecks the changed spec and plan. _Avoid_: mechanical finding **Disposition ledger**: @@ -438,6 +447,26 @@ disposition lines and nothing else — its own leading token, no severity, no license. _Avoid_: hit line, audit line +**Decision register**: +The `## Decisions` section of a design spec that sets +`decisions: registered`: one entry per decision the spec makes that +needs realization, each under a stable identifier that is never reused — +a withdrawn entry stays as a tombstone reserving it. Entries with +children are groups; their leaf entries identify the independently +realizable decisions. +_Avoid_: decision log, decision table (as the name) + +**Decision coverage**: +The map from each counted identifier of a decision register — each leaf +identifier that is neither withdrawn nor deferred by the audited plan — +to the plan tasks and Global Constraints entries citing it, in the +audited plan or in an implemented predecessor its `**Follows:**` lines +name, derived by the propagation auditor. A counted identifier nothing +cites, and a task lacking its `**Realizes:**` line where the convention +binds, are the two coverage hits. +_Avoid_: coverage (bare, for this map — the integrity audit's coverage +tell is another fact) + **Chain debt**: The obligation a diff-scoped LGTM leaves: the document was approved with no whole-document read at the end, so somebody must still take From 670a07a88ca4c010b7495bf11a459d2d60c5e2b3 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 08:17:58 +0200 Subject: [PATCH 056/143] docs(working-process): plan-coverage implementation plan --- docs/plans/2026-09-26-plan-coverage.md | 1872 ++++++++++++++++++++++++ 1 file changed, 1872 insertions(+) create mode 100644 docs/plans/2026-09-26-plan-coverage.md diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md new file mode 100644 index 0000000..6b7f7f0 --- /dev/null +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -0,0 +1,1872 @@ +--- +ticket: none +date: 2026-09-26 +status: draft +spec: ../specs/2026-09-25-plan-coverage-design.md +branch: feature/plan-coverage +base: develop +--- + +# Plan coverage Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Land `docs/specs/2026-09-25-plan-coverage-design.md` — the +decision register, the plan annotations, the propagation auditor's +coverage and table-closure duties, the held coverage hit, and the two +readers' new checks — across the `working-process` plugin. + +**Architecture:** Every deliverable is prose in a shipped rule, agent +card, skill or README. There is no code and no test suite; a task's test +is the check block it publishes, run before and after the edit, with the +before values measured against the files on 2026-09-26. The grammar has +one home — the spec-plan-lifecycle rule for the register and the plan +annotations, the technical-design rule for the table relations — and the +auditor's card names those sections by heading instead of repeating +them. + +**Tech Stack:** Markdown rule, agent and skill files distributed as a +Claude Code plugin and Rules payload; `tr`, `grep`, `awk`, `python3` and +`claude plugin validate` for verification. + +## Global Constraints + +- **The spec is the source.** Where this plan and the spec disagree, the + spec wins and the plan is wrong — except where *Deviations from the + spec* below records a departure and its reason. +- **Prose wraps at 72 characters** in every rule, agent card, skill and + README. Match the surrounding paragraph; never reflow a paragraph this + plan does not change. Indented grammar examples and table rows are + exempt, as they are everywhere in the rules. +- **Copy every Replace block verbatim.** The blocks already carry the + elements-of-style pass; a block an implementer rewords owes the pass + again, and its checks may stop matching. +- **Checks normalise whitespace and match fixed strings.** A prose check + pipes the file through `tr -s '[:space:]' ' '` and counts with + `grep -oF … | wc -l`, so a phrase matches wherever a line wraps and + the count is the same under GNU grep and ugrep. Only a check anchoring + something that cannot wrap — a heading, a table row, a command — reads + the file plain. +- **The glossary binds.** `docs/domain/glossary.md` terms and `_Avoid_` + bans govern every new sentence: **Decision register**, **Decision + coverage**, **Hit**, **Gate line**, **Ruling** and **Judged document** + are canonical; bare "coverage" for the decision coverage map, "decision + log" and "mechanical finding" are banned. +- **Realizes:** D28.1 — **The architect agent's card gains nothing.** + `plugins/working-process/agents/architect.md` is not modified by any + task; Task 12 checks it. +- **Realizes:** D28.2 — **The plan-adversary's dimension 5 is + unchanged.** Task 7 adds a dimension after it and touches none of its + text; Task 7 and Task 12 check the section's hash. +- **Realizes:** D28.3 — **`superpowers:writing-plans` is not changed.** + Nothing in this plan edits another plugin; the plan annotations live + in the working-process lifecycle rule alone. +- **Public repo hygiene**: no machine-specific paths, no company or + client names, all committed text in English. +- **Commit messages are one line** — a conventional-commit subject, no + body, no trailers, `Co-Authored-By` included. A subagent implementer's + brief says so outright, since subagents add the trailer by default. +- **Commits land on `feature/plan-coverage`.** The document branch + `feature/plan-coverage.docs` merges into it at the implementation-ready + gate, before Task 1. +- **Do not run `sync-rules`, do not bump the plugin version, and do not + move the spec's or this plan's `status`.** All three are the + developer's. + +## Deviations from the spec + +Recorded here and beside the text they concern. + +1. **Task 5 also rewrites the card's `description:` and Task 11 the + README's "a clean audit reports the single line `CLEAN`".** The + spec's *Changes by file* names only the card's "exactly two lines" + sentence, but both texts state the contract D11.4 changes, and a + changed interface reaches every consumer (propagation duty 1). +2. **Task 3 amends the lifecycle sentence "`open` and `held` are the + non-terminal states, and the only two the Unfinished-work list + anchors".** D13.2 makes the list anchor `hit held` as well, so the + sentence would turn false beside the widened command. +3. **Task 3 renames "Neither shape covers a hit left outstanding" to + "No gate-line shape covers…".** With four new shapes, "neither" no + longer names two; the out-of-scope statement itself stands, as the + spec's *Out of scope* keeps it. +4. **Task 6 narrows the workflow rule's "A hit is a report, not a + question" to a dismissed hit.** D26 makes a held coverage hit the one + hit that asks the developer something, so the sentence as written + would contradict the exception the same task adds. +5. **The register grammar lives in the lifecycle rule alone; the + auditor's duty 10 names its sections by heading.** The spec lists the + grammar under the lifecycle rule and the steps under the card, and + stating the grammar in both would give it two homes — the same reason + D21 gives the table relations one home. +6. **The glossary is verified, not written.** The spec's *Changes by + file* records the glossary entries as applied at the grilling and + the reviews; commit `69b8dcb` holds them. Task 12 checks they stand. +7. **This plan carries `**Realizes:**` annotations although the + convention ships with it.** The installed propagation auditor has no + duty 10 yet, so Task 12 derives this plan's decision coverage with a + script instead of relying on the gate. + +## File structure + +Modified, all under `plugins/working-process/`: + +- `rules/spec-plan-lifecycle.md` — Task 1 (the `decisions:` field and + the `## Decision register` section), Task 2 (the template sentence and + the `## Plan annotations` section), Task 3 (the gate lines, the + non-terminal sentence, the fix heading, the Unfinished-work entry). +- `rules/technical-design.md` — Task 4: the `## Declared table + relations` section. +- `agents/propagation-auditor.md` — Task 5: duties 10 and 11, the + output contract, the held-hit exception, one out-of-bounds bullet, the + description. +- `rules/workflow.md` — Task 6: the held coverage hit in *The + propagation gate*, the closing-token paragraph, and the delegating + brief in step 4. +- `agents/plan-adversary.md` — Task 7: dimension 6. +- `agents/integrity-auditor.md` — Task 8: the register as a declared + rule. +- `rules/propagation-duties.md` — Task 9: two rows, the duty-2 anchor + case, the counters. +- `skills/grilling-session/SKILL.md` — Task 10: updating the register. +- `README.md`, `CHANGELOG.md` — Task 11. + +Created: nothing. Not modified: `agents/architect.md` (D28.1), the +glossary (already applied — Deviation 6), and `process-status`, which +runs the Unfinished-work commands as the lifecycle rule publishes them +and names none itself. + +## Order and independence + +- Tasks 1, 2 and 3 edit the lifecycle rule in that order. Task 2's + second anchor is the `## Finding what revises a document` heading, + which Task 1's replacement keeps as its last line, so the anchor still + matches once after Task 1. +- Task 5's duty 10 names the lifecycle sections Tasks 1 and 2 create, + and its duty 11 names the heading Task 4 creates. Run Tasks 1, 2 and 4 + first; the names are the interface, recorded in each task's + **Interfaces:** block. +- Task 6's exception points at the `hit held` line Task 3 defines, and + Task 9's rows cite the duty numbers Task 5 creates. +- Tasks 7, 8 and 10 depend on nothing else in this plan. +- `claude plugin validate` runs once, in Task 12, after every file has + changed. + +--- + +### Task 1: Lifecycle rule — the `decisions:` field and the decision register + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md` — the + frontmatter block, the bullet list under it, and a new section before + `## Finding what revises a document`. + +**Interfaces:** +- Consumes: nothing from earlier tasks. +- Produces: the section heading `## Decision register`, which Task 5's + duty 10 and Task 10's bullet name; the grammar of an identity + paragraph, the `withdrawn` token and the declared rule. + +**Realizes:** D1, D2, D3, D4, D5, D6, D17, D24 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(grep -c '^decisions: registered ' $F)" +echo "B $(grep -c '^## Decision register$' $F)" +echo "C $(n $F | grep -oF 'The register is an index, never a copy.' | wc -l)" +echo "D $(n $F | grep -oF 'withdrawn , ruling: [; replaced by ]' | wc -l)" +echo "E $(n $F | grep -oF 'the register lists every decision in this spec that needs realization.' | wc -l)" +echo "F $(n $F | grep -oF 'never in a sweep' | wc -l)" +echo "G $(n $F | grep -oF 'a newer design spec carrying `revises:`' | wc -l)" +echo "H $(grep -c '^## Finding what revises a document$' $F)" +``` + +Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 0`, `G 0`, `H 1`. + +- [ ] **Step 2: Add the field to the frontmatter block** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +revises: ./.md # optional: documents this one departs from; inline list when several +``` + +Replace with: + +``` +decisions: registered # optional, design specs only: the body carries a decision register +revises: ./.md # optional: documents this one departs from; inline list when several +``` + +- [ ] **Step 3: Add the field's bullet** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` + One technical design per design spec. +- Where a plan's `technical-design:` names documents, those documents +``` + +Replace with: + +``` + One technical design per design spec. +- `decisions:` is optional, on design specs only, and takes one value, + `registered`: the spec carries a decision register, defined under + *Decision register* below. It is a convention field rather than a + process stamp — no review step writes it, and no Unfinished-work + command reads it. +- Where a plan's `technical-design:` names documents, those documents +``` + +- [ ] **Step 4: Add the section** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +## Finding what revises a document +``` + +Replace with: + +``` +## Decision register + +A design spec that sets `decisions: registered` carries a `## Decisions` +section — the decision register, the enumerable list of the decisions +the spec makes that need realization. The propagation auditor, when +that agent is available, derives a plan's decision coverage from it. + +The register is an index, never a copy. An entry is one identity +paragraph — the identifier and a short statement of the decision — and +may name the section that argues it: + + - **D3** — . Argued in *
*. [state token] + + + +The identity paragraph is the list item's first paragraph: its opening +line and the continuation lines after it, up to the first of a blank +line, a nested list item, the next list item at the same or a higher +level, or the end of the section. A reader joins those lines, +normalising whitespace, before parsing, so a statement wrapped across +lines keeps its state token at the paragraph's end. Prose beneath the +blank line is free. The grammar enforces the paragraph's shape, never a +sentence count: restating the argument in the register would give the +decision two homes, and the first fix wave would leave the register +describing the decision's old form. + +The identifier is the literal token `**D**`, or `**D.**` for a +child, at the start of a list item under `## Decisions`; a child's item +nests under its parent's. Identifiers are unique within the spec and +never positional, renumbered or reused: a decision added mid-loop takes +the next free number. A Markdown numbered list is refused, because its +numbers are positions and move when an item is inserted above. An +element of a list that needs realization on its own takes a child +identifier. The parent of children is a group: it enters no count, a +citation of it covers none of its children, and it carries no state +token. + +Every decision that needs realization enters, cross-cutting constraints +included. A ruling that something stays as it is enters too, since an +implementer can break it, and a plan realizes it as a constraint; no +kind of entry is exempt. A rejected alternative never enters: nobody +realizes it, and it already has homes — a spec's out-of-scope section, +a technical design's *Cuts not taken*. + +An entry without a token is active. One state token exists, carried by +a leaf or not at all, and never by a group: + + withdrawn , ruling: [; replaced by ] + +The decision no longer stands. The entry stays as a tombstone that +reserves its identifier, so a reuse shows up as a duplicate. +`replaced by` names an identifier of the same register other than its +own, and a chain of successors never forms a cycle; a successor may +itself be withdrawn. The token is recognized by its opening word alone: +the paragraph's final segment — after the full stop that ends the +statement and after any "Argued in" pointer — beginning with +`withdrawn`, and running to the paragraph's end, so its reason may hold +a full stop. A segment so opening that does not match the grammar is +malformed, never prose. Withdrawing a group is written on each of its +leaves. A decision that still stands but one plan does not realize is +no state of the entry: that plan defers it (see *Plan annotations*). + +A withdrawal changes what the spec decides, so the session writes the +token only on the developer's explicit decision. Its `ruling:` is dated +with that decision; the edit lands in the spec's own ledger and leaves +the `integrity:` stamp stale, as any body edit does. A spec already +`implemented` takes no such edit: the decision is withdrawn by a newer +design spec carrying `revises:` and the register that stands. + +The spec's author creates the register while writing the spec, so a +design that never meets a grilling session still has one, and a +grilling session updates it as its decisions land. A spec carrying the +field declares a rule about itself: *the register lists every decision +in this spec that needs realization.* The integrity audit's first lens +verifies a document's declared rules in both directions, which gives +the register's completeness its reader. + +A design spec without the field is legacy: the propagation audit +reports it as not checked and raises no hit, and a plan descending from +legacy specs alone carries no annotations. An existing spec migrates +when it is next substantively revised, never in a sweep. Every new +design spec is expected to set the field; its absence is reported, not +refused. + +## Finding what revises a document +``` + +- [ ] **Step 5: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 1`, `G 1`, `H 1`. + +- [ ] **Step 6: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): decision register in the lifecycle rule" +``` + +--- + +### Task 2: Lifecycle rule — the plan annotations + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md` — the + last sentence of the `**Interfaces:**` bullet, and a new section after + `## Decision register`. + +**Interfaces:** +- Consumes: the `## Decision register` section from Task 1, which the + new section follows and cites. +- Produces: the section heading `## Plan annotations`, which Task 5's + duty 10 names; the line shapes `**Realizes:**`, `**Defers:**`, + `**Follows:**` and the heading `## Deferrals and predecessors`. + +**Realizes:** D7, D8, D18, D19, D25, D30 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(grep -c '^## Plan annotations$' $F)" +echo "B $(n $F | grep -oF 'changes no plan template — the template belongs to the tool that writes plans.' | wc -l)" +echo "C $(n $F | grep -oF 'with one exception, defined under *Plan annotations* below' | wc -l)" +echo "D $(grep -c '^ \*\*Realizes:\*\* none$' $F)" +echo "E $(grep -c '^ \*\*Defers:\*\* D6 — ; ruling: $' $F)" +echo "F $(grep -c '^ \*\*Follows:\*\* \.\./plans/\.md$' $F)" +echo "G $(n $F | grep -oF 'so inheritance is not transitive' | wc -l)" +echo "H $(n $F | grep -oF '`../specs/.md#D3`' | wc -l)" +echo "I $(n $F | grep -oF 'names this rule and requires reading it first' | wc -l)" +``` + +Expected: `A 0`, `B 1`, `C 0`, `D 0`, `E 0`, `F 0`, `G 0`, `H 0`, `I 0`. + +- [ ] **Step 2: Revise the template sentence** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` + convention stands unchanged. This binds how the blocks are filled and + changes no plan template — the template belongs to the tool that + writes plans. +``` + +Replace with: + +``` + convention stands unchanged. This binds how the blocks are filled and + changes no plan template. The template belongs to the tool that + writes plans, with one exception, defined under *Plan annotations* + below: this rule adds the `**Realizes:**` annotation on tasks and + Global Constraints entries, and the `## Deferrals and predecessors` + section with its `**Defers:**` and `**Follows:**` lines. The rest of + the template stays with that tool. +``` + +- [ ] **Step 3: Add the section** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +## Finding what revises a document +``` + +Replace with: + +``` +## Plan annotations + +A plan whose `spec:` names at least one registered design spec carries, +on every task, one line after its `**Files:**` block — after its +`**Interfaces:**` block where it has one — and before its first step: + + **Realizes:** D3, D5 + **Realizes:** none + +`none` is a value, not an omission: without it a housekeeping task +cannot be told from an author who forgot. One decision may be cited by +several tasks, and a task split or merged in a fix wave stays covered as +long as the union of its identifiers survives. A cross-cutting +constraint is realized by the Global Constraints entry that states it, +and that entry opens with the same annotation as its first clause — +`**Realizes:** D9`. Only an entry that realizes a decision carries one, +and `none` is never written there. Citing the section as a whole +realizes nothing, and neither does citing a group: its leaves are what +a plan cites. + +A plan whose `spec:` names one spec writes bare identifiers. A plan +whose `spec:` names two or more — registered or legacy alike — qualifies +every identifier with its spec's path exactly as `spec:` writes it, +`../specs/.md#D3`, wherever the plan or its audit names one. The +count of entries decides, never the count of registered ones, so +migrating a second spec out of legacy changes no annotation already +written. + +Two more kinds of line form a section of their own, headed +`## Deferrals and predecessors` at the Global Constraints heading's +level and placed directly after that section. They state facts about +the whole plan rather than requirements of any task, so they stay out of +Global Constraints, which the plan template makes part of every task's +requirements: + + **Defers:** D6 — ; ruling: + **Follows:** ../plans/.md + +A `**Defers:**` line records that this plan leaves a standing decision +unrealized, one line per identifier, qualified as the annotations are. +The session writes it only on the developer's explicit decision, which +its `ruling:` dates. The deferral binds this plan alone: auditing any +other plan of the same spec, the decision counts, and the spec names no +plan. Deferring an undefined, withdrawn or group identifier is +malformed, and so is deferring one the plan also cites, locally or by +inheritance. + +A `**Follows:**` line names a plan this one continues, by a path +relative to the plan. The predecessor shares at least one spec with +this plan's `spec:` and carries `status: implemented`, since only a +frozen body's citations cannot move. This plan inherits every +identifier the predecessor's `**Realizes:**` annotations cite for the +specs both plans name, and nothing else — never its `**Defers:**` +lines, and never its `**Follows:**` lines, so inheritance is not +transitive and a plan names every predecessor whose citations it relies +on. An inherited citation of an identifier the register withdrew after +the predecessor shipped covers nothing and raises no hit; a local +citation of it still does. Whether the predecessor's realization still +stands in the code is not this line's question: the diff the +implemented-document bullet above prescribes answers it. + +The plan's author writes these lines, whoever that author is. A session +writing a plan meets this rule by reading the design spec, and a brief +that delegates plan-writing to a separate context names this rule and +requires reading it first, as the workflow rule's step 4 says. A plan +lacking an annotation is caught at its first propagation gate, and an +identifier is added there only where the task's text actually realizes +the decision, never mechanically. + +## Finding what revises a document +``` + +- [ ] **Step 4: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 0`, `C 1`, `D 1`, `E 1`, `F 1`, `G 1`, `H 1`, `I 1`. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): plan annotations for decision coverage" +``` + +--- + +### Task 3: Lifecycle rule — the held gate line + +**Files:** +- Modify: `plugins/working-process/rules/spec-plan-lifecycle.md` — the + non-terminal sentence and the cross-document fix heading paragraph in + `## The disposition ledger`; `### Gate lines`; the *Unfinished + review-loop ledger* entry. + +**Interfaces:** +- Consumes: the `**Defers:**` line and the `withdrawn` token from Tasks 1 + and 2, which two terminal shapes name. +- Produces: the `hit held` line and its three terminal shapes, which + Task 5's card and Task 6's workflow exception point at; the widened + command `rg -n --no-ignore --crlf '^- (open|held) —|^- hit held ' docs/`. + +**Realizes:** D13.1, D13.2, D23, D28.4, D29 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/rules/spec-plan-lifecycle.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(grep -c '^ - hit held — ' $F)" +echo "B $(grep -c '^ - hit fixed — ; ; ruling: $' $F)" +echo "C $(grep -c '^ - hit deferred — ' $F)" +echo "D $(grep -c '^ - hit withdrawn — ' $F)" +echo "E $(grep -cF "rg -n --no-ignore --crlf '^- (open|held) —|^- hit held ' docs/" $F)" +echo "F $(grep -cF "rg -n --no-ignore --crlf '^- (open|held) —' docs/" $F)" +echo "G $(n $F | grep -oF 'Neither shape covers' | wc -l)" +echo "H $(n $F | grep -oF 'No gate-line shape covers' | wc -l)" +echo "I $(n $F | grep -oF 'the only two disposition states the Unfinished-work list anchors' | wc -l)" +echo "J $(n $F | grep -oF 'every later pre-round episode writes there too' | wc -l)" +echo "K $(n $F | grep -oF 'The same heading serves a register edit' | wc -l)" +echo "L $(n $F | grep -oF 'The ordinary `hit fixed` shape, without `ruling:`, stays' | wc -l)" +``` + +Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 1`, `G 1`, `H 0`, `I 0`, +`J 0`, `K 0`, `L 0`. + +- [ ] **Step 2: Amend the non-terminal sentence** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +`open` and `held` are the non-terminal states, and the only two the +Unfinished-work list anchors. `fixed` and `declined` are terminal and +say what became of the document: it changed, or it stands. +``` + +Replace with: + +``` +`open` and `held` are the non-terminal states, and the only two +disposition states the Unfinished-work list anchors; the one gate line +it anchors, `hit held`, is defined under *Gate lines* below. `fixed` +and `declined` are terminal and say what became of the document: it +changed, or it stands. +``` + +- [ ] **Step 3: Let the fix heading serve a gate-licensed fix** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +round heading: it records a fix, not a round. Two stamps go stale as on +any body edit — the `integrity:` hash stops matching, and the verdict +stops certifying the words that changed. +``` + +Replace with: + +``` +round heading: it records a fix, not a round. Two stamps go stale as on +any body edit — the `integrity:` hash stops matching, and the verdict +stops certifying the words that changed. The same heading serves a +register edit that a plan's held gate line licensed, on a design spec +whose loop has closed at whatever verdict: the heading then names the +plan whose gate held the hit, and that plan's terminal gate line names +the heading and the line under it. +``` + +- [ ] **Step 4: Add the held shape to *Gate lines*** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +Neither carries a license either, because a hit's fix is licensed by its +own derivation. +``` + +Replace with: + +``` +Neither carries a license either, because a hit's fix is licensed by its +own derivation. + +A hit of the coverage duty whose fix needs a decision no derivation +settles is held for the developer — the one exception the workflow rule +makes to hits never waiting. It takes a third shape, rewritten in place +to one of three terminal shapes, so its state always has one home: + + - hit held — ; question: ; options: + - hit fixed — ; ; ruling: + - hit deferred — ; ruling: ; Defers: + - hit withdrawn — ; ruling: ; # + +The rewrite happens once the gate has re-run over the changed document +and whatever depends on it, never on the developer's answer alone. +`hit fixed … ruling:` records that the document now carries the +decision the developer settled — a plan that realizes it, or a register +the ruling repaired; `hit deferred` records an approved `**Defers:**` +line in the plan, and `hit withdrawn` a register entry now a tombstone. +Each is written only where it names what happened, and none is +`hit dismissed`, because the gap was real when the hit fired. The +ordinary `hit fixed` shape, without `ruling:`, stays for every hit +whose fix its derivation licensed. `ruling:` on a gate line means what +it means on a disposition line, and a diff-scoped round treats the line +as settled. + +A `hit held` line lives in the ledger of the document whose gate raised +it — the audited plan, or the design spec audited alone. Its leading +date is the gate's, and the terminal rewrite replaces it with the date +the line reached its terminal state. A held hit ends its gate episode +and blocks the dispatch the gate guards; the re-run after the +developer's answer is a fresh episode. A register edit the answer +causes is recorded in the spec's own ledger — under its latest round +heading while its loop is open, and under the fix heading above once +its loop has closed. A spec already `implemented` takes the edit by +revision instead, and the held hit waits for it. +``` + +- [ ] **Step 5: Place a holding pre-round episode's lines** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +diff-scoped brief is composed before its own round is stamped. A gate +before a document's first round has no heading to write under; its lines +wait for that round and are written at its stamp, the one case where +they do. +``` + +Replace with: + +``` +diff-scoped brief is composed before its own round is stamped. A gate +before a document's first round has no heading to write under; its lines +wait for that round and are written at its stamp, the one case where +they do — unless the episode holds a hit, since the round its lines +would wait for cannot start. A pre-round episode holding at least one +hit writes every one of its lines, the `hit held` line and its siblings +alike, in the `## Review rounds` section before the first round +heading, and they stay there, rewrites included: no round produced +them, and one episode keeps one home. Once such lines stand there, +every later pre-round episode writes there too, holding or not, so the +document's pre-round gate history keeps one place. A plan with no +`## Review rounds` section gains one, at its end, with its first gate +line. +``` + +- [ ] **Step 6: Reword the outstanding-hit gap and the anchors sentence** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +Neither shape covers a hit left outstanding when the re-dispatch bound +``` + +Replace with: + +``` +No gate-line shape covers a hit left outstanding when the re-dispatch bound +``` + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +rather than silence. Neither joins the unfinished-work anchors: both are +closed when written and owe nobody a next move. +``` + +Replace with: + +``` +rather than silence. Neither joins the unfinished-work anchors: both are +closed when written and owe nobody a next move. A `hit held` line owes +the developer an answer, so it joins them until its terminal rewrite. +``` + +The first replacement runs one word past the wrap; Step 7 rewraps it. + +- [ ] **Step 7: Rewrap the outstanding-hit paragraph** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +No gate-line shape covers a hit left outstanding when the re-dispatch bound +in the workflow rule stops an episode. That state owes the developer a +decision, so neither `hit fixed` nor `hit dismissed` can honestly carry +it, and no anchor surfaces it today — a stated gap, not an oversight. +Until a shape exists, the workflow rule's report is its only record. +``` + +Replace with: + +``` +No gate-line shape covers a hit left outstanding when the re-dispatch +bound in the workflow rule stops an episode. That state owes the +developer a decision, so neither `hit fixed` nor `hit dismissed` can +honestly carry it, and `hit held` is scoped to the coverage duty's +hits; no anchor surfaces it today — a stated gap, not an oversight. +Until a shape exists, the workflow rule's report is its only record. +``` + +- [ ] **Step 8: Widen the Unfinished-work entry** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +- **Unfinished review-loop ledger** — a disposition line nobody closed: + an `open` line whose remediation never ran, or a `held` line whose + question still waits. + `rg -n --no-ignore --crlf '^- (open|held) —' docs/` +``` + +Replace with: + +``` +- **Unfinished review-loop ledger** — a disposition line or a held gate + line nobody closed: an `open` line whose remediation never ran, or a + `held` or `hit held` line whose question still waits. A gate line's + date stands between its token and its dash, so the command takes an + alternative rather than a third token inside the group. + `rg -n --no-ignore --crlf '^- (open|held) —|^- hit held ' docs/` +``` + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` + Owner: an `open` line belongs to the document's next touch, which + re-offers the remediation; a `held` line belongs to the developer. +``` + +Replace with: + +``` + Owner: an `open` line belongs to the document's next touch, which + re-offers the remediation; a `held` or `hit held` line belongs to the + developer. +``` + +- [ ] **Step 9: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 0`, `G 0`, `H 1`, `I 1`, +`J 1`, `K 1`, `L 1`. + +Then run the widened command over a fixture, to prove it matches both a +disposition line and a held gate line and nothing terminal: + +```bash +d=$(mktemp -d); mkdir -p "$d/docs" +printf '%s\n' '## Review rounds' '- held — [Minor] x; question: q; options: o' \ + '- hit held 2026-09-26 — D4 uncovered; question: q; options: o' \ + '- hit fixed 2026-09-26 — D4 uncovered; task 7 added; ruling: 2026-09-26' \ + '- hit deferred 2026-09-26 — D4 uncovered; ruling: 2026-09-26; Defers: D4' > "$d/docs/p.md" +(cd "$d" && rg -n --no-ignore --crlf '^- (open|held) —|^- hit held ' docs/ | wc -l) +rm -rf "$d" +``` + +Expected: `2`. + +- [ ] **Step 10: Commit** + +```bash +git add plugins/working-process/rules/spec-plan-lifecycle.md +git commit -m "feat(working-process): held gate line for coverage hits" +``` + +--- + +### Task 4: Technical-design rule — the declared table relations + +**Files:** +- Modify: `plugins/working-process/rules/technical-design.md` — a new + section at the end of the file. + +**Interfaces:** +- Consumes: nothing from earlier tasks. +- Produces: the heading `## Declared table relations`, which Task 5's + duty 11 names and nothing repeats. + +**Realizes:** D10, D21 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/rules/technical-design.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(grep -c '^## Declared table relations$' $F)" +echo "B $(grep -c '^| State `written by` | Parts `part` |$' $F)" +echo "C $(grep -c '^| State `read by` | Parts `part` |$' $F)" +echo "D $(grep -c '^| Contracts `producer → consumer`, each side | Parts `part` |$' $F)" +echo "E $(n $F | grep -oF '`external: `' | wc -l)" +echo "F $(n $F | grep -oF 'is outside the relation' | wc -l)" +echo "G $(tail -n 1 $F)" +``` + +Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 0`, +`G responsibility, and a contract its implementation can change behind.` + +- [ ] **Step 2: Add the section** + +Find in `plugins/working-process/rules/technical-design.md`: + +``` +names must still have a resolvable identity, an explicit +responsibility, and a contract its implementation can change behind. +``` + +Replace with: + +``` +names must still have a resolvable identity, an explicit +responsibility, and a contract its implementation can change behind. + +## Declared table relations + +Where a column names rows of another table, the relation is declared +here, and this section is its only home. The propagation audit, when +that agent is available, resolves every declared relation and checks +nothing a declaration does not name: matching cell values would guess a +relation rather than read one. + +| column | names rows of | +|---|---| +| Contracts `producer → consumer`, each side | Parts `part` | +| State `written by` | Parts `part` | +| State `read by` | Parts `part` | + +A cell may name several rows, comma-separated, and +`producer → consumer` splits at the arrow before it splits at commas. A +party outside the system is written `external: ` and is no +reference. No side of these three relations may be empty: the +definitions above give every contract a consumer and every state record +a writer and a reader, so an empty cell, or one naming nothing, fails +the relation. A later declaration that admits an empty side says so +itself. Every other name resolves to exactly one row of Parts, because +a duplicated part name makes every reference to it ambiguous. + +A Contracts flow written as a numbered sequence rather than a table row +is outside the relation: the skeleton gives the sequence no grammar a +parse could split, so its steps are not references. +``` + +- [ ] **Step 3: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 1`, +`G parse could split, so its steps are not references.` + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/rules/technical-design.md +git commit -m "feat(working-process): declared table relations in the technical-design rule" +``` + +--- + +### Task 5: Propagation auditor — duties 10 and 11 and the report + +**Files:** +- Modify: `plugins/working-process/agents/propagation-auditor.md` — the + `description:` line, two new duties after duty 9, `## Output`, + `## What becomes of your hits`, `## Out of bounds`. + +**Interfaces:** +- Consumes: the lifecycle headings `## Decision register` (Task 1) and + `## Plan annotations` (Task 2); the technical-design heading + `## Declared table relations` (Task 4); the `hit held` line (Task 3). +- Produces: duty numbers 10 and 11, which Task 9's rows cite; the report + lines `decision-coverage:`, `withdrawn since:` and `table-closure:`, + which Task 6's closing-token paragraph names. + +**Realizes:** D9, D10, D11.1, D11.2, D11.3, D11.4, D12, D17, D22, D26, D27, D30 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/agents/propagation-auditor.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(grep -c '^### 10\. Decision coverage' $F)" +echo "B $(grep -c '^### 11\. Table closure' $F)" +echo "C $(n $F | grep -oF 'exactly two lines' | wc -l)" +echo "D $(n $F | grep -oF 'or the single line CLEAN' | wc -l)" +echo "E $(grep -c '^ decision-coverage: ' $F)" +echo "F $(grep -c '^ table-closure: ' $F)" +echo "G $(grep -c '^ withdrawn since: ' $F)" +echo "H $(n $F | grep -oF '*Decision register* and *Plan annotations*' | wc -l)" +echo "I $(n $F | grep -oF '`## Declared table relations`' | wc -l)" +echo "J $(n $F | grep -oF 'is held for the developer' | wc -l)" +echo "K $(n $F | grep -oF 'means no hit in the checks that ran' | wc -l)" +echo "L $(awk '/^### /{c++} END{print c}' $F)" +echo "M $(head -n 5 $F | grep -c '^description: "')" +``` + +Expected: `A 0`, `B 0`, `C 1`, `D 1`, `E 0`, `F 0`, `G 0`, `H 0`, `I 0`, +`J 0`, `K 0`, `L 9`, `M 1`. + +- [ ] **Step 2: Rewrite the description** + +Find in `plugins/working-process/agents/propagation-auditor.md`: + +``` +re-derives every counter, and returns located hits with their derivation — or the single line CLEAN. +``` + +Replace with: + +``` +re-derives every counter, derives a plan's decision coverage from its design specs' decision registers, resolves the table relations the technical-design rule declares, and returns located hits with their derivation — or, where none fires, the token CLEAN after the report's decision-coverage and table-closure lines. +``` + +The anchor is the tail of the one-line `description:` value, so the +replacement keeps the line's closing text — `Verdict-free and +persona-free: …` — untouched after it. The value stays one quoted line. + +- [ ] **Step 3: Add duties 10 and 11** + +Find in `plugins/working-process/agents/propagation-auditor.md`: + +``` +is found rather than skipped. A plan whose `spec:` is not named back is +not a hit: no design spec names its plans. +``` + +Replace with: + +``` +is found rather than skipped. A plan whose `spec:` is not named back is +not a hit: no design spec names its plans. + +### 10. Decision coverage — every registered decision has an owner + +Runs on a plan, and on a design spec audited alone. The register's +grammar, its state token and the plan's annotations are defined in the +spec-plan-lifecycle rule, under *Decision register* and *Plan +annotations*; read them there rather than recalling them. For each +spec the plan's `spec:` names: + +1. Read the spec's `decisions:` field. Absent: the spec is legacy — + write `not checked` for it and stop. Any value other than + `registered`: a hit, `not counted`, stop. +2. Check that the register is well formed. Each of these is a hit: the + field set with no `## Decisions` section; an identity paragraph that + does not parse, including a child nested under a parent other than + its own, a child whose parent does not exist, and a register written + as a numbered list; a duplicate identifier; a segment opening with + `withdrawn` that does not match the token's grammar; a group + carrying a state token; a `replaced by` naming a missing identifier, + its own, or a group, or closing a cycle. Where any fires, the counted + set cannot be trusted: write `not counted` and stop. +3. Collect the counted set: the leaf identifiers not withdrawn, less + those the plan's `**Defers:**` lines name. +4. Collect the cited set: every identifier the plan's `**Realizes:**` + annotations cite, on tasks and Global Constraints entries, and every + identifier inherited through its `**Follows:**` lines, each with the + plan and site it comes from. A cited withdrawn identifier, a cited + group and a cited identifier the register does not define are hits. + The last is an error in the plan, never a gap in the spec — + identifiers are minted only in the register — so duty 5 does not + take it. An inherited citation of an identifier withdrawn since its + predecessor shipped is skipped: not counted, not a hit, and listed + apart in the report. +5. Check the plan's other lines. A `**Defers:**` line naming an + undefined, withdrawn or group identifier, or one also in the cited + set, is a hit. A `**Follows:**` line naming a missing file, a plan + sharing no spec with the audited plan, or a plan not at + `status: implemented` is a hit. A predecessor lends identifiers only + for the specs both plans name; in the pass for a spec it does not + name, its line is out of scope rather than a hit. +6. Report every counted identifier the cited set lacks, one hit per + identifier. A task with no `**Realizes:**` line, in a plan whose + `spec:` names a registered spec, is a hit too, and so is a bare + identifier in a plan whose `spec:` names two or more specs. + +Derive the map, never tally it: write each counted identifier with the +sites citing it, and let the fraction summarise the map, since a bare +count passes one omission offset by one duplicate. An identifier cited +by several tasks is legal. + +On a design spec audited alone — the gate before its architect round — +run steps 1 and 2 only, so a malformed register is found before any plan +depends on it. + +This duty proves that every declared decision has an owner. Whether the +register lists every decision the spec makes is the integrity auditor's +judgment, and whether the citing tasks realize their decision is the +plan-adversary's. Measured in an outside project: a plan realized six of +a spec's eight table rows, and two adversary rounds, three propagation +audits and the author's self-review passed it, because none carried +"every decision has an owning task" in its brief. + +### 11. Table closure — a column naming another table's rows resolves + +Runs on a technical design. Check only the relations the rule defining +the tables declares, never relations guessed from matching values: two +unrelated columns can share names by chance, and a relation whose every +reference is wrong would match nothing. The technical-design rule +declares them under its heading `## Declared table relations`, with how +each cell splits and which values are not references; read them there. +For each declared relation whose tables the document carries, every +name in the column resolves to exactly one row of the named table: a +name matching no row is a hit, and so is a name matching several. An +empty cell is a hit wherever the declaration gives the column no empty +side. A relation whose table the document does not carry is not +applicable and is not counted, and a table no declaration names is +outside this duty. Measured on 2026-09-16: walked duty by duty, none of +the first nine checked referential integrity between two tables of one +document. +``` + +- [ ] **Step 4: Rewrite the output contract** + +Find in `plugins/working-process/agents/propagation-auditor.md`: + +``` +A clean audit runs to exactly two lines: the self-report above, then the +literal token. + + CLEAN + +Add nothing after the token. The self-report opens every report, and a +clean run is the case where that is easiest to forget. + +Otherwise, one entry per hit: + + — — derivation: +``` + +Replace with: + +``` +Then one entry per hit, if any fired: + + — — derivation: + +Then the lines duties 10 and 11 write on every run, clean ones included. +On a plan, one `decision-coverage:` block per spec its `spec:` names: + + decision-coverage: 7/8 covered; inherited [D2]; deferred [D6]; uncovered [D4.2] + D1 → Task 2 + D2 → ../plans/.md (Task 3) + D3 → Task 4, Task 6 + D4.2 → — + D9 → Global Constraints: "No code" + D10 → ../plans/.md (Global Constraints: "No code") + … + withdrawn since: D4 ← ../plans/.md (Task 2) + decision-coverage: not counted — malformed register + decision-coverage: not checked — no decision register + +The summary line opens the block. Under a counted spec the map follows, +one indented line per counted identifier naming every task and +constraint that cites it: a task by its heading's number, a Global +Constraints entry by its opening words in quotes, an inherited site by +its plan's path with the site in parentheses. An uncovered identifier +maps to `—` and is a hit above as well. The fraction's denominator is +duty 10's counted set, and its numerator the counted identifiers in the +cited set. `inherited [..]` lists the covered identifiers whose only +citation is a predecessor's; one also cited locally counts as local, +and its map line names both sites. `deferred [..]` lists what the plan +defers, never counted as covered. A skipped inherited citation follows +the map on a `withdrawn since:` line, outside the fraction. Qualify +every identifier as the plan's annotations are. `not counted` carries no +map, since its denominator cannot be derived. + +On a design spec audited alone, one line and no map: + + decision-coverage: register well formed + decision-coverage: not counted — malformed register + decision-coverage: not checked — no decision register + +On a technical design, one line, counting the declared relations the +duty resolved in that document; no other document gets one: + + table-closure: 3 relations checked + table-closure: no declared relations + +Where no hit fired, close with the literal token: + + CLEAN + +`CLEAN` means no hit in the checks that ran, and a `not checked` line +bounds that guarantee in the report itself. A clean report is the +self-report, the lines above that apply to the document, and the token, +with nothing after it. The self-report opens every report, and a clean +run is the case where that is easiest to forget. A report carrying a hit +carries no `CLEAN`. +``` + +- [ ] **Step 5: State the held-hit exception** + +Find in `plugins/working-process/agents/propagation-auditor.md`: + +``` +The dispatcher confirms or dismisses each hit. A confirmed hit's fix is +licensed by the derivation itself — a recounted counter and an enumerated +missed consumer decide themselves — so hits never wait for the developer, +and a hit the dispatching session believes is wrong reaches the developer +rather than a silent dismissal. Write each derivation to stand alone: the +dispatcher acts on it, never on your confidence. +``` + +Replace with: + +``` +The dispatcher confirms or dismisses each hit. A confirmed hit's fix is +licensed by the derivation itself — a recounted counter and an enumerated +missed consumer decide themselves — so hits never wait for the developer, +with one exception: a hit of duty 10 whose fix needs a decision no +derivation settles, such as the task an uncovered decision needs where +the decision leaves a choice open, is held for the developer. A hit the +dispatching session believes is wrong reaches the developer rather than +a silent dismissal. Write each derivation to stand alone: the dispatcher +acts on it, never on your confidence. +``` + +- [ ] **Step 6: Add the out-of-bounds bullet** + +Find in `plugins/working-process/agents/propagation-auditor.md`: + +``` +- Prose quality, naming taste, and style. +``` + +Replace with: + +``` +- Judging whether a task realizes the decision it cites, or whether the + register lists every decision its spec makes — the plan-adversary's + and the integrity auditor's. +- Prose quality, naming taste, and style. +``` + +- [ ] **Step 7: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 1`, `C 0`, `D 0`, `E 6`, `F 2`, `G 1`, `H 1`, `I 1`, +`J 1`, `K 1`, `L 11`, `M 1`. + +`E 6` is the three summary lines of a plan's block plus the three of a +spec audited alone; the map lines are indented six spaces and do not +match. `L 11` is nine duties plus two. + +- [ ] **Step 8: Commit** + +```bash +git add plugins/working-process/agents/propagation-auditor.md +git commit -m "feat(working-process): decision coverage and table closure duties" +``` + +--- + +### Task 6: Workflow rule — the held coverage hit, the report lines, the delegating brief + +**Files:** +- Modify: `plugins/working-process/rules/workflow.md` — step 4 of the + flow, and the first three paragraphs of `### The propagation gate`. + +**Interfaces:** +- Consumes: the `hit held` line (Task 3), the report lines (Task 5), + the `## Plan annotations` section (Task 2), which the brief sentence + points at by rule name. +- Produces: nothing a later task reads. + +**Realizes:** D11.4, D12, D20, D26 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/rules/workflow.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(n $F | grep -oF 'so hits never wait for the developer.' | wc -l)" +echo "B $(n $F | grep -oF 'so hits never wait for the developer, with one named exception' | wc -l)" +echo "C $(n $F | grep -oF 'No other duty' | wc -l)" +echo "D $(n $F | grep -oF 'A hit is a report, not a question' | wc -l)" +echo "E $(n $F | grep -oF 'A dismissed hit is a report, not a question' | wc -l)" +echo "F $(n $F | grep -oF 'A `decision-coverage:` or `table-closure:` line is neither a hit' | wc -l)" +echo "G $(n $F | grep -oF 'A brief that delegates plan-writing to a separate context' | wc -l)" +``` + +Expected: `A 1`, `B 0`, `C 0`, `D 1`, `E 0`, `F 0`, `G 0`. + +- [ ] **Step 2: Name the delegating brief's duty in step 4** + +Find in `plugins/working-process/rules/workflow.md`: + +``` + Then write the + implementation plan with superpowers:writing-plans when available; + plans live in `docs/plans/`. +``` + +Replace with: + +``` + Then write the + implementation plan with superpowers:writing-plans when available; + plans live in `docs/plans/`. A brief that delegates plan-writing to a + separate context names the spec-plan-lifecycle rule and requires + reading it before the plan is written: that rule carries the + annotations a plan owes a registered design spec, and a separate + context cannot count on it loading. +``` + +- [ ] **Step 3: Add the coverage exception to the gate** + +Find in `plugins/working-process/rules/workflow.md`: + +``` +an integrity audit. A hit's fix is licensed by its own derivation — a +recounted counter and an enumerated missed call site decide +themselves — so hits never wait for the developer. +``` + +Replace with: + +``` +an integrity audit. A hit's fix is licensed by its own derivation — a +recounted counter and an enumerated missed call site decide +themselves — so hits never wait for the developer, with one named +exception, for the coverage duty's hits alone. + +The two coverage hits — a registered decision no task cites, and a task +lacking its `**Realizes:**` line — are triaged by license. Where a task +already does the work and lacks only its annotation, the task's text +licenses adding the identifier. Where no task realizes the decision and +the decision's text settles every choice the missing task requires, the +decision licenses writing it, and the next round's brief names it among +the previous wave's fixes. Where writing the task needs a choice the +decision does not make, the hit is held: its question is that missing +decision, not the gap, which is already established. Any other hit of +the coverage duty is fixed where its derivation settles the fix and held +where it does not, as when breaking a `replaced by` cycle means choosing +which entry stands. No other duty's hit is ever held. + +A held hit ends its gate episode and blocks the dispatch the gate +guards, as a held finding blocks a fresh round, and the session puts its +question to the developer. The answer lands where the decision belongs — +in the spec's register, or as the plan's `**Defers:**` line — and the +gate re-runs over the changed document and whatever depends on it, as a +fresh episode; the dispatch goes out once that episode passes. The +spec-plan-lifecycle rule defines the `hit held` line and its terminal +shapes. +``` + +- [ ] **Step 4: Admit the report's non-hit lines** + +Find in `plugins/working-process/rules/workflow.md`: + +``` +belongs to the dispatcher who reads. The rule is stated here rather +than generalised, because `CLEAN` is this agent's token and no other +report carries one. +``` + +Replace with: + +``` +belongs to the dispatcher who reads. The rule is stated here rather +than generalised, because `CLEAN` is this agent's token and no other +report carries one. A `decision-coverage:` or `table-closure:` line is +neither a hit nor a breach of the token's position: the card writes +those lines on every run, clean ones included, between the hits and the +token. +``` + +- [ ] **Step 5: Narrow the report-not-question sentence** + +Find in `plugins/working-process/rules/workflow.md`: + +``` +developer. A hit is a report, not a question, so it never enters the +held batch and never spends the round's one interruption — the +developer reads the dismissal and keeps their standing veto over it. +``` + +Replace with: + +``` +developer. A dismissed hit is a report, not a question, so it never +enters the held batch and never spends the round's one interruption — +the developer reads the dismissal and keeps their standing veto over it. +``` + +- [ ] **Step 6: Verify** + +Run the Step 1 command again. + +Expected: `A 0`, `B 1`, `C 1`, `D 0`, `E 1`, `F 1`, `G 1`. + +- [ ] **Step 7: Commit** + +```bash +git add plugins/working-process/rules/workflow.md +git commit -m "feat(working-process): held coverage hits in the propagation gate" +``` + +--- + +### Task 7: Plan-adversary — dimension 6, realization of registered decisions + +**Files:** +- Modify: `plugins/working-process/agents/plan-adversary.md` — a new + dimension after dimension 5, before `## Output`. + +**Interfaces:** +- Consumes: nothing from earlier tasks at runtime; the annotations it + reads are those Task 2 defines. +- Produces: nothing a later task reads. + +**Realizes:** D14 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/agents/plan-adversary.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(grep -c '^### 6\. Realization of registered decisions$' $F)" +echo "B $(n $F | grep -oF 'realize it in full together' | wc -l)" +echo "C $(awk '/^### 5\./{f=1;print;next} f&&/^#/{exit} f' $F | shasum | cut -c1-7)" +``` + +Expected: `A 0`, `B 0`, `C c518b54`. + +- [ ] **Step 2: Add the dimension** + +Find in `plugins/working-process/agents/plan-adversary.md`: + +``` +named, the existing plan convention stands and this paragraph is silent. + +## Output +``` + +Replace with: + +``` +named, the existing plan convention stands and this paragraph is silent. + +### 6. Realization of registered decisions + +Where a design spec the plan descends from sets `decisions: registered`, +read its decision register and the plan's `**Realizes:**` annotations in +the plan itself. The propagation audit has already established that +every counted decision is cited, and its report reaches the dispatcher, +not you. Judge each decision, never each site: one decision may span +several tasks and Global Constraints entries, so ask whether all the +sites citing it realize it in full together, and whether a cited +constraint actually binds the work it constrains. A decision realized +only in part — six rows of eight — is an Important finding whose +`origin` names `implementation-plan`. A deferral the developer ruled on +a `**Defers:**` line is not judged; a task that quietly depends on the +deferred decision anyway is a finding. Decisions inherited through a +`**Follows:**` line are taken as settled, since you read plans rather +than the code that realized them; a task that changes or undoes what an +inherited decision required is a finding against this plan. + +## Output +``` + +- [ ] **Step 3: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 1`, `C c518b54` — dimension 5 is unchanged +(D28.2). The awk range stops at the next line +opening with `#`, which is dimension 6's heading after this task and +`## Output` before it. + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/agents/plan-adversary.md +git commit -m "feat(working-process): plan-adversary judges realization of registered decisions" +``` + +--- + +### Task 8: Integrity auditor — the register as a declared rule + +**Files:** +- Modify: `plugins/working-process/agents/integrity-auditor.md` — the + last bullet of lens 1. + +**Interfaces:** +- Consumes: nothing from earlier tasks. +- Produces: nothing a later task reads. + +**Realizes:** D15 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/agents/integrity-auditor.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(n $F | grep -oF 'declares one such rule' | wc -l)" +echo "B $(grep -c '^### ' $F)" +``` + +Expected: `A 0`, `B 2`. + +- [ ] **Step 2: Add the sentence** + +Find in `plugins/working-process/agents/integrity-auditor.md`: + +``` + field, and field by field for an entry the rule never claimed — the + reverse direction is where the survivors hide. +``` + +Replace with: + +``` + field, and field by field for an entry the rule never claimed — the + reverse direction is where the survivors hide. A design spec carrying + `decisions: registered` declares one such rule: its decision register + lists every decision in the spec that needs realization. Check each + entry against the text that argues it, and each decision the text + makes against the register; a decision left only in a paragraph is a + defect, and a rejected alternative without an entry is none. +``` + +- [ ] **Step 3: Verify** + +Run the Step 1 command again. + +Expected: `A 1`, `B 2` — the two lenses, since the task adds none. + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/agents/integrity-auditor.md +git commit -m "feat(working-process): integrity auditor reads the decision register as a declared rule" +``` + +--- + +### Task 9: Propagation duties checklist — rows, anchor case, counters + +**Files:** +- Modify: `plugins/working-process/rules/propagation-duties.md` — the + opening paragraph, the second paragraph, the duty-2 row, and two new + rows at the table's end. + +**Interfaces:** +- Consumes: duty numbers 10 and 11 from Task 5. +- Produces: nothing a later task reads. + +**Realizes:** D16.1, D16.2, D16.3 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/rules/propagation-duties.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(n $F | grep -oF 'nine duties fall due, keyed by the ten edits' | wc -l)" +echo "B $(n $F | grep -oF 'eleven duties fall due, keyed by the twelve edits' | wc -l)" +echo "C $(n $F | grep -oF 'walks the same nine as a gate' | wc -l)" +echo "D $(n $F | grep -oF 'walks the same eleven as a gate' | wc -l)" +echo "E $(n $F | grep -oF 'one an earlier task in the same document has already rewritten' | wc -l)" +echo "F $(grep -c '^| .* | 10 |$' $F)" +echo "G $(grep -c '^| .* | 11 |$' $F)" +echo "H $(grep -c '^| [a-z]' $F)" +``` + +Expected: `A 1`, `B 0`, `C 1`, `D 0`, `E 0`, `F 0`, `G 0`, `H 10`. + +`H` counts the table's body rows — one per edit, each opening with a +lower-case verb; the header row opens with `You` and the separator with +`---`, so neither matches. + +- [ ] **Step 2: Recount the opening paragraph** + +Find in `plugins/working-process/rules/propagation-duties.md`: + +``` +Before a document goes to an expensive reader, nine duties fall due, +keyed by the ten edits that trigger them — one duty answers two +``` + +Replace with: + +``` +Before a document goes to an expensive reader, eleven duties fall due, +keyed by the twelve edits that trigger them — one duty answers two +``` + +- [ ] **Step 3: Recount the second paragraph** + +Find in `plugins/working-process/rules/propagation-duties.md`: + +``` +When the `propagation-auditor` agent is available it walks the same +nine as a gate, and its card is their definition and keeps the +``` + +Replace with: + +``` +When the `propagation-auditor` agent is available it walks the same +eleven as a gate, and its card is their definition and keeps the +``` + +- [ ] **Step 4: Add the anchor case to the duty-2 row** + +Find in `plugins/working-process/rules/propagation-duties.md`: + +``` +| prescribed a verbatim block | the anchor it targets — the text it replaces must exist in that file byte-exactly, and once | 2 | +``` + +Replace with: + +``` +| prescribed a verbatim block | the anchor it targets — the text it replaces must exist in that file byte-exactly, and once, and not be one an earlier task in the same document has already rewritten | 2 | +``` + +- [ ] **Step 5: Add the two rows** + +Find in `plugins/working-process/rules/propagation-duties.md`: + +``` +technical designs its design specs name — none missing, none extra | 9 | +``` + +Replace with: + +``` +technical designs its design specs name — none missing, none extra | 9 | +| added, changed or withdrawn a register entry, or added, split or merged a plan task, or written a `**Defers:**` or `**Follows:**` line | the register against every `**Realizes:**` annotation, `**Defers:**` line and `**Follows:**` predecessor | 10 | +| written a column naming rows of another table | every name in it against that table | 11 | +``` + +- [ ] **Step 6: Verify** + +Run the Step 1 command again. + +Expected: `A 0`, `B 1`, `C 0`, `D 1`, `E 1`, `F 1`, `G 1`, `H 12`. + +`H 12` is the twelve edits the recounted sentence claims, re-derived +from the rows (propagation duty 4), and eleven distinct duty numbers +stand in the last column: + +```bash +grep '^| [a-z]' plugins/working-process/rules/propagation-duties.md | awk -F'|' '{print $(NF-1)}' | sort -u | wc -l +``` + +Expected: `11`. + +- [ ] **Step 7: Commit** + +```bash +git add plugins/working-process/rules/propagation-duties.md +git commit -m "feat(working-process): propagation duties checklist gains duties 10 and 11" +``` + +--- + +### Task 10: Grilling session — update the register as decisions land + +**Files:** +- Modify: `plugins/working-process/skills/grilling-session/SKILL.md` — + one bullet in `## Grilling mechanics`. + +**Interfaces:** +- Consumes: the `## Decision register` heading from Task 1, cited by + rule name. +- Produces: nothing a later task reads. + +**Realizes:** D6 + +- [ ] **Step 1: Record the before values** + +```bash +F=plugins/working-process/skills/grilling-session/SKILL.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(n $F | grep -oF 'decision register' | wc -l)" +``` + +Expected: `A 0`. + +- [ ] **Step 2: Add the bullet** + +Find in `plugins/working-process/skills/grilling-session/SKILL.md`: + +``` +- Apply glossary updates inline the moment a term settles — no batching. +``` + +Replace with: + +``` +- Apply glossary updates inline the moment a term settles — no batching. +- A spec carrying `decisions: registered` has a decision register, + defined in the spec-plan-lifecycle rule: update it inline as each + decision lands. A new decision takes the next free identifier, a + sharpened one keeps its identifier, and a dropped one is withdrawn + only on the developer's explicit decision. +``` + +- [ ] **Step 3: Verify** + +Run the Step 1 command again. + +Expected: `A 1`. + +- [ ] **Step 4: Commit** + +```bash +git add plugins/working-process/skills/grilling-session/SKILL.md +git commit -m "feat(working-process): grilling session updates the decision register" +``` + +--- + +### Task 11: README and CHANGELOG + +**Files:** +- Modify: `plugins/working-process/README.md` — the `plan-adversary` and + `propagation-auditor` bullets, and a new section before + `## Model selection`. +- Modify: `plugins/working-process/CHANGELOG.md` — bullets under + `## Unreleased`. + +**Interfaces:** +- Consumes: every name the earlier tasks produced. +- Produces: nothing a later task reads. + +**Realizes:** none + +- [ ] **Step 1: Record the before values** + +```bash +R=plugins/working-process/README.md +C=plugins/working-process/CHANGELOG.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(n $R | grep -oF 'a clean audit reports the single line `CLEAN`' | wc -l)" +echo "B $(grep -c '^## Decision register$' $R)" +echo "C $(n $R | grep -oF 'realize it in full' | wc -l)" +echo "D $(n $R | grep -oF '`decision-coverage:` block' | wc -l)" +echo "E $(n $C | grep -oF 'decision register' | wc -l)" +echo "F $(grep -c '^## Unreleased$' $C)" +``` + +Expected: `A 1`, `B 0`, `C 0`, `D 0`, `E 0`, `F 1`. + +- [ ] **Step 2: Update the plan-adversary bullet** + +Find in `plugins/working-process/README.md`: + +``` + come from `*-plan-review` checklist skills. Dispatched in the + background, scaled to the plan's size and risk; the verdict arrives + as a task notification and is stamped after relay. +``` + +Replace with: + +``` + come from `*-plan-review` checklist skills. Where a design spec keeps + a decision register, it also judges whether the tasks citing each + decision realize it in full. Dispatched in the background, scaled to + the plan's size and risk; the verdict arrives as a task notification + and is stamped after relay. +``` + +- [ ] **Step 3: Update the propagation-auditor bullet** + +Find in `plugins/working-process/README.md`: + +``` + edit needs — re-derives every counter, and runs the document's own + verification commands. Its + unit is the hit: located, binary, and carrying the derivation that + produced it; a clean audit reports the single line `CLEAN`. It grades +``` + +Replace with: + +``` + edit needs — re-derives every counter, runs the document's own + verification commands, derives a plan's decision coverage from its + design specs' decision registers, and resolves the table relations + the technical-design rule declares. Its unit is the hit: located, + binary, and carrying the derivation that produced it. Every report + also carries a `decision-coverage:` block per design spec and a + `table-closure:` line per technical design, and a clean one ends in + `CLEAN`. It grades +``` + +- [ ] **Step 4: Add the README section** + +Find in `plugins/working-process/README.md`: + +``` +## Model selection +``` + +Replace with: + +``` +## Decision register + +A design spec that sets `decisions: registered` carries a `## Decisions` +section listing, under stable identifiers (`D3`, `D4.1`), every +decision that needs realization. A plan descending from it marks each +task with `**Realizes:**` — the identifiers it realizes, or `none` — +and records deferrals and predecessor plans in a `## Deferrals and +predecessors` section. The propagation auditor derives the decision +coverage from the two lists, the plan-adversary judges whether the +citing tasks realize their decisions, and the integrity auditor checks +that the register lists every decision the spec makes. A spec without +the field is reported as not checked. The grammar lives in the +spec-plan-lifecycle rule. + +## Model selection +``` + +- [ ] **Step 5: Add the CHANGELOG bullets** + +Find in `plugins/working-process/CHANGELOG.md`: + +``` +- Run a rules re-sync after this update: the plan-adversary's `origin` + values changed, and until the re-sync an installed workflow rule + triages the new values by the old names. +``` + +Replace with: + +``` +- A design spec may carry a decision register: `decisions: registered` + and a `## Decisions` section listing, under stable identifiers, every + decision that needs realization, with `withdrawn` as its one state + token. +- A plan descending from a registered spec marks every task with + `**Realizes:**`, and records deferrals and predecessor plans in a + `## Deferrals and predecessors` section. +- The propagation auditor gains two duties: decision coverage, reported + in a `decision-coverage:` block per design spec, and table closure + over the relations the technical-design rule declares, reported in a + `table-closure:` line. A clean report carries those lines before + `CLEAN`. +- A coverage hit whose fix needs a decision the spec does not make is + held for the developer as a `hit held` gate line with three terminal + shapes, and the Unfinished review-loop ledger command finds it. +- The plan-adversary judges whether the tasks citing a decision realize + it in full, and the integrity auditor checks that the register lists + every decision its spec makes. +- The propagation duties checklist gains rows for the two duties and + the duty-2 anchor an earlier task has already rewritten. +- Run a rules re-sync after this update: the plan-adversary's `origin` + values changed, and until the re-sync an installed workflow rule + triages the new values by the old names. The re-sync also installs + the decision register, the plan annotations and the held gate line, + which the updated agents expect. +``` + +- [ ] **Step 6: Verify** + +Run the Step 1 command again. + +Expected: `A 0`, `B 1`, `C 1`, `D 1`, `E 2`, `F 1`. + +`E 2` is the first new bullet and the widened re-sync bullet. + +- [ ] **Step 7: Commit** + +```bash +git add plugins/working-process/README.md plugins/working-process/CHANGELOG.md +git commit -m "docs(working-process): README and changelog for plan coverage" +``` + +--- + +### Task 12: Final verification + +**Files:** +- Test: the whole plugin, the glossary, and this plan. + +**Interfaces:** +- Consumes: every task above. +- Produces: nothing. + +**Realizes:** none + +- [ ] **Step 1: Validate the marketplace and the plugin** + +```bash +claude plugin validate . +claude plugin validate plugins/working-process +``` + +Expected: both pass. Validation reads manifests and component +frontmatter, never `rules/`, so it proves the card's `description:` +still parses and nothing about the rules. + +- [ ] **Step 2: Check what must not change** + +```bash +git diff --stat develop -- plugins/working-process/agents/architect.md | tail -n 1 +awk '/^### 5\./{f=1;print;next} f&&/^#/{exit} f' plugins/working-process/agents/plan-adversary.md | shasum | cut -c1-7 +``` + +Expected: the first command prints nothing (D28.1); the second prints +`c518b54`, the hash Task 7 recorded (D28.2). + +- [ ] **Step 3: Check the glossary entries stand** + +```bash +G=docs/domain/glossary.md +echo "A $(grep -c '^\*\*Decision register\*\*:$' $G)" +echo "B $(grep -c '^\*\*Decision coverage\*\*:$' $G)" +echo "C $(tr -s '[:space:]' ' ' < $G | grep -oF 'a held hit it authorizes closes only once the gate has rechecked the fix' | wc -l)" +``` + +Expected: `A 1`, `B 1`, `C 1`. + +- [ ] **Step 4: Check line width of the changed prose** + +```bash +for f in rules/spec-plan-lifecycle.md rules/technical-design.md rules/workflow.md \ + rules/propagation-duties.md agents/propagation-auditor.md agents/plan-adversary.md \ + agents/integrity-auditor.md skills/grilling-session/SKILL.md README.md CHANGELOG.md; do + git diff -U0 develop -- plugins/working-process/$f | grep '^+[^+]' | cut -c2- \ + | LC_ALL=C.UTF-8 awk -v f=$f 'length > 72 && !/^ {4}/ && !/^\|/ && !/^description:/ && !/^ {6}/ && !/ # / {print f": "$0}' +done +``` + +Expected: no output. Indented grammar examples, table rows, the +one-line `description:` and the lifecycle rule's commented frontmatter +lines, whose neighbours already run past the width, are exempt. + +- [ ] **Step 5: Derive this plan's decision coverage** + +The installed auditor has no duty 10 yet, so derive it here: + +```bash +python3 - <<'EOF' +import re +spec = open('docs/specs/2026-09-25-plan-coverage-design.md').read() +reg = spec.split('\n## Decisions\n', 1)[1].split('\n## ', 1)[0] +ids = re.findall(r'^\s*- \*\*(D\d+(?:\.\d+)?)\*\*', reg, re.M) +groups = {i.split('.')[0] for i in ids if '.' in i} +leaves = [i for i in ids if i not in groups] +plan = open('docs/plans/2026-09-26-plan-coverage.md').read() +cited = set() +for v in re.findall(r'^(?:- )?\*\*Realizes:\*\* (.+)$', plan, re.M): + v = v.split(' — ')[0] + cited |= {x.strip() for x in v.split(',') if x.strip() != 'none'} +print(len(leaves), 'leaves;', len(set(leaves) & cited), 'cited;', + 'uncovered', sorted(set(leaves) - cited), + 'unknown', sorted(cited - set(leaves))) +EOF +``` + +Expected: `39 leaves; 39 cited; uncovered [] unknown []`. + +- [ ] **Step 6: Report** + +No commit. Report the results of Steps 1–5 to the developer, and leave +`sync-rules`, the version and both documents' `status` to them. From 91661fa4224fe5897a59b3b9f5a5bc217052c469 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 09:27:26 +0200 Subject: [PATCH 057/143] docs(working-process): plan-coverage plan, adversary round 1 dispositions --- docs/plans/2026-09-26-plan-coverage.md | 301 ++++++++++++++++-- docs/specs/2026-09-25-plan-coverage-design.md | 8 +- 2 files changed, 288 insertions(+), 21 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 6b7f7f0..7d7e9a9 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2,6 +2,7 @@ ticket: none date: 2026-09-26 status: draft +adversary: blocking spec: ../specs/2026-09-25-plan-coverage-design.md branch: feature/plan-coverage base: develop @@ -67,8 +68,9 @@ Claude Code plugin and Rules payload; `tr`, `grep`, `awk`, `python3` and - **Public repo hygiene**: no machine-specific paths, no company or client names, all committed text in English. - **Commit messages are one line** — a conventional-commit subject, no - body, no trailers, `Co-Authored-By` included. A subagent implementer's - brief says so outright, since subagents add the trailer by default. + body and no trailer, not even `Co-Authored-By`. A subagent + implementer's brief says so outright, since subagents add the trailer + by default. - **Commits land on `feature/plan-coverage`.** The document branch `feature/plan-coverage.docs` merges into it at the implementation-ready gate, before Task 1. @@ -108,7 +110,24 @@ Recorded here and beside the text they concern. 7. **This plan carries `**Realizes:**` annotations although the convention ships with it.** The installed propagation auditor has no duty 10 yet, so Task 12 derives this plan's decision coverage with a - script instead of relying on the gate. + script instead of relying on the gate; Task 13 then runs the changed + card itself. +8. **Duties 10 and 11 read the grammar from the plugin's own copy of + each rule, `${CLAUDE_PLUGIN_ROOT}/rules/…`.** The spec says the rule + loads when the auditor reads the document, but Task 6 writes the + premise that a separate context cannot count on a rule loading, and + an installed copy may lag the card after an update. The path repeats + none of the grammar, so D21 and Deviation 5 stand. Adversary round 1 + raised it; the developer ruled on 2026-09-26. +9. **Task 3 grants a `## Review rounds` section to "a document", where + the spec says "a plan".** The spec also places a `hit held` line in a + design spec audited alone, before its first round, and such a spec + has no section yet either. Ruled on 2026-09-26. +10. **Task 2 and Task 5 state that an annotation counts only at its + defined place.** The spec defines each line's place without saying + that the same text elsewhere instantiates nothing, and this plan + quotes `**Defers:** D6` in a code block while citing D6 — a false + hit waiting for a text-matching auditor. Ruled on 2026-09-26. ## File structure @@ -153,7 +172,8 @@ and names none itself. Task 9's rows cite the duty numbers Task 5 creates. - Tasks 7, 8 and 10 depend on nothing else in this plan. - `claude plugin validate` runs once, in Task 12, after every file has - changed. + changed. Task 13 runs the changed auditor card on fixtures and needs + every earlier task landed. --- @@ -368,9 +388,11 @@ echo "F $(grep -c '^ \*\*Follows:\*\* \.\./plans/\.md$' $F)" echo "G $(n $F | grep -oF 'so inheritance is not transitive' | wc -l)" echo "H $(n $F | grep -oF '`../specs/.md#D3`' | wc -l)" echo "I $(n $F | grep -oF 'names this rule and requires reading it first' | wc -l)" +echo "J $(n $F | grep -oF 'describes the grammar and instantiates nothing' | wc -l)" ``` -Expected: `A 0`, `B 1`, `C 0`, `D 0`, `E 0`, `F 0`, `G 0`, `H 0`, `I 0`. +Expected: `A 0`, `B 1`, `C 0`, `D 0`, `E 0`, `F 0`, `G 0`, `H 0`, `I 0`, +`J 0`. - [ ] **Step 2: Revise the template sentence** @@ -466,6 +488,12 @@ citation of it still does. Whether the predecessor's realization still stands in the code is not this line's question: the diff the implemented-document bullet above prescribes answers it. +An annotation, a `**Defers:**` line or a `**Follows:**` line counts +only at the place this section gives it: a task's line before its first +step, the opening clause of a Global Constraints entry, a line of +`## Deferrals and predecessors`. The same text inside a code block, or +quoted as an example, describes the grammar and instantiates nothing. + The plan's author writes these lines, whoever that author is. A session writing a plan meets this rule by reading the design spec, and a brief that delegates plan-writing to a separate context names this rule and @@ -481,7 +509,8 @@ the decision, never mechanically. Run the Step 1 command again. -Expected: `A 1`, `B 0`, `C 1`, `D 1`, `E 1`, `F 1`, `G 1`, `H 1`, `I 1`. +Expected: `A 1`, `B 0`, `C 1`, `D 1`, `E 1`, `F 1`, `G 1`, `H 1`, `I 1`, +`J 1`. - [ ] **Step 5: Commit** @@ -526,10 +555,14 @@ echo "I $(n $F | grep -oF 'the only two disposition states the Unfinished-work l echo "J $(n $F | grep -oF 'every later pre-round episode writes there too' | wc -l)" echo "K $(n $F | grep -oF 'The same heading serves a register edit' | wc -l)" echo "L $(n $F | grep -oF 'The ordinary `hit fixed` shape, without `ruling:`, stays' | wc -l)" +echo "M $(n $F | grep -oF 'writes two shapes of its own' | wc -l)" +echo "N $(n $F | grep -oF 'Both are written at gate time' | wc -l)" +echo "O $(n $F | grep -oF 'A document with no `## Review rounds` section gains one' | wc -l)" +echo "P $(n $F | grep -oF 'never joins a resolution note' | wc -l)" ``` Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 1`, `G 1`, `H 0`, `I 0`, -`J 0`, `K 0`, `L 0`. +`J 0`, `K 0`, `L 0`, `M 1`, `N 1`, `O 0`, `P 0`. - [ ] **Step 2: Amend the non-terminal sentence** @@ -570,13 +603,35 @@ stops certifying the words that changed. The same heading serves a register edit that a plan's held gate line licensed, on a design spec whose loop has closed at whatever verdict: the heading then names the plan whose gate held the hit, and that plan's terminal gate line names -the heading and the line under it. +the heading and the line under it. Unlike a review-licensed fix, such an +edit never joins a resolution note, even on a `concerns (resolved)` +spec: that note records what resolved the verdict, and this edit +resolves none. ``` - [ ] **Step 4: Add the held shape to *Gate lines*** Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: +``` +The propagation gate, when that agent is available, writes two shapes of +its own. They carry their own leading token and never a severity, because +a hit is a located detection the dispatcher confirms or dismisses, never +a graded finding: +``` + +Replace with: + +``` +The propagation gate, when that agent is available, writes gate lines of +its own: the two ordinary shapes here, and the held shape and its three +terminal rewrites below. Each carries its own leading token and never a +severity, because a hit is a located detection the dispatcher confirms +or dismisses, never a graded finding: +``` + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + ``` Neither carries a license either, because a hit's fix is licensed by its own derivation. @@ -627,6 +682,21 @@ revision instead, and the held hit waits for it. Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: +``` +Both are written at gate time, under the last round's heading. The `hit` +``` + +Replace with: + +``` +Each is written at gate time, under the last round's heading. The `hit` +``` + +The held paragraphs Step 4 inserts now stand between the shapes and +this sentence, so "Both" would name two of six shapes. + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + ``` diff-scoped brief is composed before its own round is stamped. A gate before a document's first round has no heading to write under; its lines @@ -647,7 +717,7 @@ alike, in the `## Review rounds` section before the first round heading, and they stay there, rewrites included: no round produced them, and one episode keeps one home. Once such lines stand there, every later pre-round episode writes there too, holding or not, so the -document's pre-round gate history keeps one place. A plan with no +document's pre-round gate history keeps one place. A document with no `## Review rounds` section gains one, at its end, with its first gate line. ``` @@ -748,7 +818,7 @@ Replace with: Run the Step 1 command again. Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 0`, `G 0`, `H 1`, `I 1`, -`J 1`, `K 1`, `L 1`. +`J 1`, `K 1`, `L 1`, `M 0`, `N 0`, `O 1`, `P 1`. Then run the widened command over a fixture, to prove it matches both a disposition line and a held gate line and nothing terminal: @@ -899,10 +969,12 @@ echo "J $(n $F | grep -oF 'is held for the developer' | wc -l)" echo "K $(n $F | grep -oF 'means no hit in the checks that ran' | wc -l)" echo "L $(awk '/^### /{c++} END{print c}' $F)" echo "M $(head -n 5 $F | grep -c '^description: "')" +echo "N $(n $F | grep -oF '${CLAUDE_PLUGIN_ROOT}/rules/' | wc -l)" +echo "O $(n $F | grep -oF 'a code block or a quoted example describes the grammar' | wc -l)" ``` Expected: `A 0`, `B 0`, `C 1`, `D 1`, `E 0`, `F 0`, `G 0`, `H 0`, `I 0`, -`J 0`, `K 0`, `L 9`, `M 1`. +`J 0`, `K 0`, `L 9`, `M 1`, `N 0`, `O 0`. - [ ] **Step 2: Rewrite the description** @@ -942,8 +1014,11 @@ not a hit: no design spec names its plans. Runs on a plan, and on a design spec audited alone. The register's grammar, its state token and the plan's annotations are defined in the spec-plan-lifecycle rule, under *Decision register* and *Plan -annotations*; read them there rather than recalling them. For each -spec the plan's `spec:` names: +annotations*. Read them in the plugin's own copy, +`${CLAUDE_PLUGIN_ROOT}/rules/spec-plan-lifecycle.md`, which matches this +card's version, rather than recalling them. Read a line only at the +place *Plan annotations* gives it: a code block or a quoted example +describes the grammar. For each spec the plan's `spec:` names: 1. Read the spec's `decisions:` field. Absent: the spec is legacy — write `not checked` for it and stop. Any value other than @@ -1005,7 +1080,8 @@ the tables declares, never relations guessed from matching values: two unrelated columns can share names by chance, and a relation whose every reference is wrong would match nothing. The technical-design rule declares them under its heading `## Declared table relations`, with how -each cell splits and which values are not references; read them there. +each cell splits and which values are not references; read them in the +plugin's own copy, `${CLAUDE_PLUGIN_ROOT}/rules/technical-design.md`. For each declared relation whose tables the document carries, every name in the column resolves to exactly one row of the named table: a name matching no row is a hit, and so is a name matching several. An @@ -1145,7 +1221,7 @@ Replace with: Run the Step 1 command again. Expected: `A 1`, `B 1`, `C 0`, `D 0`, `E 6`, `F 2`, `G 1`, `H 1`, `I 1`, -`J 1`, `K 1`, `L 11`, `M 1`. +`J 1`, `K 1`, `L 11`, `M 1`, `N 2`, `O 1`. `E 6` is the three summary lines of a plan's block plus the three of a spec audited alone; the map lines are indented six spaces and do not @@ -1866,7 +1942,196 @@ EOF Expected: `39 leaves; 39 cited; uncovered [] unknown []`. -- [ ] **Step 6: Report** +- [ ] **Step 6: Carry the count forward** + +No commit. Write down the leaf count Step 5 printed; Task 13 expects it +as `N`, derived from the register as it stands rather than fixed here. + +--- + +### Task 13: Run the changed auditor card on controlled cases + +**Files:** +- Test: a fixture project outside the repository, and the dispatch + record `.claude/working-process/2026-09-26-plan-coverage/dogfood-auditor.md`. + +**Interfaces:** +- Consumes: every task above, and `N` from Task 12 Step 5. +- Produces: nothing. + +**Realizes:** none + +The greps prove the text landed; only a run proves the card detects what +it claims. Four targets: this plan, which must come out fully covered +although it quotes `**Defers:** D6` in a code block while citing D6; a +copy with one annotation removed, which must report exactly that gap; +the spec alone; and a technical design with one wrong reference. + +- [ ] **Step 1: Build the fixture project** + +The fixture carries the plugin files at `develop`, before this plan, so +the plan's Find anchors still match there and duty 2 stays quiet; the +card and the rules come from the changed checkout. + +```bash +R=$(git rev-parse --show-toplevel) +W=${TMPDIR:-/tmp}/plan-coverage-dogfood +command rm -rf "$W"; mkdir -p "$W" +git -C "$R" archive develop plugins | tar -x -C "$W" +mkdir -p "$W/docs/specs" "$W/docs/plans" "$W/docs/domain" \ + "$W/docs/technical-designs" "$W/.claude/rules/working-process" +command cp -f "$R/docs/specs/2026-09-25-plan-coverage-design.md" "$W/docs/specs/" +command cp -f "$R/docs/plans/2026-09-26-plan-coverage.md" "$W/docs/plans/" +command cp -f "$R/docs/domain/glossary.md" "$W/docs/domain/" +command cp -f "$R"/plugins/working-process/rules/*.md "$W/.claude/rules/working-process/" +sed 's/^\*\*Realizes:\*\* D15$/**Realizes:** none/' \ + "$W/docs/plans/2026-09-26-plan-coverage.md" > "$W/docs/plans/fixture-uncovered.md" +grep -c '^\*\*Realizes:\*\* D15$' "$W/docs/plans/fixture-uncovered.md" +``` + +Expected: `0` — Task 8's only annotation is gone from the copy, so D15 +is cited nowhere else. + +Then write the table-closure pair, whose Contracts row names a part +`reder` that Parts does not carry: + +```bash +W=${TMPDIR:-/tmp}/plan-coverage-dogfood +cat > "$W/docs/specs/fixture-design.md" <<'EOF' +--- +ticket: none +date: 2026-09-26 +status: draft +technical-design: ../technical-designs/fixture-technical-design.md +--- + +# Fixture + +A fixture design spec for the table-closure duty. +EOF +cat > "$W/docs/technical-designs/fixture-technical-design.md" <<'EOF' +--- +ticket: none +date: 2026-09-26 +status: draft +spec: ../specs/fixture-design.md +--- + +# Fixture technical design + +## Scope + +The fixture spec's single behaviour. + +## Parts + +| part | kind | change | placement | owns | deliberately excludes | +|---|---|---|---|---|---| +| reader | rule | new | rules/ | reading | writing | +| writer | rule | new | rules/ | writing | reading | + +## Contracts + +| producer → consumer | crosses | guarantee | breaks when | check | +|---|---|---|---|---| +| writer → reder | file | a record | the file is missing | grep | + +## State + +| record | home | written by | read by | lifecycle | visible through | +|---|---|---|---|---|---| +| log | docs/ | writer | reader | per run | grep | + +## Failure and repetition + +Not applicable, because the fixture never runs. + +## Cuts not taken + +Not applicable, because nothing was cut. + +## Open questions + +None. +EOF +(cd "$W" && git init -q && git add -A \ + && git -c user.name=fixture -c user.email=fixture@example.invalid commit -qm fixture) +``` + +- [ ] **Step 2: Disable the installed plugin — ask the developer first** + +A `--plugin-dir` session collides with the installed plugin of the same +name. Disabling it changes the developer's own configuration, so ask +before running: + +```bash +claude plugin disable working-process +``` + +- [ ] **Step 3: Run the four audits** + +```bash +R=$(git rev-parse --show-toplevel) +W=${TMPDIR:-/tmp}/plan-coverage-dogfood +for f in docs/plans/2026-09-26-plan-coverage.md docs/plans/fixture-uncovered.md \ + docs/specs/2026-09-25-plan-coverage-design.md \ + docs/technical-designs/fixture-technical-design.md; do + echo "##### $f" + (cd "$W" && claude -p --plugin-dir "$R/plugins/working-process" \ + "Dispatch the working-process:propagation-auditor agent on the haiku model over $f and print its report verbatim, nothing else.") +done > "$W/reports.txt" +cat "$W/reports.txt" +``` + +Expected, with `N` from Task 12 Step 5: + +- **this plan** — a summary line beginning + `decision-coverage: ../specs/2026-09-25-plan-coverage-design.md N/N covered`, + `N` map lines under it, no hit, and `CLEAN` last. No hit names + `**Defers:**` or D6: the quoted grammar instantiates nothing. +- **the uncovered copy** — a hit naming D15, a summary line with + `N-1/N covered` and `uncovered [D15]`, a map line `D15 → —`, and no + `CLEAN`. +- **the spec alone** — one `decision-coverage:` line ending + `register well formed`, and `CLEAN`. +- **the technical design** — a hit naming `reder`, the line + `table-closure: 3 relations checked`, and no `CLEAN`. + +Every report opens with a `model:` line naming the haiku family. A +report that departs from its expectation is a defect in the card or in +the rules it reads: report it to the developer with the report text, and +do not adjust the expectation to fit. + +- [ ] **Step 4: Re-enable the plugin, keep the evidence, clean up** + +```bash +R=$(git rev-parse --show-toplevel) +W=${TMPDIR:-/tmp}/plan-coverage-dogfood +claude plugin enable working-process +D="$R/.claude/working-process/2026-09-26-plan-coverage" +{ printf '%s\n' 'date: 2026-09-26' 'agent: propagation-auditor (dogfood, --plugin-dir)' \ + 'model: see each report' 'subject: Task 13 controlled cases'; echo; cat "$W/reports.txt"; } \ + > "$D/dogfood-auditor.md" +command rm -rf "$W" +``` + +Expected: `claude plugin list` shows `working-process` enabled again. + +- [ ] **Step 5: Report** + +No commit. Report the results of Task 12 and of this task to the +developer, and leave `sync-rules`, the version and both documents' +`status` to them. + +## Review rounds + +### 2026-09-26 — plan-adversary, fable 5.1, blocking (round 1, full-document) -No commit. Report the results of Steps 1–5 to the developer, and leave -`sync-rules`, the version and both documents' `status` to them. +- fixed 2026-09-26 — [Important] D13.2 realized only in part: the spec's leaf widens the Unfinished review-loop ledger entry's name, description and command, and Task 3 keeps the name; ruling: 2026-09-26; option (a) — the spec's register contradicted its argued section, and D13.2 now keeps the name: the line sits in `../specs/2026-09-25-plan-coverage-design.md` under `### Loop closed — 2026-09-26` +- fixed 2026-09-26 — [Important] duties 10 and 11 send the auditor to rules by name with no path, while Task 6 states a separate context cannot count on the rule loading; license: Task 6's Replace ("a separate context cannot count on it loading") and the `${CLAUDE_PLUGIN_ROOT}` precedent in `plan-adversary.md`; deviation: *Deviations from the spec* 8 — both duties read the plugin's copy always, not only when the installed rule is absent, since an installed copy may lag the card +- fixed 2026-09-26 — [Important] Task 3 leaves *Gate lines* opening with "writes two shapes of its own" and "Both are written at gate time" beside the new held shape; license: propagation duty 4 and Deviation 3's own reason; Task 3 Step 4 rewrites the opener, Step 5 turns "Both" into "Each", checks M–N added +- fixed 2026-09-26 — [Important] the changed auditor card ships with no named test that runs it; ruling: 2026-09-26; new Task 13 runs the card from the checkout on four targets — this plan, a copy missing D15, the spec alone, a technical design with a wrong reference — with the expected count `N` derived by Task 12 rather than fixed +- fixed 2026-09-26 — [Minor] Task 3 Step 3 routes a gate-licensed register edit to the fix heading at whatever closing verdict, while a review-licensed fix on a `concerns (resolved)` document joins the resolution note; license: D29 ("where the spec's loop is closed, under the cross-document fix heading"); the sentence now states the divergence and its reason, check P added +- fixed 2026-09-26 — [Minor] "A plan with no `## Review rounds` section gains one", while a held line can land in a design spec audited alone; ruling: 2026-09-26; "A document", recorded as Deviation 9, check O added +- fixed 2026-09-26 — [Minor] duty 10 has no guard against a document quoting the annotation grammar, and this plan quotes `**Defers:** D6` while citing D6; ruling: 2026-09-26; Task 2 states that a line counts only at its defined place — Global Constraints entries included — and that a code block or quoted example instantiates nothing, and duty 10 reads lines only there; Deviation 10, checks J (Task 2) and O (Task 5) added, and Task 13 runs the case +- fixed 2026-09-26 — [Minor] the commit constraint "no trailers, `Co-Authored-By` included" reads two ways; license: the developer's commit rule (`.claude/rules/commit-messages.md`); reworded to "no body and no trailer, not even `Co-Authored-By`" diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 2afa83e..6409395 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -109,9 +109,9 @@ What stands today: - **D13.1** — Four gate-line shapes: `hit held`, and its three terminal rewrites `hit fixed …; ruling:`, `hit deferred …; ruling:` and `hit withdrawn …; ruling:`. - - **D13.2** — The *Unfinished review-loop ledger* entry widens — its - name, its description and its command — to take the `hit held` - line. + - **D13.2** — The *Unfinished review-loop ledger* entry widens its + description and its command to take the `hit held` line; its name + stays. - **D14** — The plan-adversary gains a dimension judging each decision's realization: do the tasks and constraints citing it realize it in full together, and does a cited constraint actually @@ -958,6 +958,8 @@ judged another round not worth its cost; the integrity audit at the consumption gate takes the two integrity-class questions that round named. +- fixed 2026-09-26 — [Important] D13.2 widened the entry's name as well, while *Gate lines for a held hit* widens only its description and command; ruling: 2026-09-26; D13.2 now keeps the name, which still covers a held gate line in the ledger — raised by `../plans/2026-09-26-plan-coverage.md`, plan-adversary round 1 + ### 2026-09-26 — integrity audit, fable, at the consumption gate Coverage tell: 755 lines read, highest line cited 755 — a whole-document From 40f9c65dd6eb7bee746345f107a07926429f14f0 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 09:54:25 +0200 Subject: [PATCH 058/143] docs(working-process): plan-coverage plan, adversary round 2 dispositions --- docs/plans/2026-09-26-plan-coverage.md | 112 ++++++++++++++++--------- 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 7d7e9a9..0a1a70d 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -1940,20 +1940,21 @@ print(len(leaves), 'leaves;', len(set(leaves) & cited), 'cited;', EOF ``` -Expected: `39 leaves; 39 cited; uncovered [] unknown []`. +Expected: `N leaves; N cited; uncovered [] unknown []`, the two counts +equal. `N` read 39 when this plan was written; a register edit since +changes it, and the line is right whatever `N` is. - [ ] **Step 6: Carry the count forward** -No commit. Write down the leaf count Step 5 printed; Task 13 expects it -as `N`, derived from the register as it stands rather than fixed here. +No commit. Write down `N`; Task 13 expects it. --- ### Task 13: Run the changed auditor card on controlled cases **Files:** -- Test: a fixture project outside the repository, and the dispatch - record `.claude/working-process/2026-09-26-plan-coverage/dogfood-auditor.md`. +- Test: a fixture project outside the repository, and the evidence file + `.claude/working-process/2026-09-26-plan-coverage/task-13-auditor-runs.md`. **Interfaces:** - Consumes: every task above, and `N` from Task 12 Step 5. @@ -1962,28 +1963,35 @@ as `N`, derived from the register as it stands rather than fixed here. **Realizes:** none The greps prove the text landed; only a run proves the card detects what -it claims. Four targets: this plan, which must come out fully covered -although it quotes `**Defers:** D6` in a code block while citing D6; a -copy with one annotation removed, which must report exactly that gap; -the spec alone; and a technical design with one wrong reference. +it claims. The run is scoped to duties 10 and 11, the two this plan +adds: they read documents and nothing else, so the auditor needs no +shell beyond resolving the repo root, and it never executes the +commands this plan quotes. Four cases: this plan, which must come out +fully covered although it quotes `**Defers:** D6` in a code block while +citing D6; a copy with one annotation removed, which must report exactly +that gap; the spec alone; and a technical design with one wrong +reference. + +A tool denial, a file the auditor could not read, or a report missing +its expected lines fails the test. None of them is a clean result. - [ ] **Step 1: Build the fixture project** -The fixture carries the plugin files at `develop`, before this plan, so -the plan's Find anchors still match there and duty 2 stays quiet; the -card and the rules come from the changed checkout. +The fixture holds the inputs the two duties read and nothing more. The +card and the rule copies it reads through `${CLAUDE_PLUGIN_ROOT}` come +from the changed checkout; the installed user-scope rules still load in +the inner session, and the expectations rest on the card reading the +plugin's copy, as Deviation 8 prescribes. `git init` is there because +the card resolves the repo root with `git rev-parse --show-toplevel`. ```bash R=$(git rev-parse --show-toplevel) W=${TMPDIR:-/tmp}/plan-coverage-dogfood -command rm -rf "$W"; mkdir -p "$W" -git -C "$R" archive develop plugins | tar -x -C "$W" -mkdir -p "$W/docs/specs" "$W/docs/plans" "$W/docs/domain" \ - "$W/docs/technical-designs" "$W/.claude/rules/working-process" +command rm -rf "$W" +mkdir -p "$W/docs/specs" "$W/docs/plans" "$W/docs/domain" "$W/docs/technical-designs" command cp -f "$R/docs/specs/2026-09-25-plan-coverage-design.md" "$W/docs/specs/" command cp -f "$R/docs/plans/2026-09-26-plan-coverage.md" "$W/docs/plans/" command cp -f "$R/docs/domain/glossary.md" "$W/docs/domain/" -command cp -f "$R"/plugins/working-process/rules/*.md "$W/.claude/rules/working-process/" sed 's/^\*\*Realizes:\*\* D15$/**Realizes:** none/' \ "$W/docs/plans/2026-09-26-plan-coverage.md" > "$W/docs/plans/fixture-uncovered.md" grep -c '^\*\*Realizes:\*\* D15$' "$W/docs/plans/fixture-uncovered.md" @@ -1993,7 +2001,7 @@ Expected: `0` — Task 8's only annotation is gone from the copy, so D15 is cited nowhere else. Then write the table-closure pair, whose Contracts row names a part -`reder` that Parts does not carry: +`reder` that Parts does not carry, and commit the fixture: ```bash W=${TMPDIR:-/tmp}/plan-coverage-dogfood @@ -2058,32 +2066,45 @@ EOF && git -c user.name=fixture -c user.email=fixture@example.invalid commit -qm fixture) ``` -- [ ] **Step 2: Disable the installed plugin — ask the developer first** +- [ ] **Step 2: Run the four audits — ask the developer first** A `--plugin-dir` session collides with the installed plugin of the same -name. Disabling it changes the developer's own configuration, so ask -before running: - -```bash -claude plugin disable working-process -``` - -- [ ] **Step 3: Run the four audits** +name, so the script disables it for the run and restores the state it +found, whatever the run prints. Disabling changes the developer's own +configuration: ask before running. + +The inner session may dispatch an agent, read and search, and resolve +the repo root — nothing else. It runs with `--allowedTools` rather than +a broad grant, because the auditor would otherwise be free to execute +commands this plan quotes, `claude plugin disable` and `rm -rf` among +them. ```bash R=$(git rev-parse --show-toplevel) W=${TMPDIR:-/tmp}/plan-coverage-dogfood +was=$(claude plugin list | grep -A3 'working-process@missing-bits' | grep -c 'Status: .*enabled') +restore() { [ "$was" = 1 ] && claude plugin enable working-process; } +trap restore EXIT +[ "$was" = 1 ] && claude plugin disable working-process for f in docs/plans/2026-09-26-plan-coverage.md docs/plans/fixture-uncovered.md \ docs/specs/2026-09-25-plan-coverage-design.md \ docs/technical-designs/fixture-technical-design.md; do echo "##### $f" (cd "$W" && claude -p --plugin-dir "$R/plugins/working-process" \ - "Dispatch the working-process:propagation-auditor agent on the haiku model over $f and print its report verbatim, nothing else.") -done > "$W/reports.txt" + --allowedTools "Agent Read Grep Glob Bash(git rev-parse:*)" \ + "Dispatch the working-process:propagation-auditor agent on the haiku model over $f. Tell it to walk only duties 10 and 11 of its card and to report in the card's output shape. Print its report verbatim, nothing else.") +done > "$W/reports.txt" 2>&1 +restore; trap - EXIT cat "$W/reports.txt" +claude plugin list | grep -A3 'working-process@missing-bits' | grep 'Status:' ``` -Expected, with `N` from Task 12 Step 5: +Expected: the last line shows the status the plugin had before the run. + +- [ ] **Step 3: Compare each report with its expectation** + +`CLEAN` here means no hit in duties 10 and 11 — the only checks that +ran. With `N` from Task 12 Step 5: - **this plan** — a summary line beginning `decision-coverage: ../specs/2026-09-25-plan-coverage-design.md N/N covered`, @@ -2098,24 +2119,23 @@ Expected, with `N` from Task 12 Step 5: `table-closure: 3 relations checked`, and no `CLEAN`. Every report opens with a `model:` line naming the haiku family. A -report that departs from its expectation is a defect in the card or in -the rules it reads: report it to the developer with the report text, and -do not adjust the expectation to fit. +report that departs from its expectation, or that mentions a denied tool +or an unread file, fails the test: report it to the developer with the +report text, and do not adjust the expectation to fit. -- [ ] **Step 4: Re-enable the plugin, keep the evidence, clean up** +- [ ] **Step 4: Keep the evidence, clean up** ```bash R=$(git rev-parse --show-toplevel) W=${TMPDIR:-/tmp}/plan-coverage-dogfood -claude plugin enable working-process D="$R/.claude/working-process/2026-09-26-plan-coverage" -{ printf '%s\n' 'date: 2026-09-26' 'agent: propagation-auditor (dogfood, --plugin-dir)' \ - 'model: see each report' 'subject: Task 13 controlled cases'; echo; cat "$W/reports.txt"; } \ - > "$D/dogfood-auditor.md" +{ echo '# Task 13 — auditor runs on controlled cases, 2026-09-26'; echo; cat "$W/reports.txt"; } \ + > "$D/task-13-auditor-runs.md" command rm -rf "$W" ``` -Expected: `claude plugin list` shows `working-process` enabled again. +The file is the test's evidence, not a dispatch record: audits write +none. It lives in the store, whose `*` `.gitignore` keeps it local. - [ ] **Step 5: Report** @@ -2123,6 +2143,8 @@ No commit. Report the results of Task 12 and of this task to the developer, and leave `sync-rules`, the version and both documents' `status` to them. +--- + ## Review rounds ### 2026-09-26 — plan-adversary, fable 5.1, blocking (round 1, full-document) @@ -2135,3 +2157,15 @@ developer, and leave `sync-rules`, the version and both documents' - fixed 2026-09-26 — [Minor] "A plan with no `## Review rounds` section gains one", while a held line can land in a design spec audited alone; ruling: 2026-09-26; "A document", recorded as Deviation 9, check O added - fixed 2026-09-26 — [Minor] duty 10 has no guard against a document quoting the annotation grammar, and this plan quotes `**Defers:** D6` while citing D6; ruling: 2026-09-26; Task 2 states that a line counts only at its defined place — Global Constraints entries included — and that a code block or quoted example instantiates nothing, and duty 10 reads lines only there; Deviation 10, checks J (Task 2) and O (Task 5) added, and Task 13 runs the case - fixed 2026-09-26 — [Minor] the commit constraint "no trailers, `Co-Authored-By` included" reads two ways; license: the developer's commit rule (`.claude/rules/commit-messages.md`); reworded to "no body and no trailer, not even `Co-Authored-By`" +- signal 2026-09-26 — another round pays only after the four Important findings land; it can be diff-scoped, and the leftovers were one fix wave plus one batch of held questions, not a fresh full read + +### 2026-09-26 — plan-adversary, fable 5.1, blocking (round 2, diff-scoped) + +- fixed 2026-09-26 — [Important] Task 13's `claude -p` runs grant the auditor no Bash, so duty 7's commands are denied and a clean expectation certifies a run that never simulated; ruling: 2026-09-26; option (b) — the run walks duties 10 and 11 only, the inner session gets `--allowedTools "Agent Read Grep Glob Bash(git rev-parse:*)"`, `CLEAN` there means no hit in those two duties, and a tool denial, an unread file or a missing expected line fails the test; deviation: Task 13 — none of the suggested grants, since a broad Bash grant would let the auditor execute the commands this plan quotes +- fixed 2026-09-26 — [Important] Task 13's fixture has no `develop` ref and no history, so the plan's own `git diff … develop` commands and the commit Deviation 6 names fail there; ruling: 2026-09-26; with duties 6 and 7 out of the run, the fixture carries only the inputs duties 10 and 11 read, plus `git init` for the card's `git rev-parse`; the `git archive develop` step is gone +- fixed 2026-09-26 — [Minor] Task 13 claims the rules come from the checkout, while the installed user-scope rules still load, and the project-scope copy only doubles them; license: Deviation 8 (the card reads `${CLAUDE_PLUGIN_ROOT}/rules/`); the project-scope copy is dropped and Step 1 says the user-scope rules still load and the expectations rest on the plugin's copy +- fixed 2026-09-26 — [Minor] Task 13's evidence file is called a dispatch record and given its header, though audits write none; license: glossary **Dispatch record** and the workflow rule ("Audits write none"); the file is `task-13-auditor-runs.md`, called the test's evidence, with a plain heading +- fixed 2026-09-26 — [Minor] Task 13's plugin re-enable runs only on the success path; ruling: 2026-09-26; the script records the plugin's status before the run and restores exactly that state from an `EXIT` trap, then prints the status to compare +- fixed 2026-09-26 — [Minor] Task 12 Step 6 says `N` is not fixed while Step 5 fixes it at 39; license: Step 6's own sentence; Step 5 now expects the two counts equal, noting 39 as the value at writing +- signal 2026-09-26 — another round pays only after the two Important findings land; it can stay diff-scoped and narrow, then the confirming full-document round follows + From 93826af7afbdfc35a9109369ec604baf5a155481 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 10:09:58 +0200 Subject: [PATCH 059/143] docs(working-process): plan-coverage plan, adversary round 3 --- docs/plans/2026-09-26-plan-coverage.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 0a1a70d..867123b 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2,7 +2,7 @@ ticket: none date: 2026-09-26 status: draft -adversary: blocking +adversary: concerns spec: ../specs/2026-09-25-plan-coverage-design.md branch: feature/plan-coverage base: develop @@ -2104,7 +2104,9 @@ Expected: the last line shows the status the plugin had before the run. - [ ] **Step 3: Compare each report with its expectation** `CLEAN` here means no hit in duties 10 and 11 — the only checks that -ran. With `N` from Task 12 Step 5: +ran. The text compared is the auditor's report, from its `model:` line +to its last line; any wrapper text the outer session prints before or +after it is discarded, not judged. With `N` from Task 12 Step 5: - **this plan** — a summary line beginning `decision-coverage: ../specs/2026-09-25-plan-coverage-design.md N/N covered`, @@ -2169,3 +2171,9 @@ developer, and leave `sync-rules`, the version and both documents' - fixed 2026-09-26 — [Minor] Task 12 Step 6 says `N` is not fixed while Step 5 fixes it at 39; license: Step 6's own sentence; Step 5 now expects the two counts equal, noting 39 as the value at writing - signal 2026-09-26 — another round pays only after the two Important findings land; it can stay diff-scoped and narrow, then the confirming full-document round follows +### 2026-09-26 — plan-adversary, fable 5.1, concerns (round 3, full-document) + +- held — [Minor] the new `hit held` state has an anchor and an owner but no reader at the consumption gate: the lifecycle rule's backstop sentence blocks plan-writing and implementation on open `held` lines alone, and no step of Task 3 widens it; question: should the backstop also block on an open `hit held` line?; options: (a) add "or `hit held`" to that sentence in Task 3, recorded as a deviation with ruling — the spec says a held hit blocks the dispatch its gate guards, and a developer who declines that dispatch would otherwise pass the gate over an unanswered decision (recommended); (b) leave the sentence, and state in the spec that the Unfinished-work entry is the held hit's only reader beyond the blocked dispatch +- fixed 2026-09-26 — [Minor] Task 13 compares "each report" with its expectation, while `claude -p` prints the outer session's text, so a wrapper line before `model:` would fail a correct run; license: Task 13's stated purpose (the run proves what the card detects); Step 3 now compares the auditor's report from its `model:` line to its last line and discards wrapper text +- signal 2026-09-26 — another round does not earn its cost; both leftovers are Minor, one held for the developer and one licensable, together one small fix wave rather than a re-read + From b7ddddd47e47f466d9ccf555b6fd13261d6d7d24 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 10:53:27 +0200 Subject: [PATCH 060/143] docs(working-process): plan-coverage plan, held hit blocks the consumption gate --- docs/plans/2026-09-26-plan-coverage.md | 45 ++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 867123b..a0ac2af 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -128,6 +128,11 @@ Recorded here and beside the text they concern. that the same text elsewhere instantiates nothing, and this plan quotes `**Defers:** D6` in a code block while citing D6 — a false hit waiting for a text-matching auditor. Ruled on 2026-09-26. +11. **Task 3 Step 9 lets an open `hit held` line block the consumption + gate.** The spec says a held hit blocks the dispatch its gate + guards and is silent on the gate, so a developer who declined that + dispatch would pass plan-writing over an unanswered decision. + Adversary round 3 raised it; ruled on 2026-09-26. ## File structure @@ -527,7 +532,7 @@ git commit -m "feat(working-process): plan annotations for decision coverage" - Modify: `plugins/working-process/rules/spec-plan-lifecycle.md` — the non-terminal sentence and the cross-document fix heading paragraph in `## The disposition ledger`; `### Gate lines`; the *Unfinished - review-loop ledger* entry. + review-loop ledger* entry; the consumption-gate backstop sentence. **Interfaces:** - Consumes: the `**Defers:**` line and the `withdrawn` token from Tasks 1 @@ -559,10 +564,11 @@ echo "M $(n $F | grep -oF 'writes two shapes of its own' | wc -l)" echo "N $(n $F | grep -oF 'Both are written at gate time' | wc -l)" echo "O $(n $F | grep -oF 'A document with no `## Review rounds` section gains one' | wc -l)" echo "P $(n $F | grep -oF 'never joins a resolution note' | wc -l)" +echo "Q $(n $F | grep -oF '`held` lines or `hit held` gate lines stay open' | wc -l)" ``` Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 1`, `G 1`, `H 0`, `I 0`, -`J 0`, `K 0`, `L 0`, `M 1`, `N 1`, `O 0`, `P 0`. +`J 0`, `K 0`, `L 0`, `M 1`, `N 1`, `O 0`, `P 0`, `Q 0`. - [ ] **Step 2: Amend the non-terminal sentence** @@ -813,12 +819,39 @@ Replace with: developer. ``` -- [ ] **Step 9: Verify** +- [ ] **Step 9: Let an open held hit block the consumption gate** + +Find in `plugins/working-process/rules/spec-plan-lifecycle.md`: + +``` +A document's consumption gate is the backstop for its ledger: no judged +document passes to plan-writing, nor a plan to implementation, while +`held` lines stay open — an LGTM can leave the frontmatter clean while a +decision question still pends, so the gate asks those questions at the +latest. Writing a plan from a judged document is that same seam: its +held questions are asked before the plan is written, whoever writes it. +``` + +Replace with: + +``` +A document's consumption gate is the backstop for its ledger: no judged +document passes to plan-writing, nor a plan to implementation, while +`held` lines or `hit held` gate lines stay open — an LGTM can leave the +frontmatter clean while a decision question still pends, so the gate +asks those questions at the latest. A `hit held` line blocks until the +gate has re-run and rewritten it to a terminal shape, since declining +the dispatch it guarded decides nothing; a closed gate line blocks +nothing. Writing a plan from a judged document is that same seam: its +held questions are asked before the plan is written, whoever writes it. +``` + +- [ ] **Step 10: Verify** Run the Step 1 command again. Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 0`, `G 0`, `H 1`, `I 1`, -`J 1`, `K 1`, `L 1`, `M 0`, `N 0`, `O 1`, `P 1`. +`J 1`, `K 1`, `L 1`, `M 0`, `N 0`, `O 1`, `P 1`, `Q 1`. Then run the widened command over a fixture, to prove it matches both a disposition line and a held gate line and nothing terminal: @@ -835,7 +868,7 @@ rm -rf "$d" Expected: `2`. -- [ ] **Step 10: Commit** +- [ ] **Step 11: Commit** ```bash git add plugins/working-process/rules/spec-plan-lifecycle.md @@ -2173,7 +2206,7 @@ developer, and leave `sync-rules`, the version and both documents' ### 2026-09-26 — plan-adversary, fable 5.1, concerns (round 3, full-document) -- held — [Minor] the new `hit held` state has an anchor and an owner but no reader at the consumption gate: the lifecycle rule's backstop sentence blocks plan-writing and implementation on open `held` lines alone, and no step of Task 3 widens it; question: should the backstop also block on an open `hit held` line?; options: (a) add "or `hit held`" to that sentence in Task 3, recorded as a deviation with ruling — the spec says a held hit blocks the dispatch its gate guards, and a developer who declines that dispatch would otherwise pass the gate over an unanswered decision (recommended); (b) leave the sentence, and state in the spec that the Unfinished-work entry is the held hit's only reader beyond the blocked dispatch +- fixed 2026-09-26 — [Minor] the new `hit held` state has an anchor and an owner but no reader at the consumption gate: the lifecycle rule's backstop sentence blocks plan-writing and implementation on open `held` lines alone, and no step of Task 3 widens it; ruling: 2026-09-26; option (a) — new Task 3 Step 9 adds `hit held` gate lines to the backstop, blocking until the gate re-runs and rewrites the line to a terminal shape, a closed gate line blocking nothing; Deviation 11, check Q added - fixed 2026-09-26 — [Minor] Task 13 compares "each report" with its expectation, while `claude -p` prints the outer session's text, so a wrapper line before `model:` would fail a correct run; license: Task 13's stated purpose (the run proves what the card detects); Step 3 now compares the auditor's report from its `model:` line to its last line and discards wrapper text - signal 2026-09-26 — another round does not earn its cost; both leftovers are Minor, one held for the developer and one licensable, together one small fix wave rather than a re-read From c4f48973658bd23c06edacf64054cf6eecee2975 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 10:56:45 +0200 Subject: [PATCH 061/143] docs(working-process): plan-coverage plan, adversary loop closed --- docs/plans/2026-09-26-plan-coverage.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index a0ac2af..6190d91 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2,7 +2,7 @@ ticket: none date: 2026-09-26 status: draft -adversary: concerns +adversary: concerns (resolved 2026-09-26) spec: ../specs/2026-09-25-plan-coverage-design.md branch: feature/plan-coverage base: develop @@ -2210,3 +2210,11 @@ developer, and leave `sync-rules`, the version and both documents' - fixed 2026-09-26 — [Minor] Task 13 compares "each report" with its expectation, while `claude -p` prints the outer session's text, so a wrapper line before `model:` would fail a correct run; license: Task 13's stated purpose (the run proves what the card detects); Step 3 now compares the auditor's report from its `model:` line to its last line and discards wrapper text - signal 2026-09-26 — another round does not earn its cost; both leftovers are Minor, one held for the developer and one licensable, together one small fix wave rather than a re-read +### Loop closed — 2026-09-26 + +The developer closed the loop on 2026-09-26 without a fourth round, +after round 3's two Minors were fixed and a propagation gate returned +clean. Round 3 was the confirming full-document round, no ledger line is +`open` or `held`, and its stop signal judged another round not worth its +cost. + From 928f9e710abdc2b539d8ed096832e9e06c7c256a Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 16:36:48 +0200 Subject: [PATCH 062/143] docs(working-process): plan-coverage plan, second-opinion fixes --- docs/plans/2026-09-26-plan-coverage.md | 114 ++++++++++++++++++++----- 1 file changed, 94 insertions(+), 20 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 6190d91..4e0bbc9 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -1427,7 +1427,7 @@ git commit -m "feat(working-process): held coverage hits in the propagation gate reads are those Task 2 defines. - Produces: nothing a later task reads. -**Realizes:** D14 +**Realizes:** D14, D30 - [ ] **Step 1: Record the before values** @@ -1436,10 +1436,11 @@ F=plugins/working-process/agents/plan-adversary.md n() { tr -s '[:space:]' ' ' < "$1"; } echo "A $(grep -c '^### 6\. Realization of registered decisions$' $F)" echo "B $(n $F | grep -oF 'realize it in full together' | wc -l)" +echo "D $(n $F | grep -oF 'the finding says the question is the developer' | wc -l)" echo "C $(awk '/^### 5\./{f=1;print;next} f&&/^#/{exit} f' $F | shasum | cut -c1-7)" ``` -Expected: `A 0`, `B 0`, `C c518b54`. +Expected: `A 0`, `B 0`, `D 0`, `C c518b54`. - [ ] **Step 2: Add the dimension** @@ -1472,7 +1473,12 @@ a `**Defers:**` line is not judged; a task that quietly depends on the deferred decision anyway is a finding. Decisions inherited through a `**Follows:**` line are taken as settled, since you read plans rather than the code that realized them; a task that changes or undoes what an -inherited decision required is a finding against this plan. +inherited decision required is a finding against this plan. An +inherited citation of a decision the register has since withdrawn covers +nothing, and the withdrawal alone does not settle whether this plan +must undo what the predecessor built for it: judge the plan against the +decisions that stand, and where neither the withdrawal nor a successor +decision settles it, the finding says the question is the developer's. ## Output ``` @@ -1481,7 +1487,7 @@ inherited decision required is a finding against this plan. Run the Step 1 command again. -Expected: `A 1`, `B 1`, `C c518b54` — dimension 5 is unchanged +Expected: `A 1`, `B 1`, `D 1`, `C c518b54` — dimension 5 is unchanged (D28.2). The awk range stops at the next line opening with `#`, which is dimension 6's heading after this task and `## Output` before it. @@ -2106,11 +2112,19 @@ name, so the script disables it for the run and restores the state it found, whatever the run prints. Disabling changes the developer's own configuration: ask before running. -The inner session may dispatch an agent, read and search, and resolve -the repo root — nothing else. It runs with `--allowedTools` rather than -a broad grant, because the auditor would otherwise be free to execute -commands this plan quotes, `claude plugin disable` and `rm -rf` among -them. +The guard is the inner session's permission set, not a list of allowed +tools, because `--allowedTools` only pre-approves and never forbids. +`--setting-sources project` keeps the developer's user settings, and +every allow rule in them, out of the run; the fixture carries no +project settings, so the only pre-approval is the `git rev-parse` the +command line grants, and in print mode every other prompt is denied. +`--tools` narrows the built-in set to what the dispatch needs, and +`--add-dir` admits the plugin directory the card reads its rules from. +The auditor therefore cannot execute the commands this plan quotes, +`claude plugin disable` and `rm -rf` among them. + +Each run's full event stream is kept, so the evidence is the dispatch +itself rather than what the outer session chose to print. ```bash R=$(git rev-parse --show-toplevel) @@ -2119,27 +2133,77 @@ was=$(claude plugin list | grep -A3 'working-process@missing-bits' | grep -c 'St restore() { [ "$was" = 1 ] && claude plugin enable working-process; } trap restore EXIT [ "$was" = 1 ] && claude plugin disable working-process +k=0 for f in docs/plans/2026-09-26-plan-coverage.md docs/plans/fixture-uncovered.md \ docs/specs/2026-09-25-plan-coverage-design.md \ docs/technical-designs/fixture-technical-design.md; do - echo "##### $f" + k=$((k+1)) (cd "$W" && claude -p --plugin-dir "$R/plugins/working-process" \ - --allowedTools "Agent Read Grep Glob Bash(git rev-parse:*)" \ - "Dispatch the working-process:propagation-auditor agent on the haiku model over $f. Tell it to walk only duties 10 and 11 of its card and to report in the card's output shape. Print its report verbatim, nothing else.") -done > "$W/reports.txt" 2>&1 + --add-dir "$R/plugins/working-process" \ + --setting-sources project \ + --tools "Agent,Read,Grep,Glob,Bash" \ + --allowedTools "Bash(git rev-parse:*)" \ + --output-format stream-json --verbose \ + "Dispatch the working-process:propagation-auditor agent on the haiku model over $f. Tell it to walk only duties 10 and 11 of its card and to report in the card's output shape.") \ + > "$W/run-$k.jsonl" 2> "$W/run-$k.err" + echo "$f" > "$W/run-$k.target" +done restore; trap - EXIT -cat "$W/reports.txt" claude plugin list | grep -A3 'working-process@missing-bits' | grep 'Status:' ``` Expected: the last line shows the status the plugin had before the run. -- [ ] **Step 3: Compare each report with its expectation** +- [ ] **Step 3: Extract each report and compare it with its expectation** + +The report is the result of the agent dispatch inside each stream, not +the outer session's text. The extractor names the dispatch it found, +its agent type and model, every tool result that reports a denial, and +the report: + +```bash +W=${TMPDIR:-/tmp}/plan-coverage-dogfood +cat > "$W/extract.py" <<'EOF' +import json, sys +calls, report, denials = {}, None, [] +for line in open(sys.argv[1]): + try: + m = json.loads(line).get('message') or {} + except ValueError: + continue + content = m.get('content') + for c in content if isinstance(content, list) else []: + if c.get('type') == 'tool_use' and c.get('name') in ('Agent', 'Task'): + i = c.get('input') or {} + if i.get('subagent_type') == 'working-process:propagation-auditor': + calls[c['id']] = i.get('model') + if c.get('type') == 'tool_result': + r = c.get('content') + txt = r if isinstance(r, str) else ' '.join(x.get('text', '') for x in r or []) + if c.get('is_error') or 'denied' in txt.lower(): + denials.append(txt[:200]) + if c.get('tool_use_id') in calls: + report = txt +print('dispatches:', calls) +print('denials:', denials) +print('report:') +print(report) +EOF +for k in 1 2 3 4; do + echo "##### $(cat "$W/run-$k.target")" + python3 "$W/extract.py" "$W/run-$k.jsonl" +done | tee "$W/reports.txt" +``` + +For every run, expected: exactly one dispatch, of +`working-process:propagation-auditor` with model `haiku`; `denials: []`; +and a report. No dispatch, a denial, or no report fails the test, and +the stream stays for inspection. The event shapes are the CLI's own; an +extractor that finds nothing in a stream that plainly holds a dispatch +is a test defect to fix, never a pass. `CLEAN` here means no hit in duties 10 and 11 — the only checks that -ran. The text compared is the auditor's report, from its `model:` line -to its last line; any wrapper text the outer session prints before or -after it is discarded, not judged. With `N` from Task 12 Step 5: +ran. With `N` from Task 12 Step 5: - **this plan** — a summary line beginning `decision-coverage: ../specs/2026-09-25-plan-coverage-design.md N/N covered`, @@ -2166,11 +2230,13 @@ W=${TMPDIR:-/tmp}/plan-coverage-dogfood D="$R/.claude/working-process/2026-09-26-plan-coverage" { echo '# Task 13 — auditor runs on controlled cases, 2026-09-26'; echo; cat "$W/reports.txt"; } \ > "$D/task-13-auditor-runs.md" +mkdir -p "$D/task-13-streams" +command cp -f "$W"/run-*.jsonl "$W"/run-*.err "$D/task-13-streams/" command rm -rf "$W" ``` -The file is the test's evidence, not a dispatch record: audits write -none. It lives in the store, whose `*` `.gitignore` keeps it local. +The file and the streams beside it are the test's evidence, not a +dispatch record: audits write none. It lives in the store, whose `*` `.gitignore` keeps it local. - [ ] **Step 5: Report** @@ -2218,3 +2284,11 @@ clean. Round 3 was the confirming full-document round, no ledger line is `open` or `held`, and its stop signal judged another round not worth its cost. +After the close, a second opinion from Codex, relayed by the developer, +found three more defects. Their fixes landed after round 3, so no +adversary round has read them; the propagation gate that followed did. + +- fixed 2026-09-26 — [Important] Task 13's `--allowedTools` only pre-approves and forbids nothing, so the user settings' `Bash(claude plugin *)` still reached the auditor; ruling: 2026-09-26; the run now takes `--setting-sources project`, `--tools` and `--add-dir` for the plugin directory the card reads, and Step 2 says the guard is the permission set +- fixed 2026-09-26 — [Important] Task 13 kept the outer session's printed text, not evidence that the changed card ran; ruling: 2026-09-26; each run saves its `stream-json` event stream, and Step 3 extracts the dispatch's agent type, model, denials and report from it, keeping the streams as evidence +- fixed 2026-09-26 — [Important] Task 7 dropped part of D30: the spec has the plan-adversary judge whether a withdrawn inherited decision must be undone, and send it to the developer where nothing settles it; ruling: 2026-09-26; dimension 6 now says so, Task 7 cites D30, check D added + From de3c906f9190ead70799c927eb3b784f5a738bec Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 16:40:28 +0200 Subject: [PATCH 063/143] docs(working-process): approve plan-coverage plan --- docs/plans/2026-09-26-plan-coverage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 4e0bbc9..177e797 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -1,7 +1,7 @@ --- ticket: none date: 2026-09-26 -status: draft +status: approved adversary: concerns (resolved 2026-09-26) spec: ../specs/2026-09-25-plan-coverage-design.md branch: feature/plan-coverage From dd546814d1704825e309a15b6b3a4ad5b3113b9f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 17:21:28 +0200 Subject: [PATCH 064/143] docs(working-process): plan-coverage spec, third audit dispositions --- docs/plans/2026-09-26-plan-coverage.md | 121 +++++---- docs/specs/2026-09-25-plan-coverage-design.md | 236 +++++++++++------- 2 files changed, 221 insertions(+), 136 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 177e797..5f662af 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -315,13 +315,13 @@ reserves its identifier, so a reuse shows up as a duplicate. `replaced by` names an identifier of the same register other than its own, and a chain of successors never forms a cycle; a successor may itself be withdrawn. The token is recognized by its opening word alone: -the paragraph's final segment — after the full stop that ends the -statement and after any "Argued in" pointer — beginning with -`withdrawn`, and running to the paragraph's end, so its reason may hold -a full stop. A segment so opening that does not match the grammar is -malformed, never prose. Withdrawing a group is written on each of its -leaves. A decision that still stands but one plan does not realize is -no state of the entry: that plan defers it (see *Plan annotations*). +the first segment after the full stop that ends the statement, and after +any "Argued in" pointer, that begins with `withdrawn`; it runs to the +paragraph's end, so its reason may hold a full stop. A segment so +opening that does not match the grammar is malformed, never prose. +Withdrawing a group is written on each of its leaves. A decision that +still stands but one plan does not realize is no state of the entry: +that plan defers it (see *Plan annotations*). A withdrawal changes what the spec decides, so the session writes the token only on the developer's explicit decision. Its `ruling:` is dated @@ -450,7 +450,7 @@ and that entry opens with the same annotation as its first clause — `**Realizes:** D9`. Only an entry that realizes a decision carries one, and `none` is never written there. Citing the section as a whole realizes nothing, and neither does citing a group: its leaves are what -a plan cites. +a plan cites. `none` names no identifier, so it is never qualified. A plan whose `spec:` names one spec writes bare identifiers. A plan whose `spec:` names two or more — registered or legacy alike — qualifies @@ -477,7 +477,9 @@ its `ruling:` dates. The deferral binds this plan alone: auditing any other plan of the same spec, the decision counts, and the spec names no plan. Deferring an undefined, withdrawn or group identifier is malformed, and so is deferring one the plan also cites, locally or by -inheritance. +inheritance. A plan with neither kind of line carries no such +section, and in the pass for one spec a `**Defers:**` line naming +another spec's identifier is out of scope. A `**Follows:**` line names a plan this one continues, by a path relative to the plan. The predecessor shares at least one spec with @@ -1065,26 +1067,29 @@ describes the grammar. For each spec the plan's `spec:` names: carrying a state token; a `replaced by` naming a missing identifier, its own, or a group, or closing a cycle. Where any fires, the counted set cannot be trusted: write `not counted` and stop. -3. Collect the counted set: the leaf identifiers not withdrawn, less - those the plan's `**Defers:**` lines name. +3. Check the plan's `**Follows:**` lines. One naming a missing file, a + plan sharing no spec with the audited plan, or a plan not at + `status: implemented` is a hit, and lends nothing to the steps + below. A predecessor lends identifiers only for the specs both plans + name; in the pass for a spec it does not name, its line is out of + scope rather than a hit. 4. Collect the cited set: every identifier the plan's `**Realizes:**` annotations cite, on tasks and Global Constraints entries, and every - identifier inherited through its `**Follows:**` lines, each with the - plan and site it comes from. A cited withdrawn identifier, a cited - group and a cited identifier the register does not define are hits. - The last is an error in the plan, never a gap in the spec — - identifiers are minted only in the register — so duty 5 does not - take it. An inherited citation of an identifier withdrawn since its - predecessor shipped is skipped: not counted, not a hit, and listed - apart in the report. -5. Check the plan's other lines. A `**Defers:**` line naming an - undefined, withdrawn or group identifier, or one also in the cited - set, is a hit. A `**Follows:**` line naming a missing file, a plan - sharing no spec with the audited plan, or a plan not at - `status: implemented` is a hit. A predecessor lends identifiers only - for the specs both plans name; in the pass for a spec it does not - name, its line is out of scope rather than a hit. -6. Report every counted identifier the cited set lacks, one hit per + identifier inherited through the `**Follows:**` lines step 3 + accepted, each with the plan and site it comes from. A cited + withdrawn identifier, a cited group and a cited identifier the + register does not define are hits. The last is an error in the plan, + never a gap in the spec — identifiers are minted only in the + register — so duty 5 does not take it. An inherited citation of an + identifier withdrawn since its predecessor shipped is skipped: not + counted, not a hit, and listed apart in the report. +5. Check the plan's `**Defers:**` lines. One naming an undefined, + withdrawn or group identifier, or one in the cited set, is a hit and + subtracts nothing. In the pass for one spec, a line naming another + spec's identifier is out of scope. +6. Collect the counted set: the leaf identifiers not withdrawn, less + those the `**Defers:**` lines step 5 accepted name. +7. Report every counted identifier the cited set lacks, one hit per identifier. A task with no `**Realizes:**` line, in a plan whose `spec:` names a registered spec, is a hit too, and so is a bare identifier in a plan whose `spec:` names two or more specs. @@ -1159,8 +1164,8 @@ On a plan, one `decision-coverage:` block per spec its `spec:` names: D2 → ../plans/.md (Task 3) D3 → Task 4, Task 6 D4.2 → — - D9 → Global Constraints: "No code" - D10 → ../plans/.md (Global Constraints: "No code") + D9 → Global Constraints, line 42: "No code" + D10 → ../plans/.md (Global Constraints, line 38: "No code") … withdrawn since: D4 ← ../plans/.md (Task 2) decision-coverage: not counted — malformed register @@ -1169,17 +1174,20 @@ On a plan, one `decision-coverage:` block per spec its `spec:` names: The summary line opens the block. Under a counted spec the map follows, one indented line per counted identifier naming every task and constraint that cites it: a task by its heading's number, a Global -Constraints entry by its opening words in quotes, an inherited site by -its plan's path with the site in parentheses. An uncovered identifier -maps to `—` and is a hit above as well. The fraction's denominator is -duty 10's counted set, and its numerator the counted identifiers in the -cited set. `inherited [..]` lists the covered identifiers whose only -citation is a predecessor's; one also cited locally counts as local, -and its map line names both sites. `deferred [..]` lists what the plan -defers, never counted as covered. A skipped inherited citation follows -the map on a `withdrawn since:` line, outside the fraction. Qualify -every identifier as the plan's annotations are. `not counted` carries no -map, since its denominator cannot be derived. +Constraints entry by the line it opens on and its opening words after +the annotation, in quotes, and an inherited site by its plan's path with +the site in parentheses. An uncovered identifier maps to `—` and is a +hit above as well. The fraction's denominator is duty 10's counted set, +and its numerator the counted identifiers in the cited set; a counted +set of zero reads `0/0 covered`. The summary line carries every slot, in +the example's order, an empty one written `[]`. `inherited [..]` lists +the covered identifiers whose only citation is a predecessor's; one also +cited locally counts as local, and its map line names both sites. +`deferred [..]` lists what the plan defers, never counted as covered. A +skipped inherited citation follows the map on a `withdrawn since:` line, +outside the fraction. Qualify every identifier as the plan's annotations +are. `not counted` carries no map, since its denominator cannot be +derived. On a design spec audited alone, one line and no map: @@ -2144,7 +2152,7 @@ for f in docs/plans/2026-09-26-plan-coverage.md docs/plans/fixture-uncovered.md --tools "Agent,Read,Grep,Glob,Bash" \ --allowedTools "Bash(git rev-parse:*)" \ --output-format stream-json --verbose \ - "Dispatch the working-process:propagation-auditor agent on the haiku model over $f. Tell it to walk only duties 10 and 11 of its card and to report in the card's output shape.") \ + "Dispatch the working-process:propagation-auditor agent on the haiku model over $f, in the foreground, and wait for its report. Tell it to walk only duties 10 and 11 of its card and to report in the card's output shape.") \ > "$W/run-$k.jsonl" 2> "$W/run-$k.err" echo "$f" > "$W/run-$k.target" done @@ -2156,22 +2164,30 @@ Expected: the last line shows the status the plugin had before the run. - [ ] **Step 3: Extract each report and compare it with its expectation** -The report is the result of the agent dispatch inside each stream, not -the outer session's text. The extractor names the dispatch it found, -its agent type and model, every tool result that reports a denial, and -the report: +The report comes from the agent dispatch inside each stream, not from +the outer session's text. The card runs in the background by default, +so the dispatch's own tool result may only acknowledge the launch: the +extractor takes the report from the dispatched agent's last message, +marked by its `parent_tool_use_id`, and falls back to the tool result. +It names the dispatch it found, its agent type and model, every tool +result that reports a denial, and the report: ```bash W=${TMPDIR:-/tmp}/plan-coverage-dogfood cat > "$W/extract.py" <<'EOF' import json, sys -calls, report, denials = {}, None, [] +calls, report, own, denials = {}, None, None, [] for line in open(sys.argv[1]): try: - m = json.loads(line).get('message') or {} + e = json.loads(line) except ValueError: continue + m = e.get('message') or {} content = m.get('content') + if e.get('parent_tool_use_id') in calls and m.get('role') == 'assistant': + text = ' '.join(c.get('text', '') for c in content or [] if isinstance(c, dict)) + if text.strip(): + own = text for c in content if isinstance(content, list) else []: if c.get('type') == 'tool_use' and c.get('name') in ('Agent', 'Task'): i = c.get('input') or {} @@ -2187,7 +2203,7 @@ for line in open(sys.argv[1]): print('dispatches:', calls) print('denials:', denials) print('report:') -print(report) +print(own or report) EOF for k in 1 2 3 4; do echo "##### $(cat "$W/run-$k.target")" @@ -2292,3 +2308,12 @@ adversary round has read them; the propagation gate that followed did. - fixed 2026-09-26 — [Important] Task 13 kept the outer session's printed text, not evidence that the changed card ran; ruling: 2026-09-26; each run saves its `stream-json` event stream, and Step 3 extracts the dispatch's agent type, model, denials and report from it, keeping the streams as evidence - fixed 2026-09-26 — [Important] Task 7 dropped part of D30: the spec has the plan-adversary judge whether a withdrawn inherited decision must be undone, and send it to the developer where nothing settles it; ruling: 2026-09-26; dimension 6 now says so, Task 7 cites D30, check D added +The spec's third integrity audit changed three prescribed texts, all +recorded in the spec's ledger under +`### 2026-09-26 — integrity audit, fable, at the consumption gate (third)`: + +- fixed 2026-09-26 — the state-token recognizer named the paragraph's final segment; license: the spec's *The register*, as fixed there; Task 1 now names the first segment after the statement that begins with `withdrawn` +- fixed 2026-09-26 — duty 10 inherited through unvalidated `**Follows:**` lines and subtracted unvalidated `**Defers:**` lines, and the map keyed a Global Constraints entry by the annotation; ruling: 2026-09-26; Task 5 reorders the steps, keys the entry by its line and its words after the annotation, and states the summary slots and `0/0 covered` +- fixed 2026-09-26 — the plan annotations did not say that `none` is never qualified, that an empty section is absent, or that another spec's `**Defers:**` line is out of scope; license: the spec's *The plan's annotations* and *Deferral*, as fixed there; Task 2 says so +- fixed 2026-09-26 — Task 13's extractor took the report from the dispatch's tool result, which for a background card may only acknowledge the launch; license: Task 13's stated purpose; the prompt asks for a foreground dispatch and the extractor reads the dispatched agent's last message, falling back to the tool result + diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 6409395..d931b0f 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -103,7 +103,9 @@ What stands today: - **D12** — A coverage hit stays a hit, but its fix is triaged by license, and a fix needing a decision the spec does not make waits for the developer. The exception to "hits never wait" covers the - coverage duty's hits alone. Argued in *Disposing of a coverage hit*. + coverage duty's hits alone. A held hit ends its gate episode, and the + re-run after the developer's answer is a fresh episode. Argued in + *Disposing of a coverage hit*. - **D13** — The lifecycle rule records a held hit in two independent ways. Argued in *Gate lines for a held hit*. - **D13.1** — Four gate-line shapes: `hit held`, and its three @@ -115,8 +117,11 @@ What stands today: - **D14** — The plan-adversary gains a dimension judging each decision's realization: do the tasks and constraints citing it realize it in full together, and does a cited constraint actually - bind. A decision realized only in part is an Important finding. - Argued in *Readers*. + bind. A decision realized only in part is an Important finding. The + dimension also judges a task that quietly depends on a deferred + decision, one that changes or undoes what an inherited decision + required, and whether a withdrawn inherited decision must be undone. + Argued in *Readers*, *Deferral* and *Sequential plans*. - **D15** — The integrity auditor checks the register's completeness through its existing first lens, prompted by one sentence in its card. Argued in *Readers*. @@ -128,7 +133,9 @@ What stands today: and the row omits. - **D17** — A spec without the `decisions:` field is reported as `not checked` and yields no hit; existing specs migrate at a - substantive revision, never in a sweep. Argued in *Legacy specs*. + substantive revision, never in a sweep. Every new design spec is + expected to set the field, and its absence is reported, not refused. + Argued in *Legacy specs*. - **D18** — A plan that leaves a registered decision unrealized says so in a `**Defers:**` line, one per decision, carrying the developer's ruling; the deferral binds that plan alone, and the spec names no @@ -143,15 +150,17 @@ What stands today: - **D21** — The technical-design rule carries the table-closure declarations, under one fixed heading, as their only home. Argued in *Table closure*. -- **D22** — On a registered design spec audited on its own, the coverage - duty checks the register alone and the report says whether it is well - formed. Argued in *The coverage duty* and *The report*. +- **D22** — On a design spec audited on its own, the coverage duty + checks the register alone, and the report says whether it is well + formed, not counted, or not checked. Argued in *The coverage duty* + and *The report*. - **D23** — A pre-round gate episode that holds a hit writes all its lines before the first round heading, where they stay. Argued in *Gate lines for a held hit*. -- **D24** — Withdrawing a decision of an `implemented` spec is a - revision through a newer spec's `revises:`, never an edit of the - frozen body. Argued in *Entry states*. +- **D24** — Withdrawing, repairing, sharpening or adding a decision of + an `implemented` spec is a revision through a newer spec's + `revises:`, never an edit of the frozen body. Argued in *Entry + states* and *Disposing of a coverage hit*. - **D25** — A plan whose `spec:` names two or more specs, registered or legacy, qualifies every identifier with its spec's path. Argued in *The plan's annotations*. @@ -203,10 +212,10 @@ across lines keeps its state token, which stands at the paragraph's end. Prose beneath, after the blank line, is free. A state token is recognized by its reserved opening word alone: the -paragraph's final segment, after the full stop that ends the decision's -statement — and after any "Argued in" pointer, which belongs to the -statement — beginning with `withdrawn`. The token runs from that word to -the paragraph's end, so its `` may hold a full stop. A segment +first segment after the full stop that ends the decision's statement — +and after any "Argued in" pointer, which belongs to the statement — that +begins with `withdrawn`. The token runs from that word to the +paragraph's end, so its `` may hold a full stop. A segment so opening that does not match the token's grammar is a hit, never prose. Any other text is prose, so a statement whose own words happen to end in "withdrawn" @@ -327,7 +336,7 @@ shared, and only the place differs. Only an entry that realizes a decision carries the annotation; an entry without one is no hit, and `none` is never written there, since a constraint that realizes nothing needs no statement of it. Citing the section as a whole realizes -nothing. +nothing. `none` names no identifier, so it is never qualified. A plan whose `spec:` names one spec uses bare identifiers. A plan whose `spec:` names two or more — registered or legacy alike — qualifies each @@ -348,10 +357,10 @@ This extends the plan template, and the lifecycle rule currently says the plugin does not: "This binds how the blocks are filled and changes no plan template — the template belongs to the tool that writes plans." The sentence is revised to name the exception: working-process adds the -`**Realizes:**` annotation on tasks and constraint entries, the -`**Defers:**` lines of *Deferral* and the `**Follows:**` lines of -*Sequential plans*, and the rest of the template stays with the tool -that writes plans. +`**Realizes:**` annotation on tasks and constraint entries, and the +`## Deferrals and predecessors` heading with the `**Defers:**` lines of +*Deferral* and the `**Follows:**` lines of *Sequential plans*; the rest +of the template stays with the tool that writes plans. The plan's author writes the annotations, whoever that author is, and the requirement lives in the lifecycle rule; the tool that writes plans @@ -382,7 +391,9 @@ of that section because the plan template makes every task's requirements include it, and these lines state facts about the whole plan rather than requirements of any task. Each carries the developer's ruling, and the session writes it only on the developer's -explicit decision. +explicit decision. A plan with neither kind of line carries no such +section. In the pass for one spec, a `**Defers:**` line naming another +spec's identifier is out of scope, as a `**Follows:**` line is. The plan is the right home for three reasons. The deferral is a fact about this plan — the decision still stands — so it binds this plan @@ -444,7 +455,7 @@ hidden either — the report lists it after the map, outside the counted identifiers and the fraction, as `withdrawn since: D4 ← ../plans/.md (Task 2)`. The exception covers inheritance alone: a `**Realizes:**` in the later plan that cites the withdrawn identifier -directly is still a hit. A `replaced by` successor inherits no coverage; +directly is still a hit. A `replaced by` successor inherits nothing; it is counted and needs its own realization. Whether the later plan must undo what the predecessor built for the withdrawn decision is not decided by the withdrawal itself: the plan-adversary judges the later @@ -484,25 +495,31 @@ A tenth propagation duty runs on a plan. For each spec the plan's a state token; a `replaced by` naming a missing identifier, its own, or a group, or closing a cycle. Where any of them fires, the counted set cannot be trusted: report `not counted` and stop for that spec. -3. Collect the counted set: the leaf identifiers that are not - withdrawn, less those the audited plan's `**Defers:**` lines name. +3. Check the plan's `**Follows:**` lines as *Sequential plans* says. A + line that is a hit lends nothing to the steps below. 4. Collect the cited set: every identifier the plan's `**Realizes:**` annotations cite, on tasks and constraint entries, and every - identifier inherited through the plan's `**Follows:**` lines, each - with the plan it comes from. A cited withdrawn identifier and a - cited group are hits — except an inherited citation of an identifier - withdrawn since, which *Sequential plans* skips — and so is a cited - identifier the register does not define. That last is an error in - the plan, never a gap in the spec: identifiers are minted only in - the register, so a plan citing one the register lacks has mistyped - or invented it. Duty 5 does not take it, since duty 5 reads an - undefined name as a gap in its source. -5. Check the plan's `**Defers:**` lines as *Deferral* says and its - `**Follows:**` lines as *Sequential plans* says, against the cited - set of step 4. -6. Report every counted identifier the cited set lacks: one hit per + identifier inherited through the `**Follows:**` lines step 3 + accepted, each with the plan it comes from. A cited withdrawn + identifier and a cited group are hits — except an inherited citation + of an identifier withdrawn since, which *Sequential plans* skips — + and so is a cited identifier the register does not define. That + last is an error in the plan, never a gap in the spec: identifiers + are minted only in the register, so a plan citing one the register + lacks has mistyped or invented it. Duty 5 does not take it, since + duty 5 reads an undefined name as a gap in its source. +5. Check the plan's `**Defers:**` lines as *Deferral* says, against the + register and the cited set of step 4. A line that is a hit subtracts + nothing. +6. Collect the counted set: the leaf identifiers that are not + withdrawn, less those the `**Defers:**` lines step 5 accepted name. +7. Report every counted identifier the cited set lacks: one hit per identifier. +A line that is a hit gains the plan nothing: an invalid predecessor +covers no decision, and an invalid deferral excuses none, while the hit +itself stays in the report. + The decision coverage map is derived, never tallied. The auditor writes the map from identifier to citing sites, and the fraction is that map's summary; a bare count would pass one omission offset by one duplicate. A @@ -511,7 +528,7 @@ a hit. An identifier cited by several tasks is legal — one decision, many tasks — and a task split or merged in a fix wave passes as long as the union of its identifiers survives. -On a registered design spec audited on its own — the gate before its +On a design spec audited on its own — the gate before its architect round — the duty runs steps 1 and 2 alone (D22), so a malformed register is found before any plan depends on it; *The report* gives the line it writes. @@ -522,12 +539,12 @@ belongs to the integrity auditor (see *Readers*). ## Table closure -An eleventh propagation duty, independent of coverage. It answers "does -the named element exist?", where coverage answers "does every required -element have an owner?" — correct references can coexist with a missed -decision, so neither subsumes the other. Measured on 2026-09-16: walked -duty by duty, none of the nine current duties checks referential -integrity between two tables of one document. +An eleventh propagation duty, independent of the coverage duty. It +answers "does the named element exist?", where the coverage duty answers +"does every required element have an owner?" — correct references can +coexist with a missed decision, so neither subsumes the other. Measured +on 2026-09-16: walked duty by duty, none of the nine current duties +checks referential integrity between two tables of one document. The duty checks declared relations only, never guessed ones. Matching cell values would be a false detector: two unrelated columns can share @@ -573,7 +590,9 @@ The count names the declared relations the duty resolved in that document — those whose tables it carries; a relation whose table is absent, as a conditional State section may be, is not applicable there and is not counted. The hits, if any, stand above. A document other -than a technical design gets no line, since no relation binds it. +than a technical design gets no line, since no relation binds it — a +plan included, whatever technical design its `technical-design:` +names. A table no rule declares a relation for is not checked by this duty, and nothing says it was: the report states what the duty covers, and a @@ -594,29 +613,35 @@ and before the token, and present whether or not any hit fired: D2 → ../plans/.md (Task 3) D3 → Task 4, Task 6 D4.2 → — - D9 → Global Constraints: "No code" - D10 → ../plans/.md (Global Constraints: "No code") + D9 → Global Constraints, line 42: "No code" + D10 → ../plans/.md (Global Constraints, line 38: "No code") … decision-coverage: not counted — malformed register decision-coverage: not checked — no decision register The summary line opens the block, and under a counted spec the map follows it, one indented line per counted identifier, naming every task -and constraint that cites it. A task is named by its heading's number; -a Global Constraints entry, which the template gives no key, by its -opening words in quotes; an inherited site, by its plan's path with the -site in parentheses. An uncovered identifier maps to `—` and is also a -hit above. The map is what the fraction summarises, so a -reader can check the one against the other. The fraction's denominator -is the counted set of step 3, and its numerator every counted -identifier in the cited set of step 4. `inherited [..]` lists the -covered identifiers whose only citation comes from a predecessor — an -identifier realized locally as well counts as local and is not listed -there — so the fraction can be checked without the map. Identifiers the -audited plan defers are listed apart and never counted as covered. -Every identifier in the block is qualified as the plan's annotations -are. `not counted` follows a malformed register, whose hits stand above -it, and carries no map, since its denominator cannot be derived. +and constraint that cites it. A task is named by its heading's number; a +Global Constraints entry, which the template gives no key, by the line +it opens on and its opening words after the annotation, in quotes; an +inherited site, by its plan's path with the site in parentheses. The +line number tells two entries apart, and since the report is derived +afresh on every run it need not be a stable identifier. An uncovered +identifier maps to `—` and is also a hit above. The map is what the +fraction summarises, so a reader can check the one against the other. +The fraction's denominator is the counted set of step 6, and its +numerator every counted identifier in the cited set of step 4. The +summary line always carries every slot, in the example's order, an empty +one written `[]`; a counted set of zero reads `0/0 covered`, the result +of an empty check, never a claim that the register is complete. +`inherited [..]` lists the covered identifiers whose only citation comes +from a predecessor — an identifier realized locally as well counts as +local and is not listed there — so the fraction can be checked without +the map. Identifiers the audited plan defers are listed apart and never +counted as covered. Every identifier in the block is qualified as the +plan's annotations are. `not counted` follows a malformed register, +whose hits stand above it, and carries no map, since its denominator +cannot be derived. On a design spec audited on its own, the block is one line (D22) and no map follows, since no plan cites anything yet — `register well formed` @@ -744,19 +769,20 @@ as it treats any line carrying `ruling:`. Placement follows the existing rule, with one exception. A gate after a round writes its lines under that round's heading. A gate before a -plan's first round has no heading to write under, and the existing rule -makes its lines wait for that round's stamp — which a held hit cannot -do, since the round it waits for cannot start. A pre-round gate +document's first round has no heading to write under, and the existing +rule makes its lines wait for that round's stamp — which a held hit +cannot do, since the round it waits for cannot start. A pre-round gate episode that holds at least one hit therefore writes every one of its -lines — the `hit held` line and its siblings alike — in the -`## Review rounds` section before the first round heading, and they -stay there, rewrites included: no round produced them, and one episode -keeps one home (D23). Once such lines stand there, every later -pre-round episode writes there too, holding or not, so the document's -pre-round gate history stays in one place. A pre-round episode on a -document with no pre-round lines, holding nothing, keeps the existing -rule. A plan that has no `## Review rounds` section yet gains one, at -its end, when its first gate line is written. +lines — the `hit held` line and its siblings alike — in the `## Review +rounds` section before the first round heading, and they stay there, +rewrites included: no round produced them, and one episode keeps one +home (D23). Once such lines stand there, every later pre-round episode +writes there too, holding or not, so the document's pre-round gate +history stays in one place. A pre-round episode on a document with no +pre-round lines, holding nothing, keeps the existing rule. A document +that has no `## Review rounds` section yet gains one, at its end, when +its first gate line is written — a design spec audited alone as well as +a plan. `hit held` joins the Unfinished-work list by widening the *Unfinished review-loop ledger* entry (D13.2): its description becomes "a @@ -847,19 +873,25 @@ design spec and its absence is reported, not refused. ## Out of scope -- The technical design's side of coverage — a Scope table mapping - decisions to parts, contracts or state. It needs row keys the +- The technical design's side of decision coverage — a Scope table + mapping decisions to parts, contracts or state. It needs row keys the Contracts table and the Failure section do not have, and no measured failure calls for it yet. Dimension 5 stays as it is. - Enforcing the register project-wide (see *Legacy specs*). - A shape for a hit left outstanding when the gate's re-dispatch bound stops an episode. The lifecycle rule names that gap already; `hit - held` is scoped to coverage hits by D12 and does not close it. The - two share a shape but not a question: a held coverage hit waits on a - decision the spec lacks, while an outstanding hit marks a gate that - failed to converge, and folding the second in would widen D12's - exception beyond the case it was argued for. + held` is scoped to the coverage duty's hits by D12 and D26 and does + not close it. The two share a shape but not a question: a held hit + waits on a decision the spec lacks, while an outstanding hit marks a + gate that failed to converge, and folding the second in would widen + D12's exception beyond the case it was argued for. - Migrating this repository's existing specs. +- Carrying decision coverage across a revision. A spec revised through + `revises:` is a new register: a plan of the newer spec shares no spec + with a plan of the older one, so it inherits nothing, and repeating an + identifier in the newer register does not establish that it is the + same decision. Carrying identity across a revision needs a contract + of its own. ## Changes by file @@ -867,10 +899,10 @@ All under `plugins/working-process/` unless noted. - `agents/propagation-auditor.md` — duties 10 (coverage) and 11 (table closure, naming the rule heading that holds the declarations); the - `decision-coverage:` block, its `inherited [..]` slot and the three - outcomes on a spec audited alone (`register well formed`, `not - counted`, `not checked`); the `table-closure:` line; the "exactly - two lines" sentence; the + `decision-coverage:` block, its `inherited [..]` slot, its + `withdrawn since` listing and the three outcomes on a spec audited + alone (`register well formed`, `not counted`, `not checked`); the + `table-closure:` line; the "exactly two lines" sentence; the coverage exception in *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. - `agents/integrity-auditor.md` — one sentence naming the register as a @@ -888,10 +920,10 @@ All under `plugins/working-process/` unless noted. how their cells split, the `external:` value, and that a numbered sequence is outside the relation. - `rules/workflow.md` — the coverage duty's exception to "hits never - wait"; the `decision-coverage:` and `table-closure:` lines beside the - closing-token paragraph; in - step 4, the duty of a brief delegating plan-writing to name the - lifecycle rule and require reading it. + wait", and a held hit ending its gate episode; the + `decision-coverage:` and `table-closure:` lines beside the + closing-token paragraph; in step 4, the duty of a brief delegating + plan-writing to name the lifecycle rule and require reading it. - `rules/propagation-duties.md` — the rows and counters of *The author's checklist*. - `skills/grilling-session/SKILL.md` — updating the register as @@ -997,3 +1029,31 @@ unaudited. Eight defects and twelve implementer questions. - fixed 2026-09-26 — a non-coverage hit of the coverage duty needing a decision had no shape to wait in; ruling: 2026-09-26; D26 now holds any hit of the coverage duty whose fix needs a decision no derivation settles, the held line lives in the audited document's ledger, `hit fixed … ruling:` covers a repaired register, and the re-run gate covers the changed document and what depends on it - fixed 2026-09-26 — implementer question 3, an inherited citation of an identifier withdrawn since; ruling: 2026-09-26; D30 added — skipped, listed apart as `withdrawn since`, never covering, a direct citation still a hit, a successor needing its own realization, undoing judged against the standing decisions, and a `revises:` withdrawal kept out of the case - fixed 2026-09-26 — implementer questions 4–12; license: the report contract, the lifecycle rule's gate lines and ledger section, and the template's task shape; map line shapes, the clean shape of every audited document class, the relation count, the annotation's place before the first step, the section a plan gains at its first gate line, the widened Unfinished-work entry, the token's place after the pointer, later pre-round episodes and the `table-closure:` placement are now stated + +### 2026-09-26 — integrity audit, fable, at the consumption gate (third) + +Coverage tell: 999 lines read, highest line cited 999 — a +whole-document read after the D13.2 edit left the second audit's stamp +stale. Twelve defects and fourteen implementer questions; the quotes +were checked against the spec before disposition. The plan changes they +cause are recorded in `../plans/2026-09-26-plan-coverage.md`. + +- fixed 2026-09-26 — the template sentence in *The plan's annotations* omitted the `## Deferrals and predecessors` heading D8 names; license: D8; the heading is named +- fixed 2026-09-26 — a Global Constraints entry was named by opening words that are the annotation itself; ruling: 2026-09-26; the map names the entry by its line and its words after the annotation (implementer question 5) +- fixed 2026-09-26 — step 4 inherited through `**Follows:**` lines step 5 had not yet accepted, and step 3 subtracted `**Defers:**` lines step 5 might reject; ruling: 2026-09-26; the steps now run predecessors, citations, deferrals, counted set, report, and a line that is a hit lends or subtracts nothing (implementer question 1) +- fixed 2026-09-26 — the state-token recognizer named the final segment while the reason may hold a full stop; license: *The register* ("The token runs from that word to the paragraph's end"); the first segment after the statement that begins with `withdrawn` (implementer question 2) +- fixed 2026-09-26 — D22 and *The coverage duty* scoped the spec-alone run to a registered spec while the report lists `not checked`; license: D11.3; both now say any design spec +- fixed 2026-09-26 — *Out of scope* scoped `hit held` to coverage hits while D26 holds any hit of the coverage duty; license: D26; reworded +- fixed 2026-09-26 — D12, D14, D17 and D24 did not carry parts of the decisions the text assigns them; license: the register's declared rule; each entry sharpened under its own identifier — the held hit's episode, the adversary's three further judgments, the field expected in new specs, every register change to an implemented spec by revision +- fixed 2026-09-26 — *Changes by file* omitted the `withdrawn since` listing and the held hit's episode in the workflow rule; license: D30 and D12; both named +- fixed 2026-09-26 — bare "coverage" named the duty or the map in three places; license: glossary **Decision coverage** `_Avoid_`; reworded +- fixed 2026-09-26 — implementer questions 3, 6, 9, 13; license: Deviation 9's ruling in the plan, *Deferral*, *Table closure*, the per-spec pass; a document of either kind gains the section, an empty section is absent, a plan gets no `table-closure:` line, `none` is never qualified and another spec's `**Defers:**` line is out of scope +- fixed 2026-09-26 — implementer questions 4 and 7; ruling: 2026-09-26; the summary line carries every slot, empty ones as `[]`, and `0/0 covered` for an empty counted set; carrying decision coverage across a revision is out of scope +Implementer questions 10, 11, 12 and 14 need no change to the text: D1 +and D15, like D12 and D26, name different surfaces of one decision and +a plan may cite both; the `←` arrow marks a citation that covers +nothing; the template always carries `**Files:**`; and the second +audit's questions 1 and 2 were disposed as its D26 and D3 lines. + +- held — implementer question 8: where a withdrawal the developer rules outside any hit lands in the ledger once the spec's loop has closed; question: record it only as the tombstone on a closed loop, not in the ledger?; options: (b) on a closed loop, not `implemented`, and for a withdrawal tied to no hit or finding, the tombstone is the record and no ledger line is written, an open line still closing on its own — a new loop reads the whole document, and the stale stamp signals the change (recommended, and Codex's second opinion agrees); (a) a new heading `### — ruling`; (c) `fix from` the spec's own path + From 137ad6cd4e3ad150c608573c2706a4b689b9faef Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 17:21:35 +0200 Subject: [PATCH 065/143] docs(working-process): plan-coverage spec, ledger spacing --- docs/specs/2026-09-25-plan-coverage-design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index d931b0f..0448823 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -1049,6 +1049,7 @@ cause are recorded in `../plans/2026-09-26-plan-coverage.md`. - fixed 2026-09-26 — bare "coverage" named the duty or the map in three places; license: glossary **Decision coverage** `_Avoid_`; reworded - fixed 2026-09-26 — implementer questions 3, 6, 9, 13; license: Deviation 9's ruling in the plan, *Deferral*, *Table closure*, the per-spec pass; a document of either kind gains the section, an empty section is absent, a plan gets no `table-closure:` line, `none` is never qualified and another spec's `**Defers:**` line is out of scope - fixed 2026-09-26 — implementer questions 4 and 7; ruling: 2026-09-26; the summary line carries every slot, empty ones as `[]`, and `0/0 covered` for an empty counted set; carrying decision coverage across a revision is out of scope + Implementer questions 10, 11, 12 and 14 need no change to the text: D1 and D15, like D12 and D26, name different surfaces of one decision and a plan may cite both; the `←` arrow marks a citation that covers From 003a8397ef91a2dde0f99a2697732c04af8e1c45 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 17:32:36 +0200 Subject: [PATCH 066/143] docs(working-process): plan-coverage spec and plan, withdrawal record on a closed loop --- docs/plans/2026-09-26-plan-coverage.md | 15 ++++++++++----- docs/specs/2026-09-25-plan-coverage-design.md | 14 ++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 5f662af..822633b 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -210,9 +210,10 @@ echo "E $(n $F | grep -oF 'the register lists every decision in this spec that n echo "F $(n $F | grep -oF 'never in a sweep' | wc -l)" echo "G $(n $F | grep -oF 'a newer design spec carrying `revises:`' | wc -l)" echo "H $(grep -c '^## Finding what revises a document$' $F)" +echo "I $(n $F | grep -oF 'the tombstone is its record' | wc -l)" ``` -Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 0`, `G 0`, `H 1`. +Expected: `A 0`, `B 0`, `C 0`, `D 0`, `E 0`, `F 0`, `G 0`, `H 1`, `I 0`. - [ ] **Step 2: Add the field to the frontmatter block** @@ -326,9 +327,12 @@ that plan defers it (see *Plan annotations*). A withdrawal changes what the spec decides, so the session writes the token only on the developer's explicit decision. Its `ruling:` is dated with that decision; the edit lands in the spec's own ledger and leaves -the `integrity:` stamp stale, as any body edit does. A spec already -`implemented` takes no such edit: the decision is withdrawn by a newer -design spec carrying `revises:` and the register that stands. +the `integrity:` stamp stale, as any body edit does. On a spec whose +loop has closed, a withdrawal ruled outside any hit or finding writes +no ledger line: the tombstone is its record, since no diff-scoped round +follows a close. A spec already `implemented` takes no such edit: the +decision is withdrawn by a newer design spec carrying `revises:` and the +register that stands. The spec's author creates the register while writing the spec, so a design that never meets a grilling session still has one, and a @@ -352,7 +356,7 @@ refused. Run the Step 1 command again. -Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 1`, `G 1`, `H 1`. +Expected: `A 1`, `B 1`, `C 1`, `D 1`, `E 1`, `F 1`, `G 1`, `H 1`, `I 1`. - [ ] **Step 6: Commit** @@ -2315,5 +2319,6 @@ recorded in the spec's ledger under - fixed 2026-09-26 — the state-token recognizer named the paragraph's final segment; license: the spec's *The register*, as fixed there; Task 1 now names the first segment after the statement that begins with `withdrawn` - fixed 2026-09-26 — duty 10 inherited through unvalidated `**Follows:**` lines and subtracted unvalidated `**Defers:**` lines, and the map keyed a Global Constraints entry by the annotation; ruling: 2026-09-26; Task 5 reorders the steps, keys the entry by its line and its words after the annotation, and states the summary slots and `0/0 covered` - fixed 2026-09-26 — the plan annotations did not say that `none` is never qualified, that an empty section is absent, or that another spec's `**Defers:**` line is out of scope; license: the spec's *The plan's annotations* and *Deferral*, as fixed there; Task 2 says so +- fixed 2026-09-26 — the withdrawal ruled outside any hit had no ledger home on a closed loop; ruling: 2026-09-26; Task 1 says the tombstone is its record there, check I added - fixed 2026-09-26 — Task 13's extractor took the report from the dispatch's tool result, which for a background card may only acknowledge the launch; license: Task 13's stated purpose; the prompt asks for a foreground dispatch and the extractor reads the dispatched agent's last message, falling back to the tool result diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 0448823..437d54e 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -285,9 +285,15 @@ A withdrawal is not a disposition of a hit: it changes what the spec decides, so the session writes the token only on the developer's explicit decision, and the edit lands in the spec's own ledger. That edit leaves the spec's `integrity:` stamp stale, as any body edit does, -and correctly so. Withdrawing a decision of a spec already -`implemented` is no edit of its frozen body but a revision (D24): a -newer design spec carries `revises:` and the register that stands. +and correctly so. One case writes no ledger line: on a spec whose loop +has closed, a withdrawal the developer rules outside any hit or finding +is recorded by its tombstone alone, which carries the reason, the +ruling's date and any successor. No diff-scoped round follows a close, +since a new loop reads the whole document, and the stale stamp signals +the change; a line already open under the closed loop still closes on +its own. Withdrawing a decision of a spec already `implemented` is no +edit of its frozen body but a revision (D24): a newer design spec +carries `revises:` and the register that stands. The token's `ruling:` is a Ruling in the glossary's sense, dated with the developer's decision rather than with the edit that recorded it. @@ -1056,5 +1062,5 @@ a plan may cite both; the `←` arrow marks a citation that covers nothing; the template always carries `**Files:**`; and the second audit's questions 1 and 2 were disposed as its D26 and D3 lines. -- held — implementer question 8: where a withdrawal the developer rules outside any hit lands in the ledger once the spec's loop has closed; question: record it only as the tombstone on a closed loop, not in the ledger?; options: (b) on a closed loop, not `implemented`, and for a withdrawal tied to no hit or finding, the tombstone is the record and no ledger line is written, an open line still closing on its own — a new loop reads the whole document, and the stale stamp signals the change (recommended, and Codex's second opinion agrees); (a) a new heading `### — ruling`; (c) `fix from` the spec's own path +- fixed 2026-09-26 — implementer question 8: where a withdrawal the developer rules outside any hit lands in the ledger once the spec's loop has closed; ruling: 2026-09-26; option (b) — on a closed loop, for a spec not `implemented` and a withdrawal tied to no hit or finding, the tombstone is the record and no ledger line is written (*Entry states*) From eef6b162f7941457dcdf162cad2a82fc52149865 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 17:36:28 +0200 Subject: [PATCH 067/143] docs(working-process): plan-coverage plan, gate fix --- docs/plans/2026-09-26-plan-coverage.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 822633b..e32ecc1 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2312,13 +2312,15 @@ adversary round has read them; the propagation gate that followed did. - fixed 2026-09-26 — [Important] Task 13 kept the outer session's printed text, not evidence that the changed card ran; ruling: 2026-09-26; each run saves its `stream-json` event stream, and Step 3 extracts the dispatch's agent type, model, denials and report from it, keeping the streams as evidence - fixed 2026-09-26 — [Important] Task 7 dropped part of D30: the spec has the plan-adversary judge whether a withdrawn inherited decision must be undone, and send it to the developer where nothing settles it; ruling: 2026-09-26; dimension 6 now says so, Task 7 cites D30, check D added -The spec's third integrity audit changed three prescribed texts, all -recorded in the spec's ledger under -`### 2026-09-26 — integrity audit, fable, at the consumption gate (third)`: +The spec's third integrity audit changed the prescribed texts of +Tasks 1, 2 and 5, in four lines below, each recorded in the spec's +ledger under +`### 2026-09-26 — integrity audit, fable, at the consumption gate (third)`. +The fifth line, on Task 13, comes from Codex's second opinion instead: - fixed 2026-09-26 — the state-token recognizer named the paragraph's final segment; license: the spec's *The register*, as fixed there; Task 1 now names the first segment after the statement that begins with `withdrawn` - fixed 2026-09-26 — duty 10 inherited through unvalidated `**Follows:**` lines and subtracted unvalidated `**Defers:**` lines, and the map keyed a Global Constraints entry by the annotation; ruling: 2026-09-26; Task 5 reorders the steps, keys the entry by its line and its words after the annotation, and states the summary slots and `0/0 covered` - fixed 2026-09-26 — the plan annotations did not say that `none` is never qualified, that an empty section is absent, or that another spec's `**Defers:**` line is out of scope; license: the spec's *The plan's annotations* and *Deferral*, as fixed there; Task 2 says so - fixed 2026-09-26 — the withdrawal ruled outside any hit had no ledger home on a closed loop; ruling: 2026-09-26; Task 1 says the tombstone is its record there, check I added - fixed 2026-09-26 — Task 13's extractor took the report from the dispatch's tool result, which for a background card may only acknowledge the launch; license: Task 13's stated purpose; the prompt asks for a foreground dispatch and the extractor reads the dispatched agent's last message, falling back to the tool result - +- hit fixed 2026-09-26 — the sentence opening this block counted three changed texts over five lines; it now names Tasks 1, 2 and 5 in four lines and gives the fifth its own source From f0ee90834f1f9d97ebff3df4ea4f678834b1b714 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 17:40:33 +0200 Subject: [PATCH 068/143] docs(working-process): restamp plan-coverage spec integrity --- docs/specs/2026-09-25-plan-coverage-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 437d54e..25909be 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -4,7 +4,7 @@ date: 2026-09-25 status: draft grilled: 2026-09-25 architect: concerns (resolved 2026-09-26) -integrity: 2026-09-26 (sha: 884a6cf) +integrity: 2026-09-26 (sha: 4263a22) decisions: registered branch: feature/plan-coverage base: develop From 921756c2f135a58216aa7843f2676d47beb791a7 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:05:02 +0200 Subject: [PATCH 069/143] feat(working-process): decision register in the lifecycle rule --- .../rules/spec-plan-lifecycle.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index 8ee5e9e..de01b90 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -21,6 +21,7 @@ architect: LGTM # optional: latest architect verdict (LGTM | concerns | bloc adversary: LGTM # optional: latest plan-adversary verdict (LGTM | concerns | blocking) architect-fallback: (degraded ) # optional: verdict above produced below the prescribed tier (adversary-fallback: for plans) integrity: (sha: [; with: @]) # optional: date of the last integrity audit, the body hash it certifies, and — where a technical design was audited with it — that document and its hash +decisions: registered # optional, design specs only: the body carries a decision register revises: ./.md # optional: documents this one departs from; inline list when several spec: ../specs/.md # plans and technical designs: the design spec this document descends from; inline list when several, on a plan technical-design: ../technical-designs/.md # optional: on a design spec, the technical design that develops it; on a plan, the technical design of each design spec it descends from that has one — inline list when several, each path relative to the plan @@ -148,6 +149,11 @@ base: master # optional: branch the topic branch was cut from with no technical design contributes no entry, so a plan from specs `a` and `b`, where only `a` names a design, carries that one design. One technical design per design spec. +- `decisions:` is optional, on design specs only, and takes one value, + `registered`: the spec carries a decision register, defined under + *Decision register* below. It is a convention field rather than a + process stamp — no review step writes it, and no Unfinished-work + command reads it. - Where a plan's `technical-design:` names documents, those documents define the interfaces. The plan's `**Interfaces:**` blocks reference the contracts they define and say which part of one each task @@ -157,6 +163,93 @@ base: master # optional: branch the topic branch was cut from changes no plan template — the template belongs to the tool that writes plans. +## Decision register + +A design spec that sets `decisions: registered` carries a `## Decisions` +section — the decision register, the enumerable list of the decisions +the spec makes that need realization. The propagation auditor, when +that agent is available, derives a plan's decision coverage from it. + +The register is an index, never a copy. An entry is one identity +paragraph — the identifier and a short statement of the decision — and +may name the section that argues it: + + - **D3** — . Argued in *
*. [state token] + + + +The identity paragraph is the list item's first paragraph: its opening +line and the continuation lines after it, up to the first of a blank +line, a nested list item, the next list item at the same or a higher +level, or the end of the section. A reader joins those lines, +normalising whitespace, before parsing, so a statement wrapped across +lines keeps its state token at the paragraph's end. Prose beneath the +blank line is free. The grammar enforces the paragraph's shape, never a +sentence count: restating the argument in the register would give the +decision two homes, and the first fix wave would leave the register +describing the decision's old form. + +The identifier is the literal token `**D**`, or `**D.**` for a +child, at the start of a list item under `## Decisions`; a child's item +nests under its parent's. Identifiers are unique within the spec and +never positional, renumbered or reused: a decision added mid-loop takes +the next free number. A Markdown numbered list is refused, because its +numbers are positions and move when an item is inserted above. An +element of a list that needs realization on its own takes a child +identifier. The parent of children is a group: it enters no count, a +citation of it covers none of its children, and it carries no state +token. + +Every decision that needs realization enters, cross-cutting constraints +included. A ruling that something stays as it is enters too, since an +implementer can break it, and a plan realizes it as a constraint; no +kind of entry is exempt. A rejected alternative never enters: nobody +realizes it, and it already has homes — a spec's out-of-scope section, +a technical design's *Cuts not taken*. + +An entry without a token is active. One state token exists, carried by +a leaf or not at all, and never by a group: + + withdrawn , ruling: [; replaced by ] + +The decision no longer stands. The entry stays as a tombstone that +reserves its identifier, so a reuse shows up as a duplicate. +`replaced by` names an identifier of the same register other than its +own, and a chain of successors never forms a cycle; a successor may +itself be withdrawn. The token is recognized by its opening word alone: +the first segment after the full stop that ends the statement, and after +any "Argued in" pointer, that begins with `withdrawn`; it runs to the +paragraph's end, so its reason may hold a full stop. A segment so +opening that does not match the grammar is malformed, never prose. +Withdrawing a group is written on each of its leaves. A decision that +still stands but one plan does not realize is no state of the entry: +that plan defers it (see *Plan annotations*). + +A withdrawal changes what the spec decides, so the session writes the +token only on the developer's explicit decision. Its `ruling:` is dated +with that decision; the edit lands in the spec's own ledger and leaves +the `integrity:` stamp stale, as any body edit does. On a spec whose +loop has closed, a withdrawal ruled outside any hit or finding writes +no ledger line: the tombstone is its record, since no diff-scoped round +follows a close. A spec already `implemented` takes no such edit: the +decision is withdrawn by a newer design spec carrying `revises:` and the +register that stands. + +The spec's author creates the register while writing the spec, so a +design that never meets a grilling session still has one, and a +grilling session updates it as its decisions land. A spec carrying the +field declares a rule about itself: *the register lists every decision +in this spec that needs realization.* The integrity audit's first lens +verifies a document's declared rules in both directions, which gives +the register's completeness its reader. + +A design spec without the field is legacy: the propagation audit +reports it as not checked and raises no hit, and a plan descending from +legacy specs alone carries no annotations. An existing spec migrates +when it is next substantively revised, never in a sweep. Every new +design spec is expected to set the field; its absence is reported, not +refused. + ## Finding what revises a document `revises:` points backward only, so the documents that departed from a From 3a7158f56aaaf65117a34f76d3f866f2331f4478 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:08:49 +0200 Subject: [PATCH 070/143] feat(working-process): plan annotations for decision coverage --- .../rules/spec-plan-lifecycle.md | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index de01b90..6dfc04a 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -160,8 +160,12 @@ base: master # optional: branch the topic branch was cut from implements or changes; they do not independently redefine those contracts. Where a plan names no technical design, the existing plan convention stands unchanged. This binds how the blocks are filled and - changes no plan template — the template belongs to the tool that - writes plans. + changes no plan template. The template belongs to the tool that + writes plans, with one exception, defined under *Plan annotations* + below: this rule adds the `**Realizes:**` annotation on tasks and + Global Constraints entries, and the `## Deferrals and predecessors` + section with its `**Defers:**` and `**Follows:**` lines. The rest of + the template stays with that tool. ## Decision register @@ -250,6 +254,83 @@ when it is next substantively revised, never in a sweep. Every new design spec is expected to set the field; its absence is reported, not refused. +## Plan annotations + +A plan whose `spec:` names at least one registered design spec carries, +on every task, one line after its `**Files:**` block — after its +`**Interfaces:**` block where it has one — and before its first step: + + **Realizes:** D3, D5 + **Realizes:** none + +`none` is a value, not an omission: without it a housekeeping task +cannot be told from an author who forgot. One decision may be cited by +several tasks, and a task split or merged in a fix wave stays covered as +long as the union of its identifiers survives. A cross-cutting +constraint is realized by the Global Constraints entry that states it, +and that entry opens with the same annotation as its first clause — +`**Realizes:** D9`. Only an entry that realizes a decision carries one, +and `none` is never written there. Citing the section as a whole +realizes nothing, and neither does citing a group: its leaves are what +a plan cites. `none` names no identifier, so it is never qualified. + +A plan whose `spec:` names one spec writes bare identifiers. A plan +whose `spec:` names two or more — registered or legacy alike — qualifies +every identifier with its spec's path exactly as `spec:` writes it, +`../specs/.md#D3`, wherever the plan or its audit names one. The +count of entries decides, never the count of registered ones, so +migrating a second spec out of legacy changes no annotation already +written. + +Two more kinds of line form a section of their own, headed +`## Deferrals and predecessors` at the Global Constraints heading's +level and placed directly after that section. They state facts about +the whole plan rather than requirements of any task, so they stay out of +Global Constraints, which the plan template makes part of every task's +requirements: + + **Defers:** D6 — ; ruling: + **Follows:** ../plans/.md + +A `**Defers:**` line records that this plan leaves a standing decision +unrealized, one line per identifier, qualified as the annotations are. +The session writes it only on the developer's explicit decision, which +its `ruling:` dates. The deferral binds this plan alone: auditing any +other plan of the same spec, the decision counts, and the spec names no +plan. Deferring an undefined, withdrawn or group identifier is +malformed, and so is deferring one the plan also cites, locally or by +inheritance. A plan with neither kind of line carries no such +section, and in the pass for one spec a `**Defers:**` line naming +another spec's identifier is out of scope. + +A `**Follows:**` line names a plan this one continues, by a path +relative to the plan. The predecessor shares at least one spec with +this plan's `spec:` and carries `status: implemented`, since only a +frozen body's citations cannot move. This plan inherits every +identifier the predecessor's `**Realizes:**` annotations cite for the +specs both plans name, and nothing else — never its `**Defers:**` +lines, and never its `**Follows:**` lines, so inheritance is not +transitive and a plan names every predecessor whose citations it relies +on. An inherited citation of an identifier the register withdrew after +the predecessor shipped covers nothing and raises no hit; a local +citation of it still does. Whether the predecessor's realization still +stands in the code is not this line's question: the diff the +implemented-document bullet above prescribes answers it. + +An annotation, a `**Defers:**` line or a `**Follows:**` line counts +only at the place this section gives it: a task's line before its first +step, the opening clause of a Global Constraints entry, a line of +`## Deferrals and predecessors`. The same text inside a code block, or +quoted as an example, describes the grammar and instantiates nothing. + +The plan's author writes these lines, whoever that author is. A session +writing a plan meets this rule by reading the design spec, and a brief +that delegates plan-writing to a separate context names this rule and +requires reading it first, as the workflow rule's step 4 says. A plan +lacking an annotation is caught at its first propagation gate, and an +identifier is added there only where the task's text actually realizes +the decision, never mechanically. + ## Finding what revises a document `revises:` points backward only, so the documents that departed from a From 196aeaa8a3723fe0b34dec306c6a256bd12d787e Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:13:38 +0200 Subject: [PATCH 071/143] feat(working-process): held gate line for coverage hits --- .../rules/spec-plan-lifecycle.md | 107 ++++++++++++++---- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index 6dfc04a..59e0999 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -379,9 +379,11 @@ The severity bracket is omitted on a line whose sole authorizer is - fixed — ; ruling: ; -`open` and `held` are the non-terminal states, and the only two the -Unfinished-work list anchors. `fixed` and `declined` are terminal and -say what became of the document: it changed, or it stands. +`open` and `held` are the non-terminal states, and the only two +disposition states the Unfinished-work list anchors; the one gate line +it anchors, `hit held`, is defined under *Gate lines* below. `fixed` +and `declined` are terminal and say what became of the document: it +changed, or it stands. The two dates on a terminal line record different events and are both written even when they coincide. The leading date is when the line @@ -469,7 +471,14 @@ It carries no ordinal and no verdict, which keeps it out of the round cap's derivation and out of the Unfinished-work commands that anchor a round heading: it records a fix, not a round. Two stamps go stale as on any body edit — the `integrity:` hash stops matching, and the verdict -stops certifying the words that changed. +stops certifying the words that changed. The same heading serves a +register edit that a plan's held gate line licensed, on a design spec +whose loop has closed at whatever verdict: the heading then names the +plan whose gate held the hit, and that plan's terminal gate line names +the heading and the line under it. Unlike a review-licensed fix, such an +edit never joins a resolution note, even on a `concerns (resolved)` +spec: that note records what resolved the verdict, and this edit +resolves none. Payload costs nothing structurally. An Unfinished-work command under the list's default scope guard is held to the frontmatter block, so no body @@ -555,10 +564,11 @@ is. The workflow rule owns the ask that produces it. ### Gate lines -The propagation gate, when that agent is available, writes two shapes of -its own. They carry their own leading token and never a severity, because -a hit is a located detection the dispatcher confirms or dismisses, never -a graded finding: +The propagation gate, when that agent is available, writes gate lines of +its own: the two ordinary shapes here, and the held shape and its three +terminal rewrites below. Each carries its own leading token and never a +severity, because a hit is a located detection the dispatcher confirms +or dismisses, never a graded finding: - hit fixed — ; - hit dismissed — ; counter: @@ -566,7 +576,41 @@ a graded finding: Neither carries a license either, because a hit's fix is licensed by its own derivation. -Both are written at gate time, under the last round's heading. The `hit` +A hit of the coverage duty whose fix needs a decision no derivation +settles is held for the developer — the one exception the workflow rule +makes to hits never waiting. It takes a third shape, rewritten in place +to one of three terminal shapes, so its state always has one home: + + - hit held — ; question: ; options: + - hit fixed — ; ; ruling: + - hit deferred — ; ruling: ; Defers: + - hit withdrawn — ; ruling: ; # + +The rewrite happens once the gate has re-run over the changed document +and whatever depends on it, never on the developer's answer alone. +`hit fixed … ruling:` records that the document now carries the +decision the developer settled — a plan that realizes it, or a register +the ruling repaired; `hit deferred` records an approved `**Defers:**` +line in the plan, and `hit withdrawn` a register entry now a tombstone. +Each is written only where it names what happened, and none is +`hit dismissed`, because the gap was real when the hit fired. The +ordinary `hit fixed` shape, without `ruling:`, stays for every hit +whose fix its derivation licensed. `ruling:` on a gate line means what +it means on a disposition line, and a diff-scoped round treats the line +as settled. + +A `hit held` line lives in the ledger of the document whose gate raised +it — the audited plan, or the design spec audited alone. Its leading +date is the gate's, and the terminal rewrite replaces it with the date +the line reached its terminal state. A held hit ends its gate episode +and blocks the dispatch the gate guards; the re-run after the +developer's answer is a fresh episode. A register edit the answer +causes is recorded in the spec's own ledger — under its latest round +heading while its loop is open, and under the fix heading above once +its loop has closed. A spec already `implemented` takes the edit by +revision instead, and the held hit waits for it. + +Each is written at gate time, under the last round's heading. The `hit` token tells a gate line apart from that round's own findings; the date does not, since a gate episode and the round it precedes commonly share one. A gate still never mints a heading of its own, on the separate @@ -579,15 +623,25 @@ is still re-dispatching, or the gate cannot terminate, and the next diff-scoped brief is composed before its own round is stamped. A gate before a document's first round has no heading to write under; its lines wait for that round and are written at its stamp, the one case where -they do. +they do — unless the episode holds a hit, since the round its lines +would wait for cannot start. A pre-round episode holding at least one +hit writes every one of its lines, the `hit held` line and its siblings +alike, in the `## Review rounds` section before the first round +heading, and they stay there, rewrites included: no round produced +them, and one episode keeps one home. Once such lines stand there, +every later pre-round episode writes there too, holding or not, so the +document's pre-round gate history keeps one place. A document with no +`## Review rounds` section gains one, at its end, with its first gate +line. A gate episode always lands somewhere: its lines are the reason a later reader need not re-derive what the session already settled. -Neither shape covers a hit left outstanding when the re-dispatch bound -in the workflow rule stops an episode. That state owes the developer a -decision, so neither `hit fixed` nor `hit dismissed` can honestly carry -it, and no anchor surfaces it today — a stated gap, not an oversight. +No gate-line shape covers a hit left outstanding when the re-dispatch +bound in the workflow rule stops an episode. That state owes the +developer a decision, so neither `hit fixed` nor `hit dismissed` can +honestly carry it, and `hit held` is scoped to the coverage duty's +hits; no anchor surfaces it today — a stated gap, not an oversight. Until a shape exists, the workflow rule's report is its only record. A `hit fixed` line puts the gate's ordinary work where the next @@ -596,7 +650,8 @@ diff-scoped brief already looks, beside the round's `fixed` lines. A make, so a later round cites it instead of re-deriving it and a session that would dismiss the same hit differently argues against written words rather than silence. Neither joins the unfinished-work anchors: both are -closed when written and owe nobody a next move. +closed when written and owe nobody a next move. A `hit held` line owes +the developer an answer, so it joins them until its terminal rewrite. A line carrying `ruling:` is a recorded developer decision, and what a later round may do with it depends on what that round brings: @@ -657,17 +712,20 @@ leg, and the entries below that do so say it there. leg discharges its own debt — a session derives that decline under a `concerns` heading, while a `blocking` one waits for the developer's adjudication, which is theirs to make. -- **Unfinished review-loop ledger** — a disposition line nobody closed: - an `open` line whose remediation never ran, or a `held` line whose - question still waits. - `rg -n --no-ignore --crlf '^- (open|held) —' docs/` +- **Unfinished review-loop ledger** — a disposition line or a held gate + line nobody closed: an `open` line whose remediation never ran, or a + `held` or `hit held` line whose question still waits. A gate line's + date stands between its token and its dash, so the command takes an + alternative rather than a third token inside the group. + `rg -n --no-ignore --crlf '^- (open|held) —|^- hit held ' docs/` Scope: a hit counts only inside a `## Review rounds` section — this entry's own re-scoping of the guard above, kept for the same reason, since a document quoting the grammar describes it rather than instantiating it. Confirming section membership needs line positions, which is why this command carries `-n` where the others carry `-l`. Owner: an `open` line belongs to the document's next touch, which - re-offers the remediation; a `held` line belongs to the developer. + re-offers the remediation; a `held` or `hit held` line belongs to the + developer. - **Chain debt** — a diff-scoped `LGTM` heading carrying no record that what it owed was discharged. `rg -n --no-ignore --crlf '^### .*LGTM \(round [0-9]+, diff-scoped\)$' docs/` @@ -752,9 +810,12 @@ so the `integrity:` stamp sits on the design spec and names both. A document's consumption gate is the backstop for its ledger: no judged document passes to plan-writing, nor a plan to implementation, while -`held` lines stay open — an LGTM can leave the frontmatter clean while a -decision question still pends, so the gate asks those questions at the -latest. Writing a plan from a judged document is that same seam: its +`held` lines or `hit held` gate lines stay open — an LGTM can leave the +frontmatter clean while a decision question still pends, so the gate +asks those questions at the latest. A `hit held` line blocks until the +gate has re-run and rewritten it to a terminal shape, since declining +the dispatch it guarded decides nothing; a closed gate line blocks +nothing. Writing a plan from a judged document is that same seam: its held questions are asked before the plan is written, whoever writes it. A technical design's own consumption gate is plan-writing, the same From 91f79833ea277fdb4bf26bd256d2760bcc91450f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:17:36 +0200 Subject: [PATCH 072/143] feat(working-process): declared table relations in the technical-design rule --- .../working-process/rules/technical-design.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/plugins/working-process/rules/technical-design.md b/plugins/working-process/rules/technical-design.md index 30dbc54..7b7aea6 100644 --- a/plugins/working-process/rules/technical-design.md +++ b/plugins/working-process/rules/technical-design.md @@ -109,3 +109,31 @@ A domain adds rows and values — outside the closed `change` column — never columns, and never redefines what qualifies: a kind of part it names must still have a resolvable identity, an explicit responsibility, and a contract its implementation can change behind. + +## Declared table relations + +Where a column names rows of another table, the relation is declared +here, and this section is its only home. The propagation audit, when +that agent is available, resolves every declared relation and checks +nothing a declaration does not name: matching cell values would guess a +relation rather than read one. + +| column | names rows of | +|---|---| +| Contracts `producer → consumer`, each side | Parts `part` | +| State `written by` | Parts `part` | +| State `read by` | Parts `part` | + +A cell may name several rows, comma-separated, and +`producer → consumer` splits at the arrow before it splits at commas. A +party outside the system is written `external: ` and is no +reference. No side of these three relations may be empty: the +definitions above give every contract a consumer and every state record +a writer and a reader, so an empty cell, or one naming nothing, fails +the relation. A later declaration that admits an empty side says so +itself. Every other name resolves to exactly one row of Parts, because +a duplicated part name makes every reference to it ambiguous. + +A Contracts flow written as a numbered sequence rather than a table row +is outside the relation: the skeleton gives the sequence no grammar a +parse could split, so its steps are not references. From 1d2789bf243a4ca1464d6b1a5634ee69522d0dd3 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:21:20 +0200 Subject: [PATCH 073/143] feat(working-process): decision coverage and table closure duties --- .../agents/propagation-auditor.md | 162 ++++++++++++++++-- 1 file changed, 151 insertions(+), 11 deletions(-) diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index cc39bbd..18a0e64 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -1,6 +1,6 @@ --- name: propagation-auditor -description: "Mechanical propagation audit of a design spec, a technical design or a plan before an expensive dispatch: parses changed interfaces to enumerate their consumers, diffs every prescribed block against the file it targets — a landed change against what shipped, a promised one against the anchor its edit needs — re-derives every counter, and returns located hits with their derivation — or the single line CLEAN. Verdict-free and persona-free: it stamps nothing and grades nothing, so a passing gate is a precondition for the dispatch that follows, never a judgment on the design. Dispatch before every verdict-agent dispatch, after a fix wave, before an integrity audit, and after any multi-site edit during authoring. Run it on the cheapest available family, named explicitly — every duty is procedural, and the never-cheapest rule governs reviews, which an audit is not. Runs in the background; the report arrives as a task notification." +description: "Mechanical propagation audit of a design spec, a technical design or a plan before an expensive dispatch: parses changed interfaces to enumerate their consumers, diffs every prescribed block against the file it targets — a landed change against what shipped, a promised one against the anchor its edit needs — re-derives every counter, derives a plan's decision coverage from its design specs' decision registers, resolves the table relations the technical-design rule declares, and returns located hits with their derivation — or, where none fires, the token CLEAN after the report's decision-coverage and table-closure lines. Verdict-free and persona-free: it stamps nothing and grades nothing, so a passing gate is a precondition for the dispatch that follows, never a judgment on the design. Dispatch before every verdict-agent dispatch, after a fix wave, before an integrity audit, and after any multi-site edit during authoring. Run it on the cheapest available family, named explicitly — every duty is procedural, and the never-cheapest rule governs reviews, which an audit is not. Runs in the background; the report arrives as a task notification." background: true --- @@ -177,23 +177,157 @@ a hit — the check reads each design spec's pointer, so a missing field is found rather than skipped. A plan whose `spec:` is not named back is not a hit: no design spec names its plans. +### 10. Decision coverage — every registered decision has an owner + +Runs on a plan, and on a design spec audited alone. The register's +grammar, its state token and the plan's annotations are defined in the +spec-plan-lifecycle rule, under *Decision register* and *Plan +annotations*. Read them in the plugin's own copy, +`${CLAUDE_PLUGIN_ROOT}/rules/spec-plan-lifecycle.md`, which matches this +card's version, rather than recalling them. Read a line only at the +place *Plan annotations* gives it: a code block or a quoted example +describes the grammar. For each spec the plan's `spec:` names: + +1. Read the spec's `decisions:` field. Absent: the spec is legacy — + write `not checked` for it and stop. Any value other than + `registered`: a hit, `not counted`, stop. +2. Check that the register is well formed. Each of these is a hit: the + field set with no `## Decisions` section; an identity paragraph that + does not parse, including a child nested under a parent other than + its own, a child whose parent does not exist, and a register written + as a numbered list; a duplicate identifier; a segment opening with + `withdrawn` that does not match the token's grammar; a group + carrying a state token; a `replaced by` naming a missing identifier, + its own, or a group, or closing a cycle. Where any fires, the counted + set cannot be trusted: write `not counted` and stop. +3. Check the plan's `**Follows:**` lines. One naming a missing file, a + plan sharing no spec with the audited plan, or a plan not at + `status: implemented` is a hit, and lends nothing to the steps + below. A predecessor lends identifiers only for the specs both plans + name; in the pass for a spec it does not name, its line is out of + scope rather than a hit. +4. Collect the cited set: every identifier the plan's `**Realizes:**` + annotations cite, on tasks and Global Constraints entries, and every + identifier inherited through the `**Follows:**` lines step 3 + accepted, each with the plan and site it comes from. A cited + withdrawn identifier, a cited group and a cited identifier the + register does not define are hits. The last is an error in the plan, + never a gap in the spec — identifiers are minted only in the + register — so duty 5 does not take it. An inherited citation of an + identifier withdrawn since its predecessor shipped is skipped: not + counted, not a hit, and listed apart in the report. +5. Check the plan's `**Defers:**` lines. One naming an undefined, + withdrawn or group identifier, or one in the cited set, is a hit and + subtracts nothing. In the pass for one spec, a line naming another + spec's identifier is out of scope. +6. Collect the counted set: the leaf identifiers not withdrawn, less + those the `**Defers:**` lines step 5 accepted name. +7. Report every counted identifier the cited set lacks, one hit per + identifier. A task with no `**Realizes:**` line, in a plan whose + `spec:` names a registered spec, is a hit too, and so is a bare + identifier in a plan whose `spec:` names two or more specs. + +Derive the map, never tally it: write each counted identifier with the +sites citing it, and let the fraction summarise the map, since a bare +count passes one omission offset by one duplicate. An identifier cited +by several tasks is legal. + +On a design spec audited alone — the gate before its architect round — +run steps 1 and 2 only, so a malformed register is found before any plan +depends on it. + +This duty proves that every declared decision has an owner. Whether the +register lists every decision the spec makes is the integrity auditor's +judgment, and whether the citing tasks realize their decision is the +plan-adversary's. Measured in an outside project: a plan realized six of +a spec's eight table rows, and two adversary rounds, three propagation +audits and the author's self-review passed it, because none carried +"every decision has an owning task" in its brief. + +### 11. Table closure — a column naming another table's rows resolves + +Runs on a technical design. Check only the relations the rule defining +the tables declares, never relations guessed from matching values: two +unrelated columns can share names by chance, and a relation whose every +reference is wrong would match nothing. The technical-design rule +declares them under its heading `## Declared table relations`, with how +each cell splits and which values are not references; read them in the +plugin's own copy, `${CLAUDE_PLUGIN_ROOT}/rules/technical-design.md`. +For each declared relation whose tables the document carries, every +name in the column resolves to exactly one row of the named table: a +name matching no row is a hit, and so is a name matching several. An +empty cell is a hit wherever the declaration gives the column no empty +side. A relation whose table the document does not carry is not +applicable and is not counted, and a table no declaration names is +outside this duty. Measured on 2026-09-16: walked duty by duty, none of +the first nine checked referential integrity between two tables of one +document. + ## Output Open with the self-report, one line: model: -A clean audit runs to exactly two lines: the self-report above, then the -literal token. +Then one entry per hit, if any fired: - CLEAN + — — derivation: -Add nothing after the token. The self-report opens every report, and a -clean run is the case where that is easiest to forget. +Then the lines duties 10 and 11 write on every run, clean ones included. +On a plan, one `decision-coverage:` block per spec its `spec:` names: + + decision-coverage: 7/8 covered; inherited [D2]; deferred [D6]; uncovered [D4.2] + D1 → Task 2 + D2 → ../plans/.md (Task 3) + D3 → Task 4, Task 6 + D4.2 → — + D9 → Global Constraints, line 42: "No code" + D10 → ../plans/.md (Global Constraints, line 38: "No code") + … + withdrawn since: D4 ← ../plans/.md (Task 2) + decision-coverage: not counted — malformed register + decision-coverage: not checked — no decision register + +The summary line opens the block. Under a counted spec the map follows, +one indented line per counted identifier naming every task and +constraint that cites it: a task by its heading's number, a Global +Constraints entry by the line it opens on and its opening words after +the annotation, in quotes, and an inherited site by its plan's path with +the site in parentheses. An uncovered identifier maps to `—` and is a +hit above as well. The fraction's denominator is duty 10's counted set, +and its numerator the counted identifiers in the cited set; a counted +set of zero reads `0/0 covered`. The summary line carries every slot, in +the example's order, an empty one written `[]`. `inherited [..]` lists +the covered identifiers whose only citation is a predecessor's; one also +cited locally counts as local, and its map line names both sites. +`deferred [..]` lists what the plan defers, never counted as covered. A +skipped inherited citation follows the map on a `withdrawn since:` line, +outside the fraction. Qualify every identifier as the plan's annotations +are. `not counted` carries no map, since its denominator cannot be +derived. + +On a design spec audited alone, one line and no map: + + decision-coverage: register well formed + decision-coverage: not counted — malformed register + decision-coverage: not checked — no decision register + +On a technical design, one line, counting the declared relations the +duty resolved in that document; no other document gets one: + + table-closure: 3 relations checked + table-closure: no declared relations + +Where no hit fired, close with the literal token: -Otherwise, one entry per hit: + CLEAN - — — derivation: +`CLEAN` means no hit in the checks that ran, and a `not checked` line +bounds that guarantee in the report itself. A clean report is the +self-report, the lines above that apply to the document, and the token, +with nothing after it. The self-report opens every report, and a clean +run is the case where that is easiest to forget. A report carrying a hit +carries no `CLEAN`. Hits are never graded. Critical, Important, Minor, and every other severity word stay out of your report: grading belongs to review rounds, @@ -205,9 +339,12 @@ frontmatter. The dispatcher confirms or dismisses each hit. A confirmed hit's fix is licensed by the derivation itself — a recounted counter and an enumerated missed consumer decide themselves — so hits never wait for the developer, -and a hit the dispatching session believes is wrong reaches the developer -rather than a silent dismissal. Write each derivation to stand alone: the -dispatcher acts on it, never on your confidence. +with one exception: a hit of duty 10 whose fix needs a decision no +derivation settles, such as the task an uncovered decision needs where +the decision leaves a choice open, is held for the developer. A hit the +dispatching session believes is wrong reaches the developer rather than +a silent dismissal. Write each derivation to stand alone: the dispatcher +acts on it, never on your confidence. ## Out of bounds @@ -218,4 +355,7 @@ dispatcher acts on it, never on your confidence. hunts: decisions restated in their old form, unworkable sequencing, underspecified places. That is the sibling pass, on the most capable available tier and in a fresh context. +- Judging whether a task realizes the decision it cites, or whether the + register lists every decision its spec makes — the plan-adversary's + and the integrity auditor's. - Prose quality, naming taste, and style. From af08effb40efd98d577f071936993ee4c69ad8dc Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:25:12 +0200 Subject: [PATCH 074/143] feat(working-process): held coverage hits in the propagation gate --- plugins/working-process/rules/workflow.md | 42 +++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/plugins/working-process/rules/workflow.md b/plugins/working-process/rules/workflow.md index e78244f..1d04f62 100644 --- a/plugins/working-process/rules/workflow.md +++ b/plugins/working-process/rules/workflow.md @@ -114,7 +114,11 @@ disables its suggestion — never the work itself. has passed, leaving no confirmed hit, when that agent is available. Then write the implementation plan with superpowers:writing-plans when available; - plans live in `docs/plans/`. + plans live in `docs/plans/`. A brief that delegates plan-writing to a + separate context names the spec-plan-lifecycle rule and requires + reading it before the plan is written: that rule carries the + annotations a plan owes a registered design spec, and a separate + context cannot count on it loading. 5. **Plan → adversary review.** Before implementing a non-trivial plan, offer a working-process plan-adversary agent dispatch (when available); dispatch and stamping follow the verdict-agent dispatch @@ -439,7 +443,30 @@ verdict-agent dispatch, first rounds included — authoring errors exist before any repair; after a fix wave, before the next round; and before an integrity audit. A hit's fix is licensed by its own derivation — a recounted counter and an enumerated missed call site decide -themselves — so hits never wait for the developer. +themselves — so hits never wait for the developer, with one named +exception, for the coverage duty's hits alone. + +The two coverage hits — a registered decision no task cites, and a task +lacking its `**Realizes:**` line — are triaged by license. Where a task +already does the work and lacks only its annotation, the task's text +licenses adding the identifier. Where no task realizes the decision and +the decision's text settles every choice the missing task requires, the +decision licenses writing it, and the next round's brief names it among +the previous wave's fixes. Where writing the task needs a choice the +decision does not make, the hit is held: its question is that missing +decision, not the gap, which is already established. Any other hit of +the coverage duty is fixed where its derivation settles the fix and held +where it does not, as when breaking a `replaced by` cycle means choosing +which entry stands. No other duty's hit is ever held. + +A held hit ends its gate episode and blocks the dispatch the gate +guards, as a held finding blocks a fresh round, and the session puts its +question to the developer. The answer lands where the decision belongs — +in the spec's register, or as the plan's `**Defers:**` line — and the +gate re-runs over the changed document and whatever depends on it, as a +fresh episode; the dispatch goes out once that episode passes. The +spec-plan-lifecycle rule defines the `hit held` line and its terminal +shapes. A report's body governs, never its closing token. Where a report lists located hits and also carries `CLEAN`, the hits are the report and the @@ -448,14 +475,17 @@ family, and once under a brief that ruled the combination out in as many words — so emphasis on the writing side is spent, and the guard belongs to the dispatcher who reads. The rule is stated here rather than generalised, because `CLEAN` is this agent's token and no other -report carries one. +report carries one. A `decision-coverage:` or `table-closure:` line is +neither a hit nor a breach of the token's position: the card writes +those lines on every run, clean ones included, between the hits and the +token. A hit the session believes is wrong is dismissed, never silently: the session writes the `dismissed` line the spec-plan-lifecycle rule defines and reports the dismissal in the next report it relays to the -developer. A hit is a report, not a question, so it never enters the -held batch and never spends the round's one interruption — the -developer reads the dismissal and keeps their standing veto over it. +developer. A dismissed hit is a report, not a question, so it never +enters the held batch and never spends the round's one interruption — +the developer reads the dismissal and keeps their standing veto over it. The written line is what makes the gate terminate: a dismissed hit recurs on every re-dispatch, so a gate waiting on a hitless audit would wait forever, and a dismissal nobody wrote down would be re-derived From e3ab73457f75f443cdefc7950372c4f6b1862017 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:28:07 +0200 Subject: [PATCH 075/143] feat(working-process): plan-adversary judges realization of registered decisions --- .../working-process/agents/plan-adversary.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plugins/working-process/agents/plan-adversary.md b/plugins/working-process/agents/plan-adversary.md index 28a6688..84e3804 100644 --- a/plugins/working-process/agents/plan-adversary.md +++ b/plugins/working-process/agents/plan-adversary.md @@ -113,6 +113,29 @@ block that redefines a contract independently is an Important finding whose `origin` names `implementation-plan`. Where no technical design is named, the existing plan convention stands and this paragraph is silent. +### 6. Realization of registered decisions + +Where a design spec the plan descends from sets `decisions: registered`, +read its decision register and the plan's `**Realizes:**` annotations in +the plan itself. The propagation audit has already established that +every counted decision is cited, and its report reaches the dispatcher, +not you. Judge each decision, never each site: one decision may span +several tasks and Global Constraints entries, so ask whether all the +sites citing it realize it in full together, and whether a cited +constraint actually binds the work it constrains. A decision realized +only in part — six rows of eight — is an Important finding whose +`origin` names `implementation-plan`. A deferral the developer ruled on +a `**Defers:**` line is not judged; a task that quietly depends on the +deferred decision anyway is a finding. Decisions inherited through a +`**Follows:**` line are taken as settled, since you read plans rather +than the code that realized them; a task that changes or undoes what an +inherited decision required is a finding against this plan. An +inherited citation of a decision the register has since withdrawn covers +nothing, and the withdrawal alone does not settle whether this plan +must undo what the predecessor built for it: judge the plan against the +decisions that stand, and where neither the withdrawal nor a successor +decision settles it, the finding says the question is the developer's. + ## Output { From 768b3bdee0cbce91cefb6c7f5eab6948114fa573 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:30:47 +0200 Subject: [PATCH 076/143] feat(working-process): integrity auditor reads the decision register as a declared rule --- plugins/working-process/agents/integrity-auditor.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/working-process/agents/integrity-auditor.md b/plugins/working-process/agents/integrity-auditor.md index 326f3b1..c0491e7 100644 --- a/plugins/working-process/agents/integrity-auditor.md +++ b/plugins/working-process/agents/integrity-auditor.md @@ -108,7 +108,12 @@ no longer does: document declares about itself, in both directions.** A document that says every entry carries a field is checked entry by entry for the field, and field by field for an entry the rule never claimed — the - reverse direction is where the survivors hide. + reverse direction is where the survivors hide. A design spec carrying + `decisions: registered` declares one such rule: its decision register + lists every decision in the spec that needs realization. Check each + entry against the text that argues it, and each decision the text + makes against the register; a decision left only in a paragraph is a + defect, and a rejected alternative without an entry is none. ### 2. Sufficiency for an implementer From 4a05325b37d46d3e256603ea56759c5ea343c151 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:33:21 +0200 Subject: [PATCH 077/143] feat(working-process): propagation duties checklist gains duties 10 and 11 --- plugins/working-process/rules/propagation-duties.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/working-process/rules/propagation-duties.md b/plugins/working-process/rules/propagation-duties.md index 959e58e..651e19a 100644 --- a/plugins/working-process/rules/propagation-duties.md +++ b/plugins/working-process/rules/propagation-duties.md @@ -8,15 +8,15 @@ paths: # Propagation duties — the author's checklist -Before a document goes to an expensive reader, nine duties fall due, -keyed by the ten edits that trigger them — one duty answers two +Before a document goes to an expensive reader, eleven duties fall due, +keyed by the twelve edits that trigger them — one duty answers two different edits. This rule lists them by the edit an author has just made, so the enumeration can be done at the desk instead of paid for at the gate. The list below is complete as it stands and needs nothing else to be usable. When the `propagation-auditor` agent is available it walks the same -nine as a gate, and its card is their definition and keeps the +eleven as a gate, and its card is their definition and keeps the measurement behind each; the numbers in the last column are that card's, so a hit it reports can be read back to the row that would have caught it. Without the agent the rows still hold — what is lost is the second @@ -25,7 +25,7 @@ pair of eyes, not the duties. | You have just… | Enumerate | Duty | |---|---|---| | changed an interface — a signature, a name, a heading, an anchor, a field | every consumer, by parsing the structure that defines them, never by text match | 1 | -| prescribed a verbatim block | the anchor it targets — the text it replaces must exist in that file byte-exactly, and once | 2 | +| prescribed a verbatim block | the anchor it targets — the text it replaces must exist in that file byte-exactly, and once, and not be one an earlier task in the same document has already rewritten | 2 | | reported a change already made | that block against the shipped file, both ways: one that never landed, and shipped text a block no longer matches | 2 | | added a field, label or state | both ends of its chain — what writes it, and what reads it | 3 | | asserted a count | the count itself, re-derived from what the tool prints or what the list holds | 4 | @@ -34,6 +34,8 @@ pair of eyes, not the duties. | written a verification command | the command, run against your own replacement text | 7 | | copied a citation out of a review report | the file and line it names, read at the source | 8 | | added or repointed a `spec:` or `technical-design:` pointer | both targets; on a design spec or a technical design, that each pointer names the document that names it; on a plan, that its `technical-design:` entries resolve, from the plan's own directory, to exactly the technical designs its design specs name — none missing, none extra | 9 | +| added, changed or withdrawn a register entry, or added, split or merged a plan task, or written a `**Defers:**` or `**Follows:**` line | the register against every `**Realizes:**` annotation, `**Defers:**` line and `**Follows:**` predecessor | 10 | +| written a column naming rows of another table | every name in it against that table | 11 | Two of these fire where an author does not expect them. A newly minted glossary `_Avoid_` ban is a changed interface, so duty 1 reaches every From 0be76c02ffacb43d5b6bf969cd9e4d19a1cbed21 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:35:20 +0200 Subject: [PATCH 078/143] feat(working-process): grilling session updates the decision register --- plugins/working-process/skills/grilling-session/SKILL.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/working-process/skills/grilling-session/SKILL.md b/plugins/working-process/skills/grilling-session/SKILL.md index a111db0..fdd57a4 100644 --- a/plugins/working-process/skills/grilling-session/SKILL.md +++ b/plugins/working-process/skills/grilling-session/SKILL.md @@ -70,6 +70,11 @@ applies (edit with the Edit tool): intended. Fuzzy term → propose a canonical one. Probe edge cases. Verify claims against the code. - Apply glossary updates inline the moment a term settles — no batching. +- A spec carrying `decisions: registered` has a decision register, + defined in the spec-plan-lifecycle rule: update it inline as each + decision lands. A new decision takes the next free identifier, a + sharpened one keeps its identifier, and a dropped one is withdrawn + only on the developer's explicit decision. - ADRs are rare: offer one only when the decision is hard to reverse AND surprising without context AND a real trade-off existed. From 0651a76b64e749c7fe5737fd2b1d015547bf4216 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 18:37:47 +0200 Subject: [PATCH 079/143] docs(working-process): README and changelog for plan coverage --- plugins/working-process/CHANGELOG.md | 24 +++++++++++++++++++- plugins/working-process/README.md | 34 ++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/plugins/working-process/CHANGELOG.md b/plugins/working-process/CHANGELOG.md index 5ae98fe..7f66f30 100644 --- a/plugins/working-process/CHANGELOG.md +++ b/plugins/working-process/CHANGELOG.md @@ -17,9 +17,31 @@ Released versions of this plugin, newest first. - The class a design spec and a technical design share is named `judged document` on the three agent cards that carried the old phrase and in the README; that phrase is retired. +- A design spec may carry a decision register: `decisions: registered` + and a `## Decisions` section listing, under stable identifiers, every + decision that needs realization, with `withdrawn` as its one state + token. +- A plan descending from a registered spec marks every task with + `**Realizes:**`, and records deferrals and predecessor plans in a + `## Deferrals and predecessors` section. +- The propagation auditor gains two duties: decision coverage, reported + in a `decision-coverage:` block per design spec, and table closure + over the relations the technical-design rule declares, reported in a + `table-closure:` line. A clean report carries those lines before + `CLEAN`. +- A coverage hit whose fix needs a decision the spec does not make is + held for the developer as a `hit held` gate line with three terminal + shapes, and the Unfinished review-loop ledger command finds it. +- The plan-adversary judges whether the tasks citing a decision realize + it in full, and the integrity auditor checks that the register lists + every decision its spec makes. +- The propagation duties checklist gains rows for the two duties and + the duty-2 anchor an earlier task has already rewritten. - Run a rules re-sync after this update: the plan-adversary's `origin` values changed, and until the re-sync an installed workflow rule - triages the new values by the old names. + triages the new values by the old names. The re-sync also installs + the decision register, the plan annotations and the held gate line, + which the updated agents expect. ## 0.17.0 — 2026-09-15 diff --git a/plugins/working-process/README.md b/plugins/working-process/README.md index 46815b2..c7e3aad 100644 --- a/plugins/working-process/README.md +++ b/plugins/working-process/README.md @@ -49,18 +49,24 @@ verdict dispatch and the integrity audit itself. plans (plans only; handed a judged document it declines toward the `architect` agent). Generic failure-mode dimensions live here; domain specifics - come from `*-plan-review` checklist skills. Dispatched in the - background, scaled to the plan's size and risk; the verdict arrives - as a task notification and is stamped after relay. + come from `*-plan-review` checklist skills. Where a design spec keeps + a decision register, it also judges whether the tasks citing each + decision realize it in full. Dispatched in the background, scaled to + the plan's size and risk; the verdict arrives as a task notification + and is stamped after relay. - **`propagation-auditor` agent** — the mechanical audit of a design spec, a technical design or a plan: it parses every changed interface to enumerate its consumers, diffs every prescribed block against the file it targets — a landed change against what shipped, a promised one against the anchor its - edit needs — re-derives every counter, and runs the document's own - verification commands. Its - unit is the hit: located, binary, and carrying the derivation that - produced it; a clean audit reports the single line `CLEAN`. It grades + edit needs — re-derives every counter, runs the document's own + verification commands, derives a plan's decision coverage from its + design specs' decision registers, and resolves the table relations + the technical-design rule declares. Its unit is the hit: located, + binary, and carrying the derivation that produced it. Every report + also carries a `decision-coverage:` block per design spec and a + `table-closure:` line per technical design, and a clean one ends in + `CLEAN`. It grades nothing, ends in no verdict, and stamps nothing. Dispatched in the background on the cheapest available family, because every duty is procedural; the workflow gates every verdict-agent dispatch and every @@ -147,6 +153,20 @@ anchors live there, and the `process-status` skill runs them. The tail anchors are exact, so a resolved-concern annotation drops out of the match by design. +## Decision register + +A design spec that sets `decisions: registered` carries a `## Decisions` +section listing, under stable identifiers (`D3`, `D4.1`), every +decision that needs realization. A plan descending from it marks each +task with `**Realizes:**` — the identifiers it realizes, or `none` — +and records deferrals and predecessor plans in a `## Deferrals and +predecessors` section. The propagation auditor derives the decision +coverage from the two lists, the plan-adversary judges whether the +citing tasks realize their decisions, and the integrity auditor checks +that the register lists every decision the spec makes. A spec without +the field is reported as not checked. The grammar lives in the +spec-plan-lifecycle rule. + ## Model selection The architect is dispatched on the most capable available model; the From f8e743b766485c3ac4a08e7cbaa3b1680768d655 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 19:29:43 +0200 Subject: [PATCH 080/143] fix(working-process): tighten the propagation auditor's report contract --- plugins/working-process/agents/propagation-auditor.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index 18a0e64..a78d3c1 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -265,7 +265,8 @@ document. ## Output -Open with the self-report, one line: +Open with the self-report, one line, as the report's first line — no +preamble, heading or narration before it: model: @@ -313,7 +314,10 @@ On a design spec audited alone, one line and no map: decision-coverage: not checked — no decision register On a technical design, one line, counting the declared relations the -duty resolved in that document; no other document gets one: +duty resolved in that document — one per row of the declaration table, +so the Contracts relation counts once for both of its sides. A report +on any other document carries no `table-closure:` line, not even one +saying the duty does not apply: table-closure: 3 relations checked table-closure: no declared relations From 3fe9602d3b939c3e4bcbcb1f38f85566679766f2 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 19:30:12 +0200 Subject: [PATCH 081/143] docs(working-process): plan-coverage plan, Task 13 harness and record --- docs/plans/2026-09-26-plan-coverage.md | 52 ++++++++++++++++++-------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index e32ecc1..a2f9361 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2045,11 +2045,12 @@ command cp -f "$R/docs/plans/2026-09-26-plan-coverage.md" "$W/docs/plans/" command cp -f "$R/docs/domain/glossary.md" "$W/docs/domain/" sed 's/^\*\*Realizes:\*\* D15$/**Realizes:** none/' \ "$W/docs/plans/2026-09-26-plan-coverage.md" > "$W/docs/plans/fixture-uncovered.md" -grep -c '^\*\*Realizes:\*\* D15$' "$W/docs/plans/fixture-uncovered.md" +grep -c '^\*\*Realizes:\*\* D15$' "$W/docs/plans/fixture-uncovered.md" || true ``` Expected: `0` — Task 8's only annotation is gone from the copy, so D15 -is cited nowhere else. +is cited nowhere else. `grep -c` exits 1 on a zero count, hence the +`|| true`. Then write the table-closure pair, whose Contracts row names a part `reder` that Parts does not carry, and commit the fixture: @@ -2125,15 +2126,20 @@ found, whatever the run prints. Disabling changes the developer's own configuration: ask before running. The guard is the inner session's permission set, not a list of allowed -tools, because `--allowedTools` only pre-approves and never forbids. -`--setting-sources project` keeps the developer's user settings, and -every allow rule in them, out of the run; the fixture carries no -project settings, so the only pre-approval is the `git rev-parse` the -command line grants, and in print mode every other prompt is denied. -`--tools` narrows the built-in set to what the dispatch needs, and -`--add-dir` admits the plugin directory the card reads its rules from. -The auditor therefore cannot execute the commands this plan quotes, -`claude plugin disable` and `rm -rf` among them. +tools, because `--allowedTools` only pre-approves and never forbids. The +developer's user settings stay in the run: `--setting-sources project` +would keep them out, but it also drops the agents `--plugin-dir` loads, +so the dispatch finds no auditor (measured on 2026-09-26, one flag at a +time). Their one risky allow rule is `Bash(claude plugin *)`, and +`--disallowedTools "Bash(claude *)"` denies that family, since a deny +rule wins over an allow rule. The harness itself pre-approves read-only +commands; in print mode every other prompt is denied, writes and +compound commands among them. `--tools` narrows the built-in set to +what the dispatch needs, and `--add-dir` admits the plugin directory the +card reads its rules from. These are the run's real limits: they block +the commands this plan quotes that change state, `claude plugin +disable` and `rm -rf` among them, and they isolate nothing else from +the developer's settings. Each run's full event stream is kept, so the evidence is the dispatch itself rather than what the outer session chose to print. @@ -2152,7 +2158,7 @@ for f in docs/plans/2026-09-26-plan-coverage.md docs/plans/fixture-uncovered.md k=$((k+1)) (cd "$W" && claude -p --plugin-dir "$R/plugins/working-process" \ --add-dir "$R/plugins/working-process" \ - --setting-sources project \ + --disallowedTools "Bash(claude *)" \ --tools "Agent,Read,Grep,Glob,Bash" \ --allowedTools "Bash(git rev-parse:*)" \ --output-format stream-json --verbose \ @@ -2186,7 +2192,8 @@ for line in open(sys.argv[1]): e = json.loads(line) except ValueError: continue - m = e.get('message') or {} + m = e.get('message') + m = m if isinstance(m, dict) else {} content = m.get('content') if e.get('parent_tool_use_id') in calls and m.get('role') == 'assistant': text = ' '.join(c.get('text', '') for c in content or [] if isinstance(c, dict)) @@ -2216,9 +2223,12 @@ done | tee "$W/reports.txt" ``` For every run, expected: exactly one dispatch, of -`working-process:propagation-auditor` with model `haiku`; `denials: []`; -and a report. No dispatch, a denial, or no report fails the test, and -the stream stays for inspection. The event shapes are the CLI's own; an +`working-process:propagation-auditor` with model `haiku`, and a report. +No dispatch or no report fails the test, and the stream stays for +inspection. A denial fails it too, unless the stream shows the auditor +completing the same operation through an allowed alternative — the +denied `cat` followed by a `Read` of that file — since a complete report +does not prove the check behind it ran. The event shapes are the CLI's own; an extractor that finds nothing in a stream that plainly holds a dispatch is a test defect to fix, never a pass. @@ -2324,3 +2334,13 @@ The fifth line, on Task 13, comes from Codex's second opinion instead: - fixed 2026-09-26 — the withdrawal ruled outside any hit had no ledger home on a closed loop; ruling: 2026-09-26; Task 1 says the tombstone is its record there, check I added - fixed 2026-09-26 — Task 13's extractor took the report from the dispatch's tool result, which for a background card may only acknowledge the launch; license: Task 13's stated purpose; the prompt asks for a foreground dispatch and the extractor reads the dispatched agent's last message, falling back to the tool result - hit fixed 2026-09-26 — the sentence opening this block counted three changed texts over five lines; it now names Tasks 1, 2 and 5 in four lines and gives the fifth its own source + +Task 13's first run, during implementation, found three departures in +the card and four defects in the task's own harness. The card fix +landed as `f8e743b`; the harness fixes are in Task 13 above, and the +evidence is the run's record in the dispatcher's store. + +- fixed 2026-09-26 — the haiku auditor counted the Contracts relation once per side, wrote a `table-closure:` line on a plan's report, and opened reports with narration; license: D27 and the spec's *Table closure* ("3 relations checked", "A document other than a technical design gets no line"), and the card's own "Open with the self-report"; the card now counts one relation per declaration row, forbids the line on any other document, and puts `model:` first with nothing before it (`f8e743b`) +- fixed 2026-09-26 — Task 13's harness: `--setting-sources project` dropped the agents `--plugin-dir` loads, the extractor failed on an event whose `message` is a string, `grep -c` exits 1 on a zero count, and the step overstated the run's permission limits; ruling: 2026-09-26; the run now denies `Bash(claude *)` instead, the extractor skips such events, the count takes `|| true`, and Step 2 states the real limits +- fixed 2026-09-26 — Task 13 failed any denial, or on the draft wording passed any complete report; ruling: 2026-09-26; a denial fails the test unless the stream shows the same operation completed through an allowed alternative, since a complete report does not prove its check ran (Codex's second opinion) + From 7237a7cbb32397ff0959f179727a83e74caa303a Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 20:02:31 +0200 Subject: [PATCH 082/143] docs(working-process): plan-coverage spec D31 and plan Tasks 14-16 --- docs/plans/2026-09-26-plan-coverage.md | 341 +++++++++++++++++- docs/specs/2026-09-25-plan-coverage-design.md | 35 +- 2 files changed, 372 insertions(+), 4 deletions(-) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index a2f9361..956bba2 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -158,7 +158,9 @@ Modified, all under `plugins/working-process/`: - `skills/grilling-session/SKILL.md` — Task 10: updating the register. - `README.md`, `CHANGELOG.md` — Task 11. -Created: nothing. Not modified: `agents/architect.md` (D28.1), the +Created: `plugins/working-process/scripts/decision-coverage.py` and +`tests/working-process/test_decision_coverage.py` (Task 14). Not +modified: `agents/architect.md` (D28.1), the glossary (already applied — Deviation 6), and `process-status`, which runs the Unfinished-work commands as the lifecycle rule publishes them and names none itself. @@ -178,7 +180,10 @@ and names none itself. - Tasks 7, 8 and 10 depend on nothing else in this plan. - `claude plugin validate` runs once, in Task 12, after every file has changed. Task 13 runs the changed auditor card on fixtures and needs - every earlier task landed. + every earlier task landed. Tasks 14–16 were added during + implementation, after Task 13 measured duty 10 unstable: Task 14 + writes the script, Task 15 points the card at it, and Task 16 re-runs + Task 13 twice on the script-backed card. --- @@ -2160,7 +2165,7 @@ for f in docs/plans/2026-09-26-plan-coverage.md docs/plans/fixture-uncovered.md --add-dir "$R/plugins/working-process" \ --disallowedTools "Bash(claude *)" \ --tools "Agent,Read,Grep,Glob,Bash" \ - --allowedTools "Bash(git rev-parse:*)" \ + --allowedTools "Bash(git rev-parse:*)" "Bash(python3 */scripts/decision-coverage.py *)" \ --output-format stream-json --verbose \ "Dispatch the working-process:propagation-auditor agent on the haiku model over $f, in the foreground, and wait for its report. Tell it to walk only duties 10 and 11 of its card and to report in the card's output shape.") \ > "$W/run-$k.jsonl" 2> "$W/run-$k.err" @@ -2276,6 +2281,335 @@ developer, and leave `sync-rules`, the version and both documents' --- +### Task 14: The coverage derivation script and its tests + +**Files:** +- Create: `plugins/working-process/scripts/decision-coverage.py` +- Create: `tests/working-process/test_decision_coverage.py` + +**Interfaces:** +- Consumes: the grammar Tasks 1 and 2 wrote into + `rules/spec-plan-lifecycle.md` (*Decision register*, *Plan + annotations*) and the report shapes Task 5 wrote into the auditor card + (duty 10 and `## Output`). +- Produces: the command + `python3 plugins/working-process/scripts/decision-coverage.py `, + which Task 15's card text runs as + `python3 ${CLAUDE_PLUGIN_ROOT}/scripts/decision-coverage.py `. + +**Realizes:** D31 + +This task is code, so its steps are test-driven rather than +Find/Replace. The contract below is the requirement; the tests encode +it, and the script is written to pass them. Python 3.9 or later, the +standard library only, no third-party package. + +**The contract.** + +- Invocation: one positional argument, the path of a plan or a design + spec, relative to the working directory or absolute. Paths the + document names (`spec:`, `**Follows:**`) resolve against the + document's own directory. Output goes to stdout; exit code 0 whenever + the derivation ran, hits or not; 2 on a usage error or an unreadable + file, with a one-line message on stderr. +- A plan is a document whose frontmatter carries `spec:`; a design spec + is one that does not. Frontmatter is the block between the `---` on + the first line and the next `---`. `spec:` is a single path or an + inline list `[a, b]`. +- Fenced code blocks — a line opening with three backticks toggles a + fence — are skipped everywhere: nothing inside one is an entry, an + annotation or a heading. +- **Register** (per spec): the `decisions:` field absent → the block + line `decision-coverage: not checked — no decision register` + and nothing else for that spec. Any value other than `registered` → a + hit and `not counted — malformed register`. Otherwise the register is + the `## Decisions` section, up to the next line opening `## `; no such + section → a hit and `not counted`. An entry is a line matching + `^(\s*)- \*\*(D\d+(?:\.\d+)?)\*\*`; indentation of two or more spaces + marks a child. Its identity paragraph is that line plus the + continuation lines up to a blank line, the next line opening a list + item, or the section's end, joined with single spaces. Malformed, each + a hit: a numbered-list entry (`^\s*\d+\.\s+\*\*D`), a duplicate + identifier, a child `D.` not nested under entry `D` or whose + parent does not exist, a group carrying a state token, a state token + not matching the grammar, a `replaced by` naming a missing + identifier, itself or a group, and a `replaced by` chain closing a + cycle. Any malformation → `not counted` for that spec, and its other + checks stop. +- **State token**: the first segment of the identity paragraph that + begins with `withdrawn` right after a full stop and whitespace; it + runs to the paragraph's end and must match + `withdrawn , ruling: [; replaced by ]`, + optionally ending with a full stop. A group is an entry with children; + a leaf is every other entry. +- **Plan annotations**: tasks are `### Task ` headings; a task's + `**Realizes:**` line is a line opening `**Realizes:** ` between its + heading and the next line opening `#`; its value is `none` or a + comma-separated list. Global Constraints entries are lines opening + `- **Realizes:** ` inside `## Global Constraints`; the identifiers + run up to the first ` — `. `**Defers:** — ; ruling: ` + and `**Follows:** ` lines count only inside + `## Deferrals and predecessors`. +- **Qualification**: where `spec:` names two or more specs, every + identifier is written `#`; a bare + one is a hit. Each spec's pass reads only the identifiers qualified + with its path; a `**Defers:**` line for another spec is out of scope + there. +- **Steps**, per registered spec, in order — the duty's steps 3 to 7: + check each `**Follows:**` line (missing file, no shared spec, or + `status` other than `implemented` → a hit, and the line lends + nothing); collect the cited set from local annotations and from each + accepted predecessor's annotations for the specs both name (an + identifier cited locally and inherited counts as local); a cited + withdrawn identifier, group or undefined identifier is a hit, except + an inherited citation of an identifier withdrawn since, which is + skipped and listed; check each `**Defers:**` line (undefined, + withdrawn, group, or in the cited set → a hit, and it subtracts + nothing); the counted set is the leaves not withdrawn less the + accepted deferrals; every counted identifier the cited set lacks is a + hit. A task with no `**Realizes:**` line, in a plan whose `spec:` + names a registered spec, is a hit. +- **A design spec audited alone** runs the register checks only and + prints `register well formed`, `not counted — malformed register` or + `not checked — no decision register`. +- **Output**, in this order: every hit, one line each, + `: — — derivation: decision-coverage.py, `; + then, per spec in `spec:` order, its block, exactly as the auditor + card's `## Output` shows it: + + decision-coverage: / covered; inherited [..]; deferred [..]; uncovered [..] + → , + withdrawn since: ← () + + `` is the path as `spec:` writes it. Map lines follow the + register's order, one per counted identifier; an uncovered one maps to + `—`. A site is `Task `, or + `Global Constraints, line : ""` where `` are the + first six words after the annotation clause with `**` and backticks + removed, or ` ()`. Local sites come before + inherited ones. Every slot is always present, an empty one `[]`, and + identifiers inside slots follow register order. The script prints no + `CLEAN`: other duties may still hit. + +- [ ] **Step 1: Write the failing tests** + +`tests/working-process/test_decision_coverage.py`, `unittest`, each +test building its documents in a `tempfile.TemporaryDirectory()` and +running the script with `subprocess.run([sys.executable, SCRIPT, +path], capture_output=True, text=True)`, where `SCRIPT` is resolved +from the test file's location. One test per case, asserting the exact +lines named: + +1. A registered spec with `D1`, `D2` and a group `D3` with `D3.1`, + `D3.2`; a plan whose three tasks cite `D1`, `D3.1`, `D3.2` and whose + Global Constraints entry `- **Realizes:** D2 — **No code** anywhere.` + → `decision-coverage: ../specs/s.md 4/4 covered; inherited []; deferred []; uncovered []`, + map line ` D2 → Global Constraints, line : "No code anywhere."` + with `` that entry's line, and no hit line. +2. The same plan with one task's `D3.1` replaced by `none` → a hit + naming `D3.1`, `3/4 covered`, `uncovered [D3.1]`, ` D3.1 → —`. +3. A plan quoting `**Realizes:** D9` and `**Defers:** D1 — x; ruling: 2026-01-01` + inside a fenced block, while citing `D1` in a task → no hit, and no + `D9` anywhere in the output. +4. A task with no `**Realizes:**` line → a hit naming that task. +5. A register entry `D2` ending + `. withdrawn superseded by D1. See notes, ruling: 2026-01-01; replaced by D1.` + → `D2` absent from the counted set, a plan citing `D2` gets a hit. +6. Malformed registers, one test each: a duplicate `D1`; a child `D4.1` + with no `D4`; a group carrying a token; `replaced by` naming itself; + `D1 → D2 → D1` as a cycle; a numbered list → each a hit and + `not counted — malformed register`, no map. +7. A spec with no `decisions:` → `not checked — no decision register`, + and a plan descending from it alone needs no `**Realizes:**` lines. +8. A plan whose `spec:` names two registered specs, citing a bare `D1` + → a hit on the bare identifier. +9. A valid `**Defers:** D2 — later; ruling: 2026-01-01` → `D2` out of + the counted set and in `deferred [D2]`; a `**Defers:**` naming a + cited identifier → a hit, and the identifier still counted. +10. `**Follows:** ../plans/p0.md` naming an implemented predecessor that + cites `D2` → `D2` covered, `inherited [D2]`, + ` D2 → ../plans/p0.md (Task 1)`; the predecessor at `status: draft` + → a hit and `D2` uncovered; the predecessor citing a `D2` the + register has since withdrawn → a `withdrawn since:` line and no hit. +11. A registered spec audited alone → `register well formed`; with a + duplicate → `not counted — malformed register`. +12. The repository's own documents: the script over + `docs/plans/2026-09-26-plan-coverage.md` prints no hit and a summary + whose two counts equal the register's leaves; over + `docs/specs/2026-09-25-plan-coverage-design.md`, `register well formed`. + +- [ ] **Step 2: Run the tests to see them fail** + +Run: `python3 -m unittest discover -s tests/working-process -v` +Expected: every test fails or errors, the script not existing yet. + +- [ ] **Step 3: Write the script** + +`plugins/working-process/scripts/decision-coverage.py`, executable, +opening with `#!/usr/bin/env python3` and a docstring naming the +contract's home — the spec-plan-lifecycle rule's *Decision register* +and *Plan annotations*, and duty 10 of the propagation-auditor card. +Match the contract above; add nothing it does not name. + +- [ ] **Step 4: Run the tests to see them pass** + +Run: `python3 -m unittest discover -s tests/working-process -v` +Expected: every test passes, output pristine. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/scripts/decision-coverage.py tests/working-process/test_decision_coverage.py +git commit -m "feat(working-process): decision coverage derivation script" +``` + +--- + +### Task 15: The auditor runs the script + +**Files:** +- Modify: `plugins/working-process/agents/propagation-auditor.md` — the + opening paragraph of duty 10. +- Modify: `plugins/working-process/README.md` — the `## Decision + register` section. +- Modify: `plugins/working-process/CHANGELOG.md` — one bullet under + `## Unreleased`. + +**Interfaces:** +- Consumes: the command Task 14 produces. +- Produces: nothing a later task reads. + +**Realizes:** D31 + +- [ ] **Step 1: Record the before values** + +```bash +C=plugins/working-process/agents/propagation-auditor.md +R=plugins/working-process/README.md +L=plugins/working-process/CHANGELOG.md +n() { tr -s '[:space:]' ' ' < "$1"; } +echo "A $(n $C | grep -oF 'scripts/decision-coverage.py ' | wc -l)" +echo "B $(n $C | grep -oF 'never walk it by hand' | wc -l)" +echo "C $(n $R | grep -oF 'scripts/decision-coverage.py' | wc -l)" +echo "D $(n $L | grep -oF 'scripts/decision-coverage.py' | wc -l)" +``` + +Expected: `A 0`, `B 0`, `C 0`, `D 0`. + +- [ ] **Step 2: Duty 10 runs the script** + +Find in `plugins/working-process/agents/propagation-auditor.md`: + +``` +place *Plan annotations* gives it: a code block or a quoted example +describes the grammar. For each spec the plan's `spec:` names: +``` + +Replace with: + +``` +place *Plan annotations* gives it: a code block or a quoted example +describes the grammar. + +Run the derivation, never walk it by hand: +`python3 ${CLAUDE_PLUGIN_ROOT}/scripts/decision-coverage.py `, +once per audited document, as a single command from the repository +root. It performs the steps below and prints this duty's hits and +`decision-coverage:` lines in the report's shapes; copy its output into +your report unchanged, neither recounting nor reordering it. If it exits +non-zero, or you cannot run it, report that as a hit naming the command +and its message, and write no `decision-coverage:` line — never derive +the sets yourself. The steps below define what the script does. For +each spec the plan's `spec:` names: +``` + +- [ ] **Step 3: The README names the script** + +Find in `plugins/working-process/README.md`: + +``` +the field is reported as not checked. The grammar lives in the +spec-plan-lifecycle rule. +``` + +Replace with: + +``` +the field is reported as not checked. The grammar lives in the +spec-plan-lifecycle rule. The auditor derives the decision coverage by +running `scripts/decision-coverage.py` with `python3`, so a session +that asks before running a command asks once for it. +``` + +- [ ] **Step 4: The CHANGELOG names the script** + +Find in `plugins/working-process/CHANGELOG.md`: + +``` +- The propagation duties checklist gains rows for the two duties and + the duty-2 anchor an earlier task has already rewritten. +``` + +Replace with: + +``` +- The propagation duties checklist gains rows for the two duties and + the duty-2 anchor an earlier task has already rewritten. +- The decision coverage is derived by `scripts/decision-coverage.py`, + which the auditor runs with `python3` (3.9 or later, standard library + only), rather than by the auditor walking the steps itself. +``` + +- [ ] **Step 5: Verify** + +Run the Step 1 command again, then `claude plugin validate +plugins/working-process`. + +Expected: `A 1`, `B 1`, `C 1`, `D 1`, and validation passes. + +- [ ] **Step 6: Commit** + +```bash +git add plugins/working-process/agents/propagation-auditor.md plugins/working-process/README.md plugins/working-process/CHANGELOG.md +git commit -m "feat(working-process): the propagation auditor runs the coverage script" +``` + +--- + +### Task 16: Run Task 13 twice more on the script-backed card + +**Files:** +- Test: the Task 13 fixture, and the evidence files + `.claude/working-process/2026-09-26-plan-coverage/task-13-auditor-runs-.md`. + +**Interfaces:** +- Consumes: Tasks 14 and 15, and Task 13's steps. +- Produces: nothing. + +**Realizes:** none + +- [ ] **Step 1: Run Task 13 in full, twice** + +Run Task 13's Steps 1–4 two times in a row, asking the developer once +before the first — the plugin is disabled and restored around each. +After each run, rename the evidence file and streams directory to carry +the run's number, `-2` and `-3`, so neither overwrites the other. + +- [ ] **Step 2: Compare** + +Expected: both runs meet every expectation of Task 13 Step 3 with the +numbers exact — `N/N` on this plan, `N-1/N` with `uncovered [D15]` on +the copy, `register well formed` on the spec, `3 relations checked` +and the `reder` hit on the technical design — and each report's +`decision-coverage:` lines equal the script's own output over the same +document, run directly. Two agreeing runs are the acceptance criterion, +not a statistical proof of reliability; a single departure fails the +task and goes to the developer with both reports. + +- [ ] **Step 3: Report** + +No commit. Report both runs to the developer. + ## Review rounds ### 2026-09-26 — plan-adversary, fable 5.1, blocking (round 1, full-document) @@ -2343,4 +2677,5 @@ evidence is the run's record in the dispatcher's store. - fixed 2026-09-26 — the haiku auditor counted the Contracts relation once per side, wrote a `table-closure:` line on a plan's report, and opened reports with narration; license: D27 and the spec's *Table closure* ("3 relations checked", "A document other than a technical design gets no line"), and the card's own "Open with the self-report"; the card now counts one relation per declaration row, forbids the line on any other document, and puts `model:` first with nothing before it (`f8e743b`) - fixed 2026-09-26 — Task 13's harness: `--setting-sources project` dropped the agents `--plugin-dir` loads, the extractor failed on an event whose `message` is a string, `grep -c` exits 1 on a zero count, and the step overstated the run's permission limits; ruling: 2026-09-26; the run now denies `Bash(claude *)` instead, the extractor skips such events, the count takes `|| true`, and Step 2 states the real limits - fixed 2026-09-26 — Task 13 failed any denial, or on the draft wording passed any complete report; ruling: 2026-09-26; a denial fails the test unless the stream shows the same operation completed through an allowed alternative, since a complete report does not prove its check ran (Codex's second opinion) +- fixed 2026-09-26 — Task 13's second run measured duty 10 unstable on the cheapest family (34/37 and 31/32 where 39 leaves stand), each miss following a denied compound command; ruling: 2026-09-26; the derivation becomes a script — spec D31, recorded in the spec under `### 2026-09-26 — fix from ../plans/2026-09-26-plan-coverage.md` — and Tasks 14–16 add it, point the card at it and re-run Task 13 twice; Task 13's run allows that one script, and the narration before `model:` is accepted as a known departure of the cheapest family, the workflow rule's reading of the body guarding it diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 25909be..0bc1e4f 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -188,6 +188,11 @@ What stands today: predecessor shipped is skipped — neither counted nor a hit — and listed apart in the report as `withdrawn since`. Argued in *Sequential plans*. +- **D31** — The coverage duty's derivation is a script the plugin ships, + `scripts/decision-coverage.py`, run by the auditor, whose output is + the duty's hits and `decision-coverage:` lines; the auditor reports + that output and never derives the sets itself. Argued in *The + coverage duty*. ## The register @@ -539,6 +544,22 @@ architect round — the duty runs steps 1 and 2 alone (D22), so a malformed register is found before any plan depends on it; *The report* gives the line it writes. +The derivation runs as a script the plugin ships (D31). Measured on +2026-09-26, the cheapest family walking these steps from prose read the +same plan three times as 39/39, 34/37 and — on a copy missing one +annotation — 31/32: it missed the annotations opening Global +Constraints entries and miscounted the register's leaves wherever the +harness denied the compound commands it reached for. Bounding the +section, telling groups from leaves and taking set differences are +exactly the operations it got wrong, and a parse settles them. The +script reads the document it is given — a plan, or a design spec audited +alone — with the specs and predecessors it names, performs the steps +above, and prints the duty's hits and `decision-coverage:` lines in the +report's shapes; it reports a document it cannot parse rather than +guessing. The auditor runs it once per audited document, copies its +output into the report, and never derives the sets itself. The steps +stay the duty's definition; the script is their one implementation. + The duty proves that every *declared* decision has an owner. Whether the register faithfully lists the spec's decisions is judgment, and belongs to the integrity auditor (see *Readers*). @@ -908,7 +929,8 @@ All under `plugins/working-process/` unless noted. `decision-coverage:` block, its `inherited [..]` slot, its `withdrawn since` listing and the three outcomes on a spec audited alone (`register well formed`, `not counted`, `not checked`); the - `table-closure:` line; the "exactly two lines" sentence; the + `table-closure:` line; the "exactly two lines" sentence; running + `scripts/decision-coverage.py` for duty 10; the coverage exception in *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. - `agents/integrity-auditor.md` — one sentence naming the register as a @@ -944,6 +966,9 @@ All under `plugins/working-process/` unless noted. second integrity audit: **Hit** holds any hit of the coverage duty whose fix needs a decision, and **Audit agent**'s disposed audit admits a hit closed by the developer's ruling. +- `scripts/decision-coverage.py` — new: the coverage duty's derivation + (D31); its tests live outside the plugin, in the repository's + `tests/working-process/`. - `README.md`, `CHANGELOG.md` — the new duties and the convention. ## Open questions @@ -1064,3 +1089,11 @@ audit's questions 1 and 2 were disposed as its D26 and D3 lines. - fixed 2026-09-26 — implementer question 8: where a withdrawal the developer rules outside any hit lands in the ledger once the spec's loop has closed; ruling: 2026-09-26; option (b) — on a closed loop, for a spec not `implemented` and a withdrawal tied to no hit or finding, the tombstone is the record and no ledger line is written (*Entry states*) +### 2026-09-26 — fix from ../plans/2026-09-26-plan-coverage.md + +The plan's Task 13 ran the changed auditor card on controlled cases and +measured duty 10 unstable on the cheapest family. The developer ruled +that the derivation becomes a script in this change. + +- fixed 2026-09-26 — the coverage duty's derivation, walked from prose on the cheapest family, read one plan as 39/39, 34/37 and 31/32 across runs; ruling: 2026-09-26; D31 added, *The coverage duty* gives the script and the measurement, *Changes by file* lists the script and its tests — the plan's line under its Task 13 record points here + From b4a0008cbebfa7fca64cf5679855929486120f25 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 20:08:14 +0200 Subject: [PATCH 083/143] feat(working-process): decision coverage derivation script --- .../scripts/decision-coverage.py | 532 ++++++++++++++++++ .../working-process/test_decision_coverage.py | 505 +++++++++++++++++ 2 files changed, 1037 insertions(+) create mode 100755 plugins/working-process/scripts/decision-coverage.py create mode 100644 tests/working-process/test_decision_coverage.py diff --git a/plugins/working-process/scripts/decision-coverage.py b/plugins/working-process/scripts/decision-coverage.py new file mode 100755 index 0000000..62b2b6d --- /dev/null +++ b/plugins/working-process/scripts/decision-coverage.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 +"""Derive decision coverage for a plan, or check a design spec's register. + +Usage: decision-coverage.py + +The grammar this script reads is defined in the spec-plan-lifecycle rule, +under *Decision register* and *Plan annotations*; the steps and the +report shapes are duty 10 and `## Output` of the propagation-auditor +card. Where this script and those texts disagree, the texts govern. + +Output goes to stdout: every hit, one line each, then one +`decision-coverage:` block per spec. Exit 0 whenever the derivation ran, +hits or not; 2 on a usage error or an unreadable file. +""" + +import os +import re +import sys + +TOOL = "decision-coverage.py" + +ENTRY_RE = re.compile(r"^(\s*)- \*\*(D\d+(?:\.\d+)?)\*\*") +NUMBERED_RE = re.compile(r"^\s*\d+\.\s+\*\*D") +LIST_ITEM_RE = re.compile(r"^\s*(?:[-*+]|\d+\.)\s") +TOKEN_START_RE = re.compile(r"\.\s+(withdrawn\b.*)$") +TOKEN_RE = re.compile( + r"^withdrawn (.+), ruling: (\d{4}-\d{2}-\d{2})" + r"(?:; replaced by (D\d+(?:\.\d+)?))?\.?$" +) +TASK_RE = re.compile(r"^### Task (\d+)") +FIELD_RE = re.compile(r"^([A-Za-z_][\w-]*):\s*(.*?)\s*$") + + +class Unreadable(Exception): + pass + + +# --- reading ------------------------------------------------------------- + +def read_lines(path): + try: + with open(path, encoding="utf-8") as handle: + return handle.read().splitlines() + except (OSError, UnicodeDecodeError) as error: + raise Unreadable(f"cannot read {path}: {error.strerror or error}") + + +def frontmatter(lines): + """Fields between the `---` on line 1 and the next `---`. + + Returns ({key: (value, line number)}, index of the first body line). + """ + fields = {} + if not lines or lines[0].strip() != "---": + return fields, 0 + for index in range(1, len(lines)): + if lines[index].strip() == "---": + return fields, index + 1 + match = FIELD_RE.match(lines[index]) + if match: + fields[match.group(1)] = (match.group(2), index + 1) + return fields, len(lines) + + +def spec_paths(value): + """`spec:` as a single path or an inline list `[a, b]`.""" + value = value.strip() + if value.startswith("[") and value.endswith("]"): + value = value[1:-1] + return [item.strip().strip("'\"") for item in value.split(",") if item.strip()] + + +def unfenced(lines, start): + """(line number, text) pairs of the body, fenced code blocks removed.""" + kept, in_fence = [], False + for index in range(start, len(lines)): + text = lines[index] + if text.startswith("```"): + in_fence = not in_fence + continue + if not in_fence: + kept.append((index + 1, text)) + return kept + + +def resolve(base_display, written): + """A path a document names, resolved against that document's directory.""" + return os.path.normpath(os.path.join(os.path.dirname(base_display), written)) + + +def same_file(a, b): + return os.path.abspath(a) == os.path.abspath(b) + + +# --- the register -------------------------------------------------------- + +class Entry: + def __init__(self, ident, line, child): + self.ident = ident + self.line = line + self.child = child + self.children = [] + self.token = None + self.withdrawn = False + self.replaced_by = None + + @property + def group(self): + return bool(self.children) + + +class Register: + """Outcome of steps 1 and 2 for one spec: legacy, malformed or counted.""" + + def __init__(self, state, entries=None, hits=None): + self.state = state # "legacy" | "malformed" | "ok" + self.entries = entries or {} + self.hits = hits or [] + + def leaves(self): + return [e for e in self.entries.values() if not e.group] + + +def hit(path, line, claim, step): + return f"{path}:{line} — {claim} — derivation: {TOOL}, {step}" + + +def read_register(display, lines): + fields, body_start = frontmatter(lines) + if "decisions" not in fields: + return Register("legacy") + value, field_line = fields["decisions"] + if value != "registered": + return Register("malformed", hits=[hit( + display, field_line, + f"`decisions:` is `{value}`, not `registered`", "step 1")]) + + body = unfenced(lines, body_start) + start = next((i for i, (_, text) in enumerate(body) + if text.rstrip() == "## Decisions"), None) + if start is None: + return Register("malformed", hits=[hit( + display, field_line, + "`decisions: registered` but no `## Decisions` section", "step 2")]) + section = [] + for number, text in body[start + 1:]: + if text.startswith("## "): + break + section.append((number, text)) + return parse_register(display, section) + + +def parse_register(display, section): + hits, entries, parent = [], {}, None + + def bad(line, claim): + hits.append(hit(display, line, claim, "step 2")) + + for index, (number, text) in enumerate(section): + if NUMBERED_RE.match(text): + bad(number, "register entry written as a numbered list") + continue + match = ENTRY_RE.match(text) + if not match: + continue + indent, ident = len(match.group(1)), match.group(2) + child = indent >= 2 + paragraph = [text.strip()] + for _, following in section[index + 1:]: + if not following.strip() or LIST_ITEM_RE.match(following): + break + paragraph.append(following.strip()) + paragraph = " ".join(paragraph) + + if ident in entries: + bad(number, f"duplicate identifier {ident}") + continue + entry = Entry(ident, number, child) + if child: + parent_id = ident.split(".")[0] + if "." not in ident: + bad(number, f"{ident} is nested but is not a child identifier") + continue + if parent_id not in entries: + bad(number, f"child {ident} has no parent entry {parent_id}") + continue + if parent is None or parent.ident != parent_id: + bad(number, f"child {ident} is not nested under {parent_id}") + continue + parent.children.append(entry) + else: + if "." in ident: + bad(number, f"child {ident} is not nested under {ident.split('.')[0]}") + continue + parent = entry + + start = TOKEN_START_RE.search(paragraph) + if start: + token = start.group(1) + parsed = TOKEN_RE.match(token) + if not parsed: + bad(number, f"{ident} carries a `withdrawn` segment that does not " + "match the state token's grammar") + else: + entry.token = token + entry.withdrawn = True + entry.replaced_by = parsed.group(3) + entries[ident] = entry + + for entry in entries.values(): + if entry.group and entry.token: + bad(entry.line, f"group {entry.ident} carries a state token") + target = entry.replaced_by + if target is None: + continue + if target == entry.ident: + bad(entry.line, f"{entry.ident} is replaced by itself") + elif target not in entries: + bad(entry.line, f"{entry.ident} is replaced by {target}, which the register does not define") + elif entries[target].group: + bad(entry.line, f"{entry.ident} is replaced by group {target}") + + reported = set() + for entry in entries.values(): + path, current = [], entry + while current is not None and current.replaced_by in entries: + if current.ident in path: + cycle = path[path.index(current.ident):] + key = frozenset(cycle) + if len(cycle) > 1 and key not in reported: + reported.add(key) + first = min((entries[i] for i in cycle), key=lambda e: e.line) + bad(first.line, "`replaced by` chain closes a cycle: " + + " → ".join(cycle + [cycle[0]])) + break + path.append(current.ident) + current = entries[current.replaced_by] + + if hits: + return Register("malformed", hits=hits) + return Register("ok", entries=entries) + + +# --- the plan ------------------------------------------------------------ + +class Citation: + def __init__(self, token, line, site): + self.token = token + self.line = line + self.site = site + + +class Plan: + def __init__(self, display, lines): + self.display = display + fields, body_start = frontmatter(lines) + self.fields = fields + self.specs = spec_paths(fields["spec"][0]) if "spec" in fields else [] + self.status = fields.get("status", ("", 0))[0] + self.citations = [] # Citation, in document order + self.tasks = [] # (number, heading line, has a Realizes line) + self.defers = [] # (line, token) + self.follows = [] # (line, written path) + self._parse(unfenced(lines, body_start)) + + def _parse(self, body): + section, task = None, None + for index, (number, text) in enumerate(body): + if text.startswith("#"): + task = None + match = TASK_RE.match(text) + if match: + task = [match.group(1), number, False] + self.tasks.append(task) + if text.startswith("## "): + section = text[3:].strip() + continue + if task is not None and text.startswith("**Realizes:** "): + task[2] = True + value = text[len("**Realizes:** "):].strip() + if value != "none": + for token in split_ids(value): + self.citations.append(Citation(token, number, f"Task {task[0]}")) + continue + if section == "Global Constraints" and text.startswith("- **Realizes:** "): + rest = text[len("- **Realizes:** "):] + ids, _, words = rest.partition(" — ") + for following_number, following in body[index + 1:]: + if (not following.strip() or LIST_ITEM_RE.match(following) + or following.startswith("#")): + break + words += " " + following.strip() + words = " ".join(words.replace("**", "").replace("`", "").split()[:6]) + site = f'Global Constraints, line {number}: "{words}"' + for token in split_ids(ids): + self.citations.append(Citation(token, number, site)) + continue + if section == "Deferrals and predecessors": + if text.startswith("**Defers:** "): + token = text[len("**Defers:** "):].split(" — ")[0].strip() + self.defers.append((number, token)) + elif text.startswith("**Follows:** "): + self.follows.append((number, text[len("**Follows:** "):].strip())) + + +def split_ids(value): + return [item.strip() for item in value.split(",") if item.strip()] + + +def scoped(plan, token, spec_abs): + """The identifier `token` names for the spec at spec_abs, or None. + + A plan naming one spec writes bare identifiers; one naming two or more + qualifies each with the spec's path as its `spec:` writes it. + """ + if len(plan.specs) >= 2: + if "#" not in token: + return None + qualifier, ident = token.rsplit("#", 1) + return ident if same_file(resolve(plan.display, qualifier), spec_abs) else None + if len(plan.specs) == 1 and same_file(resolve(plan.display, plan.specs[0]), spec_abs): + return token + return None + + +# --- the derivation ------------------------------------------------------ + +class Audit: + def __init__(self, plan): + self.plan = plan + self.hits = [] + self.blocks = [] + self._predecessors = None + + def add(self, line): + if line not in self.hits: + self.hits.append(line) + + def predecessors(self): + """Step 3, once per plan: the accepted `**Follows:**` lines.""" + if self._predecessors is not None: + return self._predecessors + accepted = [] + own = [os.path.abspath(resolve(self.plan.display, s)) for s in self.plan.specs] + for line, written in self.plan.follows: + display = resolve(self.plan.display, written) + try: + pred = Plan(display, read_lines(display)) + except Unreadable: + self.add(hit(self.plan.display, line, + f"`**Follows:**` names {written}, which does not exist", "step 3")) + continue + theirs = [os.path.abspath(resolve(display, s)) for s in pred.specs] + shared = [s for s in own if s in theirs] + if not shared: + self.add(hit(self.plan.display, line, + f"`**Follows:**` names {written}, which shares no spec with this plan", + "step 3")) + continue + if pred.status != "implemented": + self.add(hit(self.plan.display, line, + f"`**Follows:**` names {written}, which is at `status: " + f"{pred.status or '(none)'}`, not `implemented`", "step 3")) + continue + accepted.append((written, pred, shared)) + self._predecessors = accepted + return accepted + + def check_spec(self, written): + plan = self.plan + spec_display = resolve(plan.display, written) + spec_abs = os.path.abspath(spec_display) + register = read_register(spec_display, read_lines(spec_display)) + for line in register.hits: + self.add(line) + if register.state == "legacy": + self.blocks.append(f"decision-coverage: {written} not checked — no decision register") + return + if register.state == "malformed": + self.blocks.append(f"decision-coverage: {written} not counted — malformed register") + return + entries = register.entries + multi = len(plan.specs) >= 2 + + def show(ident): + return f"{written}#{ident}" if multi else ident + + def problem(ident): + if ident not in entries: + return "which the register does not define" + if entries[ident].group: + return "which is a group" + if entries[ident].withdrawn: + return "which is withdrawn" + return None + + # Step 3. + predecessors = [(w, p) for w, p, shared in self.predecessors() if spec_abs in shared] + + # Step 4: local citations, then inherited ones. + local, inherited, withdrawn_since = {}, {}, [] + for citation in plan.citations: + ident = scoped(plan, citation.token, spec_abs) + if ident is None: + continue + reason = problem(ident) + if reason: + self.add(hit(plan.display, citation.line, + f"cites {show(ident)}, {reason}", "step 4")) + sites = local.setdefault(ident, []) + if citation.site not in sites: + sites.append(citation.site) + for pred_written, pred in predecessors: + for citation in pred.citations: + ident = scoped(pred, citation.token, spec_abs) + if ident is None: + continue + site = f"{pred_written} ({citation.site})" + if ident in entries and not entries[ident].group and entries[ident].withdrawn: + withdrawn_since.append((ident, site)) + continue + reason = problem(ident) + if reason: + self.add(hit(pred.display, citation.line, + f"inherited citation of {show(ident)}, {reason}", "step 4")) + sites = inherited.setdefault(ident, []) + if site not in sites: + sites.append(site) + cited = set(local) | set(inherited) + + # Step 5. + deferred = set() + for line, token in plan.defers: + ident = scoped(plan, token, spec_abs) + if ident is None: + continue + reason = problem(ident) + if reason is None and ident in cited: + reason = "which this plan also cites" + if reason: + self.add(hit(plan.display, line, f"defers {show(ident)}, {reason}", "step 5")) + else: + deferred.add(ident) + + # Steps 6 and 7. + order = list(entries) + counted = [e.ident for e in register.leaves() + if not e.withdrawn and e.ident not in deferred] + covered = [i for i in counted if i in cited] + uncovered = [i for i in counted if i not in cited] + only_inherited = [i for i in covered if i not in local] + for ident in uncovered: + self.add(hit(spec_display, entries[ident].line, + f"{show(ident)} is counted but no task or constraint cites it", + "step 7")) + + def slot(idents): + return "[" + ", ".join(show(i) for i in idents) + "]" + + self.blocks.append( + f"decision-coverage: {written} {len(covered)}/{len(counted)} covered; " + f"inherited {slot(only_inherited)}; " + f"deferred {slot([i for i in order if i in deferred])}; " + f"uncovered {slot(uncovered)}") + for ident in counted: + sites = local.get(ident, []) + inherited.get(ident, []) + self.blocks.append(f" {show(ident)} → {', '.join(sites) if sites else '—'}") + withdrawn_since.sort(key=lambda item: order.index(item[0])) + for ident, site in withdrawn_since: + self.blocks.append(f" withdrawn since: {show(ident)} ← {site}") + + def plan_wide(self, registered): + """Step 7's plan-wide hits: missing annotations and bare identifiers.""" + plan = self.plan + if registered: + for number, heading_line, has_line in plan.tasks: + if not has_line: + self.add(hit(plan.display, heading_line, + f"Task {number} carries no `**Realizes:**` line", "step 7")) + if len(plan.specs) >= 2: + tokens = [(c.line, c.token) for c in plan.citations] + plan.defers + for line, token in sorted(tokens): + if "#" not in token: + self.add(hit(plan.display, line, + f"bare identifier {token} in a plan whose `spec:` names " + f"{len(plan.specs)} specs", "step 7")) + + +def audit_plan(display, lines): + plan = Plan(display, lines) + audit = Audit(plan) + registered = False + for written in plan.specs: + spec_display = resolve(display, written) + fields, _ = frontmatter(read_lines(spec_display)) + registered = registered or fields.get("decisions", ("", 0))[0] == "registered" + for written in plan.specs: + audit.check_spec(written) + audit.plan_wide(registered) + return audit.hits + audit.blocks + + +def audit_spec(display, lines): + register = read_register(display, lines) + outcome = { + "legacy": "not checked — no decision register", + "malformed": "not counted — malformed register", + "ok": "register well formed", + }[register.state] + return register.hits + [f"decision-coverage: {display} {outcome}"] + + +def main(argv): + if len(argv) != 2: + print("decision-coverage: usage: decision-coverage.py ", + file=sys.stderr) + return 2 + display = argv[1] + try: + lines = read_lines(display) + fields, _ = frontmatter(lines) + output = audit_plan(display, lines) if "spec" in fields else audit_spec(display, lines) + except Unreadable as error: + print(f"decision-coverage: {error}", file=sys.stderr) + return 2 + for line in output: + print(line) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tests/working-process/test_decision_coverage.py b/tests/working-process/test_decision_coverage.py new file mode 100644 index 0000000..8e61932 --- /dev/null +++ b/tests/working-process/test_decision_coverage.py @@ -0,0 +1,505 @@ +"""Tests for plugins/working-process/scripts/decision-coverage.py. + +Each test builds its documents in a temporary directory, runs the script +on one of them and asserts the exact lines the contract names. +""" + +import os +import re +import subprocess +import sys +import tempfile +import textwrap +import unittest +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] +SCRIPT = str(REPO / "plugins" / "working-process" / "scripts" / "decision-coverage.py") +DERIVATION = " — derivation: decision-coverage.py, " + +SPEC_BASIC = """\ +--- +status: draft +decisions: registered +--- + +# S + +## Decisions + +- **D1** — One. +- **D2** — Two. +- **D3** — Group. + - **D3.1** — Three one. + - **D3.2** — Three two. + +## Next + +Text. +""" + +PLAN_BASIC = """\ +--- +status: draft +spec: ../specs/s.md +--- + +# P + +## Global Constraints + +- **Realizes:** D2 — **No code** anywhere. +- **Commits are one line.** + +## File structure + +Text. + +### Task 1: one + +**Files:** a + +**Realizes:** D1 + +- [ ] **Step 1: do** + +### Task 2: two + +**Realizes:** D3.1 + +### Task 3: three + +**Realizes:** D3.2 +""" + + +def dedent(text): + return textwrap.dedent(text) + + +def line_of(text, needle): + """1-based number of the first line containing needle.""" + for number, line in enumerate(text.splitlines(), 1): + if needle in line: + return number + raise AssertionError(f"{needle!r} not in text") + + +class Result: + def __init__(self, proc): + self.proc = proc + self.lines = proc.stdout.splitlines() + self.hits = [line for line in self.lines if DERIVATION in line] + self.map = [line for line in self.lines if line.startswith(" ")] + self.blocks = [line for line in self.lines if line.startswith("decision-coverage: ")] + + +class CoverageTest(unittest.TestCase): + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.root = Path(self._tmp.name) + (self.root / "specs").mkdir() + (self.root / "plans").mkdir() + + def tearDown(self): + self._tmp.cleanup() + + def write(self, relative, text): + path = self.root / relative + path.write_text(text, encoding="utf-8") + return path + + def run_script(self, relative): + proc = subprocess.run( + [sys.executable, SCRIPT, str(self.root / relative)], + capture_output=True, + text=True, + ) + self.assertEqual(proc.returncode, 0, proc.stderr) + self.assertEqual(proc.stderr, "") + return Result(proc) + + def assert_hit_at(self, result, filename, line, needle=None): + prefix = f"{filename}:{line} — " + found = [h for h in result.hits if os.path.basename(h.split(" — ")[0].rsplit(":", 1)[0]) == filename + and h.split(" — ")[0].endswith(f":{line}")] + self.assertTrue(found, f"no hit at {prefix} in {result.lines}") + if needle is not None: + self.assertTrue(any(needle in h for h in found), f"{needle!r} not in {found}") + + # 1 + def test_01_full_coverage_with_global_constraint(self): + self.write("specs/s.md", SPEC_BASIC) + self.write("plans/p.md", PLAN_BASIC) + result = self.run_script("plans/p.md") + n = line_of(PLAN_BASIC, "- **Realizes:** D2") + self.assertEqual(result.hits, []) + self.assertEqual(result.lines, [ + "decision-coverage: ../specs/s.md 4/4 covered; inherited []; deferred []; uncovered []", + " D1 → Task 1", + f' D2 → Global Constraints, line {n}: "No code anywhere."', + " D3.1 → Task 2", + " D3.2 → Task 3", + ]) + self.assertNotIn("CLEAN", result.proc.stdout) + + # 2 + def test_02_uncovered_leaf(self): + self.write("specs/s.md", SPEC_BASIC) + plan = PLAN_BASIC.replace("**Realizes:** D3.1", "**Realizes:** none") + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assertEqual(len(result.hits), 1, result.lines) + self.assertIn("D3.1", result.hits[0]) + self.assertIn( + "decision-coverage: ../specs/s.md 3/4 covered; inherited []; deferred []; uncovered [D3.1]", + result.lines) + self.assertIn(" D3.1 → —", result.lines) + + # 3 + def test_03_fenced_grammar_instantiates_nothing(self): + self.write("specs/s.md", dedent("""\ + --- + decisions: registered + --- + + ## Decisions + + - **D1** — One. + """)) + plan = dedent("""\ + --- + spec: ../specs/s.md + --- + + ## Global Constraints + + ``` + - **Realizes:** D9 — quoted. + ``` + + ## Deferrals and predecessors + + ``` + **Defers:** D1 — x; ruling: 2026-01-01 + **Realizes:** D9 + ``` + + ### Task 1: one + + **Realizes:** D1 + + ``` + **Realizes:** D9 + ### Task 9: quoted + ``` + """) + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assertEqual(result.hits, []) + self.assertNotIn("D9", result.proc.stdout) + self.assertEqual(result.lines, [ + "decision-coverage: ../specs/s.md 1/1 covered; inherited []; deferred []; uncovered []", + " D1 → Task 1", + ]) + + # 4 + def test_04_task_without_realizes_line(self): + self.write("specs/s.md", SPEC_BASIC) + plan = PLAN_BASIC.replace("### Task 3: three\n\n**Realizes:** D3.2\n", + "### Task 3: three\n\nNo annotation.\n\n### Task 4: four\n\n**Realizes:** D3.2\n") + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assertEqual(len(result.hits), 1, result.lines) + self.assert_hit_at(result, "p.md", line_of(plan, "### Task 3"), "Task 3") + self.assertIn( + "decision-coverage: ../specs/s.md 4/4 covered; inherited []; deferred []; uncovered []", + result.lines) + + # 5 + def test_05_withdrawn_entry(self): + spec = dedent("""\ + --- + decisions: registered + --- + + ## Decisions + + - **D1** — One. + - **D2** — Two. Argued in *X*. + withdrawn superseded by D1. See notes, ruling: 2026-01-01; replaced by D1. + """) + self.write("specs/s.md", spec) + plan = dedent("""\ + --- + spec: ../specs/s.md + --- + + ### Task 1: one + + **Realizes:** D1, D2 + """) + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assertEqual(len(result.hits), 1, result.lines) + self.assert_hit_at(result, "p.md", line_of(plan, "**Realizes:** D1, D2"), "D2") + self.assertEqual(result.blocks, [ + "decision-coverage: ../specs/s.md 1/1 covered; inherited []; deferred []; uncovered []", + ]) + self.assertEqual(result.map, [" D1 → Task 1"]) + + # 6 + def assert_malformed(self, register, bad_line_needle): + spec = "---\ndecisions: registered\n---\n\n## Decisions\n\n" + dedent(register) + self.write("specs/s.md", spec) + self.write("plans/p.md", dedent("""\ + --- + spec: ../specs/s.md + --- + + ### Task 1: one + + **Realizes:** D1 + """)) + result = self.run_script("plans/p.md") + self.assertGreaterEqual(len(result.hits), 1, result.lines) + self.assert_hit_at(result, "s.md", line_of(spec, bad_line_needle)) + self.assertEqual(result.blocks, + ["decision-coverage: ../specs/s.md not counted — malformed register"]) + self.assertEqual(result.map, []) + + def test_06a_duplicate_identifier(self): + self.assert_malformed("""\ + - **D1** — One. + - **D2** — Two. + - **D1** — One again. + """, "One again") + + def test_06b_child_without_parent(self): + self.assert_malformed("""\ + - **D1** — One. + - **D4.1** — Orphan. + """, "Orphan") + + def test_06c_group_with_token(self): + self.assert_malformed("""\ + - **D1** — One. + - **D3** — Group. withdrawn old, ruling: 2026-01-01 + - **D3.1** — Child. + """, "Group.") + + def test_06d_replaced_by_itself(self): + self.assert_malformed("""\ + - **D1** — One. + - **D2** — Two. withdrawn old, ruling: 2026-01-01; replaced by D2 + """, "Two.") + + def test_06e_replacement_cycle(self): + self.assert_malformed("""\ + - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D2 + - **D2** — Two. withdrawn old, ruling: 2026-01-01; replaced by D1 + - **D3** — Three. + """, "One.") + + def test_06f_numbered_list(self): + self.assert_malformed("""\ + 1. **D1** — One. + 2. **D2** — Two. + """, "1. **D1**") + + # 7 + def test_07_legacy_spec(self): + self.write("specs/s.md", "---\nstatus: draft\n---\n\n# S\n\n## Decisions\n\n- **D1** — x.\n") + self.write("plans/p.md", dedent("""\ + --- + spec: ../specs/s.md + --- + + ### Task 1: one + + No annotation. + """)) + result = self.run_script("plans/p.md") + self.assertEqual(result.lines, + ["decision-coverage: ../specs/s.md not checked — no decision register"]) + + # 8 + def test_08_bare_identifier_in_multi_spec_plan(self): + one = "---\ndecisions: registered\n---\n\n## Decisions\n\n- **D1** — One.\n" + self.write("specs/a.md", one) + self.write("specs/b.md", one) + plan = dedent("""\ + --- + spec: [../specs/a.md, ../specs/b.md] + --- + + ### Task 1: one + + **Realizes:** ../specs/a.md#D1 + + ### Task 2: two + + **Realizes:** D1 + """) + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assert_hit_at(result, "p.md", line_of(plan, "**Realizes:** D1"), "D1") + self.assertIn( + "decision-coverage: ../specs/a.md 1/1 covered; inherited []; deferred []; uncovered []", + result.lines) + self.assertIn(" ../specs/a.md#D1 → Task 1", result.lines) + self.assertIn( + "decision-coverage: ../specs/b.md 0/1 covered; inherited []; deferred []; uncovered [../specs/b.md#D1]", + result.lines) + + # 9 + def test_09_deferrals(self): + self.write("specs/s.md", SPEC_BASIC) + valid = PLAN_BASIC.replace("**Realizes:** D3.1", "**Realizes:** none").replace( + "## File structure", + "## Deferrals and predecessors\n\n**Defers:** D3.1 — later; ruling: 2026-01-01\n\n## File structure") + self.write("plans/p.md", valid) + result = self.run_script("plans/p.md") + self.assertEqual(result.hits, []) + self.assertIn( + "decision-coverage: ../specs/s.md 3/3 covered; inherited []; deferred [D3.1]; uncovered []", + result.lines) + self.assertNotIn(" D3.1 → —", result.lines) + + cited = PLAN_BASIC.replace( + "## File structure", + "## Deferrals and predecessors\n\n**Defers:** D1 — later; ruling: 2026-01-01\n\n## File structure") + self.write("plans/p.md", cited) + result = self.run_script("plans/p.md") + self.assertEqual(len(result.hits), 1, result.lines) + self.assert_hit_at(result, "p.md", line_of(cited, "**Defers:** D1"), "D1") + self.assertIn( + "decision-coverage: ../specs/s.md 4/4 covered; inherited []; deferred []; uncovered []", + result.lines) + self.assertIn(" D1 → Task 1", result.lines) + + # 10 + def follows_case(self, spec, predecessor_status): + self.write("specs/s.md", spec) + self.write("plans/p0.md", dedent(f"""\ + --- + status: {predecessor_status} + spec: ../specs/s.md + --- + + ### Task 1: earlier + + **Realizes:** D2 + """)) + plan = dedent("""\ + --- + spec: ../specs/s.md + --- + + ## Deferrals and predecessors + + **Follows:** ../plans/p0.md + + ### Task 1: one + + **Realizes:** D1 + """) + self.write("plans/p.md", plan) + return plan, self.run_script("plans/p.md") + + def test_10a_inherited_from_implemented_predecessor(self): + spec = "---\ndecisions: registered\n---\n\n## Decisions\n\n- **D1** — One.\n- **D2** — Two.\n" + _, result = self.follows_case(spec, "implemented") + self.assertEqual(result.lines, [ + "decision-coverage: ../specs/s.md 2/2 covered; inherited [D2]; deferred []; uncovered []", + " D1 → Task 1", + " D2 → ../plans/p0.md (Task 1)", + ]) + + def test_10b_draft_predecessor_lends_nothing(self): + spec = "---\ndecisions: registered\n---\n\n## Decisions\n\n- **D1** — One.\n- **D2** — Two.\n" + plan, result = self.follows_case(spec, "draft") + self.assert_hit_at(result, "p.md", line_of(plan, "**Follows:**")) + self.assertIn( + "decision-coverage: ../specs/s.md 1/2 covered; inherited []; deferred []; uncovered [D2]", + result.lines) + self.assertIn(" D2 → —", result.lines) + + def test_10c_inherited_citation_withdrawn_since(self): + spec = ("---\ndecisions: registered\n---\n\n## Decisions\n\n- **D1** — One.\n" + "- **D2** — Two. withdrawn obsolete, ruling: 2026-02-01\n") + _, result = self.follows_case(spec, "implemented") + self.assertEqual(result.hits, []) + self.assertEqual(result.lines, [ + "decision-coverage: ../specs/s.md 1/1 covered; inherited []; deferred []; uncovered []", + " D1 → Task 1", + " withdrawn since: D2 ← ../plans/p0.md (Task 1)", + ]) + + # 11 + def test_11_spec_audited_alone(self): + self.write("specs/s.md", SPEC_BASIC) + result = self.run_script("specs/s.md") + self.assertEqual(len(result.lines), 1, result.lines) + self.assertTrue(result.lines[0].startswith("decision-coverage: ")) + self.assertTrue(result.lines[0].endswith("s.md register well formed"), result.lines) + + self.write("specs/s.md", SPEC_BASIC.replace("- **D2** — Two.", "- **D1** — Two.")) + result = self.run_script("specs/s.md") + self.assertEqual(len(result.hits), 1, result.lines) + self.assertEqual(len(result.blocks), 1) + self.assertTrue(result.blocks[0].endswith("s.md not counted — malformed register")) + + # 12 + def test_12_repository_documents(self): + spec = REPO / "docs" / "specs" / "2026-09-25-plan-coverage-design.md" + plan = REPO / "docs" / "plans" / "2026-09-26-plan-coverage.md" + ids = [] + in_register = False + in_fence = False + for line in spec.read_text(encoding="utf-8").splitlines(): + if line.startswith("```"): + in_fence = not in_fence + continue + if in_fence: + continue + if line.startswith("## "): + in_register = line.strip() == "## Decisions" + continue + match = re.match(r"^\s*- \*\*(D\d+(?:\.\d+)?)\*\*", line) + if in_register and match: + ids.append(match.group(1)) + leaves = [i for i in ids if not any(j.startswith(i + ".") for j in ids)] + self.assertGreater(len(leaves), 0) + + proc = subprocess.run([sys.executable, SCRIPT, str(plan)], capture_output=True, text=True) + self.assertEqual(proc.returncode, 0, proc.stderr) + result = Result(proc) + self.assertEqual(result.hits, []) + self.assertEqual(len(result.blocks), 1, result.lines) + match = re.match(r"^decision-coverage: \S+ (\d+)/(\d+) covered;", result.blocks[0]) + self.assertIsNotNone(match, result.blocks[0]) + self.assertEqual((int(match.group(1)), int(match.group(2))), (len(leaves), len(leaves))) + + proc = subprocess.run([sys.executable, SCRIPT, str(spec)], capture_output=True, text=True) + self.assertEqual(proc.returncode, 0, proc.stderr) + self.assertEqual(proc.stdout.splitlines(), + [f"decision-coverage: {spec} register well formed"]) + + +class UsageTest(unittest.TestCase): + def test_usage_error(self): + proc = subprocess.run([sys.executable, SCRIPT], capture_output=True, text=True) + self.assertEqual(proc.returncode, 2) + self.assertEqual(len(proc.stderr.strip().splitlines()), 1) + self.assertTrue(proc.stderr.startswith("decision-coverage: "), proc.stderr) + + def test_unreadable_file(self): + proc = subprocess.run([sys.executable, SCRIPT, "/nonexistent/x.md"], capture_output=True, text=True) + self.assertEqual(proc.returncode, 2) + self.assertEqual(len(proc.stderr.strip().splitlines()), 1) + self.assertTrue(proc.stderr.startswith("decision-coverage: "), proc.stderr) + + +if __name__ == "__main__": + unittest.main() From d8586568f317c4c8a24b96880a6e6c64a43921aa Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 20:16:32 +0200 Subject: [PATCH 084/143] feat(working-process): the propagation auditor runs the coverage script --- plugins/working-process/CHANGELOG.md | 3 +++ plugins/working-process/README.md | 4 +++- .../working-process/agents/propagation-auditor.md | 13 ++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/CHANGELOG.md b/plugins/working-process/CHANGELOG.md index 7f66f30..f294c54 100644 --- a/plugins/working-process/CHANGELOG.md +++ b/plugins/working-process/CHANGELOG.md @@ -37,6 +37,9 @@ Released versions of this plugin, newest first. every decision its spec makes. - The propagation duties checklist gains rows for the two duties and the duty-2 anchor an earlier task has already rewritten. +- The decision coverage is derived by `scripts/decision-coverage.py`, + which the auditor runs with `python3` (3.9 or later, standard library + only), rather than by the auditor walking the steps itself. - Run a rules re-sync after this update: the plan-adversary's `origin` values changed, and until the re-sync an installed workflow rule triages the new values by the old names. The re-sync also installs diff --git a/plugins/working-process/README.md b/plugins/working-process/README.md index c7e3aad..f4260f5 100644 --- a/plugins/working-process/README.md +++ b/plugins/working-process/README.md @@ -165,7 +165,9 @@ coverage from the two lists, the plan-adversary judges whether the citing tasks realize their decisions, and the integrity auditor checks that the register lists every decision the spec makes. A spec without the field is reported as not checked. The grammar lives in the -spec-plan-lifecycle rule. +spec-plan-lifecycle rule. The auditor derives the decision coverage by +running `scripts/decision-coverage.py` with `python3`, so a session +that asks before running a command asks once for it. ## Model selection diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index a78d3c1..26db67c 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -186,7 +186,18 @@ annotations*. Read them in the plugin's own copy, `${CLAUDE_PLUGIN_ROOT}/rules/spec-plan-lifecycle.md`, which matches this card's version, rather than recalling them. Read a line only at the place *Plan annotations* gives it: a code block or a quoted example -describes the grammar. For each spec the plan's `spec:` names: +describes the grammar. + +Run the derivation, never walk it by hand: +`python3 ${CLAUDE_PLUGIN_ROOT}/scripts/decision-coverage.py `, +once per audited document, as a single command from the repository +root. It performs the steps below and prints this duty's hits and +`decision-coverage:` lines in the report's shapes; copy its output into +your report unchanged, neither recounting nor reordering it. If it exits +non-zero, or you cannot run it, report that as a hit naming the command +and its message, and write no `decision-coverage:` line — never derive +the sets yourself. The steps below define what the script does. For +each spec the plan's `spec:` names: 1. Read the spec's `decisions:` field. Absent: the spec is legacy — write `not checked` for it and stop. Any value other than From d737769afccfa0737f13f6840022264d539eb11b Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 20:46:54 +0200 Subject: [PATCH 085/143] fix(working-process): coverage duty skips technical designs and keeps the self-report first --- .../agents/propagation-auditor.md | 11 +++--- .../scripts/decision-coverage.py | 14 +++++++- .../working-process/test_decision_coverage.py | 36 +++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index 26db67c..bb8504a 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -190,10 +190,13 @@ describes the grammar. Run the derivation, never walk it by hand: `python3 ${CLAUDE_PLUGIN_ROOT}/scripts/decision-coverage.py `, -once per audited document, as a single command from the repository -root. It performs the steps below and prints this duty's hits and -`decision-coverage:` lines in the report's shapes; copy its output into -your report unchanged, neither recounting nor reordering it. If it exits +once per audited plan or design spec — never on a technical design, +which this duty does not cover — as a single command from the +repository root. It performs the steps below and prints this duty's +hits and `decision-coverage:` lines in the report's shapes; copy its +output into your report unchanged, below your `model:` line, which +stays the report's first line, neither recounting nor reordering it. If +it exits non-zero, or you cannot run it, report that as a hit naming the command and its message, and write no `decision-coverage:` line — never derive the sets yourself. The steps below define what the script does. For diff --git a/plugins/working-process/scripts/decision-coverage.py b/plugins/working-process/scripts/decision-coverage.py index 62b2b6d..c9533c9 100755 --- a/plugins/working-process/scripts/decision-coverage.py +++ b/plugins/working-process/scripts/decision-coverage.py @@ -10,7 +10,10 @@ Output goes to stdout: every hit, one line each, then one `decision-coverage:` block per spec. Exit 0 whenever the derivation ran, -hits or not; 2 on a usage error or an unreadable file. +hits or not; 2 on a usage error or an unreadable file. A technical +design — a document under a `technical-designs` directory directly +under a `docs` directory — produces no output; this duty does not cover +it. """ import os @@ -510,12 +513,21 @@ def audit_spec(display, lines): return register.hits + [f"decision-coverage: {display} {outcome}"] +def is_technical_design(display): + """The resolved path has `docs/technical-designs/` among its parents.""" + parts = os.path.abspath(display).split(os.sep) + return any(a == "docs" and b == "technical-designs" + for a, b in zip(parts, parts[1:])) + + def main(argv): if len(argv) != 2: print("decision-coverage: usage: decision-coverage.py ", file=sys.stderr) return 2 display = argv[1] + if is_technical_design(display): + return 0 try: lines = read_lines(display) fields, _ = frontmatter(lines) diff --git a/tests/working-process/test_decision_coverage.py b/tests/working-process/test_decision_coverage.py index 8e61932..6e35eba 100644 --- a/tests/working-process/test_decision_coverage.py +++ b/tests/working-process/test_decision_coverage.py @@ -487,6 +487,42 @@ def test_12_repository_documents(self): [f"decision-coverage: {spec} register well formed"]) + # 13 + def test_13_technical_design_produces_no_output(self): + (self.root / "docs" / "specs").mkdir(parents=True) + (self.root / "docs" / "technical-designs").mkdir(parents=True) + self.write("docs/specs/s.md", SPEC_BASIC) + design = dedent("""\ + --- + spec: ../specs/s.md + --- + + # S — technical design + + Text. + """) + self.write("docs/technical-designs/s-technical-design.md", design) + proc = subprocess.run( + [sys.executable, SCRIPT, + str(self.root / "docs" / "technical-designs" / "s-technical-design.md")], + capture_output=True, + text=True, + ) + self.assertEqual(proc.returncode, 0, proc.stderr) + self.assertEqual(proc.stdout, "") + self.assertEqual(proc.stderr, "") + + # 14 + def test_14_plan_under_plans_is_still_counted(self): + self.write("specs/s.md", SPEC_BASIC) + self.write("plans/p.md", PLAN_BASIC) + result = self.run_script("plans/p.md") + self.assertEqual(result.hits, []) + match = re.match(r"^decision-coverage: \S+ (\d+)/(\d+) covered;", result.blocks[0]) + self.assertIsNotNone(match, result.blocks[0]) + self.assertEqual(match.group(1), match.group(2)) + + class UsageTest(unittest.TestCase): def test_usage_error(self): proc = subprocess.run([sys.executable, SCRIPT], capture_output=True, text=True) From 4bf5c3a973bd069ed5e75a9131e7ade3415b13d1 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 20:52:51 +0200 Subject: [PATCH 086/143] test(working-process): cover a plan under docs/plans in the technical-design skip --- tests/working-process/test_decision_coverage.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/working-process/test_decision_coverage.py b/tests/working-process/test_decision_coverage.py index 6e35eba..d019788 100644 --- a/tests/working-process/test_decision_coverage.py +++ b/tests/working-process/test_decision_coverage.py @@ -514,9 +514,11 @@ def test_13_technical_design_produces_no_output(self): # 14 def test_14_plan_under_plans_is_still_counted(self): - self.write("specs/s.md", SPEC_BASIC) - self.write("plans/p.md", PLAN_BASIC) - result = self.run_script("plans/p.md") + (self.root / "docs" / "specs").mkdir(parents=True) + (self.root / "docs" / "plans").mkdir(parents=True) + self.write("docs/specs/s.md", SPEC_BASIC) + self.write("docs/plans/p.md", PLAN_BASIC) + result = self.run_script("docs/plans/p.md") self.assertEqual(result.hits, []) match = re.match(r"^decision-coverage: \S+ (\d+)/(\d+) covered;", result.blocks[0]) self.assertIsNotNone(match, result.blocks[0]) From c486e67790d95e8742a0980be86219872e816470 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 20:55:47 +0200 Subject: [PATCH 087/143] docs(working-process): plan-coverage plan, Task 16 record --- docs/plans/2026-09-26-plan-coverage.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 956bba2..51a2f34 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2678,4 +2678,5 @@ evidence is the run's record in the dispatcher's store. - fixed 2026-09-26 — Task 13's harness: `--setting-sources project` dropped the agents `--plugin-dir` loads, the extractor failed on an event whose `message` is a string, `grep -c` exits 1 on a zero count, and the step overstated the run's permission limits; ruling: 2026-09-26; the run now denies `Bash(claude *)` instead, the extractor skips such events, the count takes `|| true`, and Step 2 states the real limits - fixed 2026-09-26 — Task 13 failed any denial, or on the draft wording passed any complete report; ruling: 2026-09-26; a denial fails the test unless the stream shows the same operation completed through an allowed alternative, since a complete report does not prove its check ran (Codex's second opinion) - fixed 2026-09-26 — Task 13's second run measured duty 10 unstable on the cheapest family (34/37 and 31/32 where 39 leaves stand), each miss following a denied compound command; ruling: 2026-09-26; the derivation becomes a script — spec D31, recorded in the spec under `### 2026-09-26 — fix from ../plans/2026-09-26-plan-coverage.md` — and Tasks 14–16 add it, point the card at it and re-run Task 13 twice; Task 13's run allows that one script, and the narration before `model:` is accepted as a known departure of the cheapest family, the workflow rule's reading of the body guarding it +- fixed 2026-09-26 — Task 16's first double run matched every number but printed a `decision-coverage:` line on the technical design and dropped the `model:` line from two reports; ruling: 2026-09-26; the script skips a document under `docs/technical-designs/` and the card runs it on plans and design specs only, with its output copied below the `model:` line (`d737769`, test `4bf5c3a`); the second double run met every expectation, numbers exact both times From b12ab97658ad0b4664c0668c6a472cc6ecbe5d7c Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 21:16:39 +0200 Subject: [PATCH 088/143] fix(working-process): coverage script encoding, register hits and Realizes placement --- .../scripts/decision-coverage.py | 28 ++- .../working-process/test_decision_coverage.py | 178 +++++++++++++++++- 2 files changed, 190 insertions(+), 16 deletions(-) diff --git a/plugins/working-process/scripts/decision-coverage.py b/plugins/working-process/scripts/decision-coverage.py index c9533c9..3e8872f 100755 --- a/plugins/working-process/scripts/decision-coverage.py +++ b/plugins/working-process/scripts/decision-coverage.py @@ -25,6 +25,8 @@ ENTRY_RE = re.compile(r"^(\s*)- \*\*(D\d+(?:\.\d+)?)\*\*") NUMBERED_RE = re.compile(r"^\s*\d+\.\s+\*\*D") LIST_ITEM_RE = re.compile(r"^\s*(?:[-*+]|\d+\.)\s") +ATTEMPTED_ID_RE = re.compile(r"^\s*(?:[-*+]|\d+\.)\s+\*\*D\d") +STEP_RE = re.compile(r"^\s*-\s\[.\]") TOKEN_START_RE = re.compile(r"\.\s+(withdrawn\b.*)$") TOKEN_RE = re.compile( r"^withdrawn (.+), ruling: (\d{4}-\d{2}-\d{2})" @@ -165,6 +167,10 @@ def bad(line, claim): continue match = ENTRY_RE.match(text) if not match: + if LIST_ITEM_RE.match(text): + indent = len(text) - len(text.lstrip()) + if indent == 0 or ATTEMPTED_ID_RE.match(text): + bad(number, "list item does not parse as a register entry") continue indent, ident = len(match.group(1)), match.group(2) child = indent >= 2 @@ -261,7 +267,7 @@ def __init__(self, display, lines): self.specs = spec_paths(fields["spec"][0]) if "spec" in fields else [] self.status = fields.get("status", ("", 0))[0] self.citations = [] # Citation, in document order - self.tasks = [] # (number, heading line, has a Realizes line) + self.tasks = [] # (number, heading line, has a Realizes line, past first step) self.defers = [] # (line, token) self.follows = [] # (line, written path) self._parse(unfenced(lines, body_start)) @@ -273,17 +279,21 @@ def _parse(self, body): task = None match = TASK_RE.match(text) if match: - task = [match.group(1), number, False] + task = [match.group(1), number, False, False] self.tasks.append(task) if text.startswith("## "): section = text[3:].strip() continue + if task is not None and STEP_RE.match(text): + task[3] = True + continue if task is not None and text.startswith("**Realizes:** "): - task[2] = True - value = text[len("**Realizes:** "):].strip() - if value != "none": - for token in split_ids(value): - self.citations.append(Citation(token, number, f"Task {task[0]}")) + if not task[3]: + task[2] = True + value = text[len("**Realizes:** "):].strip() + if value != "none": + for token in split_ids(value): + self.citations.append(Citation(token, number, f"Task {task[0]}")) continue if section == "Global Constraints" and text.startswith("- **Realizes:** "): rest = text[len("- **Realizes:** "):] @@ -476,7 +486,7 @@ def plan_wide(self, registered): """Step 7's plan-wide hits: missing annotations and bare identifiers.""" plan = self.plan if registered: - for number, heading_line, has_line in plan.tasks: + for number, heading_line, has_line, _ in plan.tasks: if not has_line: self.add(hit(plan.display, heading_line, f"Task {number} carries no `**Realizes:**` line", "step 7")) @@ -521,6 +531,8 @@ def is_technical_design(display): def main(argv): + sys.stdout.reconfigure(encoding="utf-8") + sys.stderr.reconfigure(encoding="utf-8") if len(argv) != 2: print("decision-coverage: usage: decision-coverage.py ", file=sys.stderr) diff --git a/tests/working-process/test_decision_coverage.py b/tests/working-process/test_decision_coverage.py index d019788..6696e6a 100644 --- a/tests/working-process/test_decision_coverage.py +++ b/tests/working-process/test_decision_coverage.py @@ -307,6 +307,81 @@ def test_06f_numbered_list(self): 2. **D2** — Two. """, "1. **D1**") + def test_06g_unsupported_nesting_depth(self): + self.assert_malformed("""\ + - **D1** — One. + - **D2** — Two. + - **D2.1** — Child. + - **D2.1.1** — x. + """, "D2.1.1") + + def test_06h_bullet_without_identifier(self): + self.assert_malformed("""\ + - **D1** — One. + - note + """, "- note") + + def test_06i_decisions_field_not_registered(self): + spec = "---\ndecisions: draft\n---\n\n## Decisions\n\n- **D1** — One.\n" + self.write("specs/s.md", spec) + self.write("plans/p.md", dedent("""\ + --- + spec: ../specs/s.md + --- + + ### Task 1: one + + **Realizes:** D1 + """)) + result = self.run_script("plans/p.md") + self.assertEqual(len(result.hits), 1, result.lines) + self.assert_hit_at(result, "s.md", line_of(spec, "decisions: draft"), "not `registered`") + self.assertEqual(result.blocks, + ["decision-coverage: ../specs/s.md not counted — malformed register"]) + + def test_06j_child_nested_under_wrong_parent(self): + self.assert_malformed("""\ + - **D1** — One. + - **D1.1** — Child of D1. + - **D2** — Two. + - **D1.2** — Wrongly placed. + """, "Wrongly placed") + + def test_06k_withdrawn_segment_malformed(self): + self.assert_malformed("""\ + - **D1** — One. + - **D2** — Two. withdrawn reason without a ruling date. + """, "withdrawn reason") + + def test_06l_replaced_by_missing_identifier(self): + self.assert_malformed("""\ + - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D9 + """, "One.") + + def test_06m_replaced_by_group(self): + self.assert_malformed("""\ + - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D2 + - **D2** — Group. + - **D2.1** — Child. + """, "One.") + + def test_06n_legal_nested_list_in_free_prose_is_not_a_hit(self): + spec = "---\ndecisions: registered\n---\n\n## Decisions\n\n" + dedent("""\ + - **D1** — One. + + Some prose about D1. + + - a legal nested note + - another legal note + + - **D2** — Two. + """) + self.write("specs/s.md", spec) + result = self.run_script("specs/s.md") + self.assertEqual(len(result.lines), 1, result.lines) + self.assertTrue(result.lines[0].startswith("decision-coverage: ")) + self.assertTrue(result.lines[0].endswith("s.md register well formed"), result.lines) + # 7 def test_07_legacy_spec(self): self.write("specs/s.md", "---\nstatus: draft\n---\n\n# S\n\n## Decisions\n\n- **D1** — x.\n") @@ -436,6 +511,25 @@ def test_10c_inherited_citation_withdrawn_since(self): " withdrawn since: D2 ← ../plans/p0.md (Task 1)", ]) + def test_10d_follows_missing_file(self): + self.write("specs/s.md", "---\ndecisions: registered\n---\n\n## Decisions\n\n- **D1** — One.\n") + plan = dedent("""\ + --- + spec: ../specs/s.md + --- + + ## Deferrals and predecessors + + **Follows:** ../plans/missing.md + + ### Task 1: one + + **Realizes:** D1 + """) + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assert_hit_at(result, "p.md", line_of(plan, "**Follows:**"), "missing.md") + # 11 def test_11_spec_audited_alone(self): self.write("specs/s.md", SPEC_BASIC) @@ -454,9 +548,12 @@ def test_11_spec_audited_alone(self): def test_12_repository_documents(self): spec = REPO / "docs" / "specs" / "2026-09-25-plan-coverage-design.md" plan = REPO / "docs" / "plans" / "2026-09-26-plan-coverage.md" - ids = [] - in_register = False - in_fence = False + + # Count the register's leaves independently of the script, + # excluding an entry whose identity paragraph carries a segment + # beginning `withdrawn` after a full stop — the same rule the + # contract gives the counted set, derived from scratch here. + in_register, in_fence, entries, current = False, False, [], None for line in spec.read_text(encoding="utf-8").splitlines(): if line.startswith("```"): in_fence = not in_fence @@ -465,12 +562,28 @@ def test_12_repository_documents(self): continue if line.startswith("## "): in_register = line.strip() == "## Decisions" + current = None + continue + if not in_register: + continue + match = re.match(r"^(\s*)- \*\*(D\d+(?:\.\d+)?)\*\*(.*)$", line) + if match: + current = [match.group(2), match.group(3).strip()] + entries.append(current) continue - match = re.match(r"^\s*- \*\*(D\d+(?:\.\d+)?)\*\*", line) - if in_register and match: - ids.append(match.group(1)) + if current is None: + continue + if (not line.strip() or re.match(r"^\s*(?:[-*+]|\d+\.)\s", line)): + current = None + continue + current[1] += " " + line.strip() + + ids = [ident for ident, _ in entries] + withdrawn = {ident for ident, paragraph in entries + if re.search(r"\.\s+withdrawn\b", paragraph)} leaves = [i for i in ids if not any(j.startswith(i + ".") for j in ids)] - self.assertGreater(len(leaves), 0) + counted = [i for i in leaves if i not in withdrawn] + self.assertGreater(len(counted), 0) proc = subprocess.run([sys.executable, SCRIPT, str(plan)], capture_output=True, text=True) self.assertEqual(proc.returncode, 0, proc.stderr) @@ -479,7 +592,8 @@ def test_12_repository_documents(self): self.assertEqual(len(result.blocks), 1, result.lines) match = re.match(r"^decision-coverage: \S+ (\d+)/(\d+) covered;", result.blocks[0]) self.assertIsNotNone(match, result.blocks[0]) - self.assertEqual((int(match.group(1)), int(match.group(2))), (len(leaves), len(leaves))) + self.assertEqual((int(match.group(1)), int(match.group(2))), + (len(counted), len(counted))) proc = subprocess.run([sys.executable, SCRIPT, str(spec)], capture_output=True, text=True) self.assertEqual(proc.returncode, 0, proc.stderr) @@ -524,6 +638,54 @@ def test_14_plan_under_plans_is_still_counted(self): self.assertIsNotNone(match, result.blocks[0]) self.assertEqual(match.group(1), match.group(2)) + # 15 + def test_15_stdout_encoding_survives_non_utf8_env(self): + self.write("specs/s.md", SPEC_BASIC) + self.write("plans/p.md", PLAN_BASIC) + env = dict(os.environ) + env["PYTHONIOENCODING"] = "ascii" + proc = subprocess.run( + [sys.executable, SCRIPT, str(self.root / "plans" / "p.md")], + capture_output=True, + text=True, + encoding="utf-8", + env=env, + ) + self.assertEqual(proc.returncode, 0, proc.stderr) + self.assertEqual(proc.stderr, "") + result = Result(proc) + self.assertEqual(result.hits, []) + self.assertIn( + "decision-coverage: ../specs/s.md 4/4 covered; inherited []; deferred []; uncovered []", + result.lines) + self.assertIn(" D1 → Task 1", result.lines) + + # 16 + def test_16_realizes_after_first_step_not_counted(self): + self.write("specs/s.md", + "---\ndecisions: registered\n---\n\n## Decisions\n\n- **D1** — One.\n") + plan = dedent("""\ + --- + spec: ../specs/s.md + --- + + ### Task 1: one + + **Files:** a + + - [ ] **Step 1: do** + + **Realizes:** D1 + """) + self.write("plans/p.md", plan) + result = self.run_script("plans/p.md") + self.assert_hit_at(result, "p.md", line_of(plan, "### Task 1"), + "carries no `**Realizes:**` line") + self.assertIn( + "decision-coverage: ../specs/s.md 0/1 covered; inherited []; deferred []; uncovered [D1]", + result.lines) + self.assertIn(" D1 → —", result.lines) + class UsageTest(unittest.TestCase): def test_usage_error(self): From 57d66f8cd1d39c7ee29e913d72ab86b6a6475cfd Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 21:16:42 +0200 Subject: [PATCH 089/143] docs(working-process): README requirements and auditor hit ordering --- plugins/working-process/README.md | 6 +++++- plugins/working-process/agents/propagation-auditor.md | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/working-process/README.md b/plugins/working-process/README.md index f4260f5..c4083a7 100644 --- a/plugins/working-process/README.md +++ b/plugins/working-process/README.md @@ -119,6 +119,9 @@ exist, so it speaks the project's language from its first message. /plugin marketplace add obra/superpowers-marketplace /plugin install elements-of-style@superpowers-marketplace +- `python3` 3.9 or later, standard library only, for the propagation + auditor's coverage duty. + ## Extending with a domain checklist Ship a skill named `-plan-review` in your domain plugin. Its @@ -167,7 +170,8 @@ that the register lists every decision the spec makes. A spec without the field is reported as not checked. The grammar lives in the spec-plan-lifecycle rule. The auditor derives the decision coverage by running `scripts/decision-coverage.py` with `python3`, so a session -that asks before running a command asks once for it. +that asks before running a command asks once for it; the permission +entry for the plugin cache below covers it. ## Model selection diff --git a/plugins/working-process/agents/propagation-auditor.md b/plugins/working-process/agents/propagation-auditor.md index bb8504a..1804549 100644 --- a/plugins/working-process/agents/propagation-auditor.md +++ b/plugins/working-process/agents/propagation-auditor.md @@ -195,7 +195,9 @@ which this duty does not cover — as a single command from the repository root. It performs the steps below and prints this duty's hits and `decision-coverage:` lines in the report's shapes; copy its output into your report unchanged, below your `model:` line, which -stays the report's first line, neither recounting nor reordering it. If +stays the report's first line, neither recounting nor reordering it; +where other duties hit too, their hits come first and the script's +output follows them as one unit. If it exits non-zero, or you cannot run it, report that as a hit naming the command and its message, and write no `decision-coverage:` line — never derive From 2f296f0563debe73dc8d5b8611e21b2b5df8bde7 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 21:16:45 +0200 Subject: [PATCH 090/143] chore: ignore __pycache__ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 40167c9..709f43c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .claude/settings.local.json .claude/worktrees/ +__pycache__/ From ed414df1a467df2d2fb8604fe5467fa67351238b Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Sat, 26 Sep 2026 21:22:08 +0200 Subject: [PATCH 091/143] docs(working-process): plan-coverage plan, final review record --- docs/plans/2026-09-26-plan-coverage.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index 51a2f34..f0ec572 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2679,4 +2679,5 @@ evidence is the run's record in the dispatcher's store. - fixed 2026-09-26 — Task 13 failed any denial, or on the draft wording passed any complete report; ruling: 2026-09-26; a denial fails the test unless the stream shows the same operation completed through an allowed alternative, since a complete report does not prove its check ran (Codex's second opinion) - fixed 2026-09-26 — Task 13's second run measured duty 10 unstable on the cheapest family (34/37 and 31/32 where 39 leaves stand), each miss following a denied compound command; ruling: 2026-09-26; the derivation becomes a script — spec D31, recorded in the spec under `### 2026-09-26 — fix from ../plans/2026-09-26-plan-coverage.md` — and Tasks 14–16 add it, point the card at it and re-run Task 13 twice; Task 13's run allows that one script, and the narration before `model:` is accepted as a known departure of the cheapest family, the workflow rule's reading of the body guarding it - fixed 2026-09-26 — Task 16's first double run matched every number but printed a `decision-coverage:` line on the technical design and dropped the `model:` line from two reports; ruling: 2026-09-26; the script skips a document under `docs/technical-designs/` and the card runs it on plans and design specs only, with its output copied below the `model:` line (`d737769`, test `4bf5c3a`); the second double run met every expectation, numbers exact both times +- fixed 2026-09-26 — the final whole-branch review (fable, "With fixes") found the script failing on a non-UTF-8 stdout, a register list item that is no entry vanishing without a hit — a plan defect, since Task 14's contract narrowed the spec's step 2 ("an identity paragraph that does not parse") — a `**Realizes:**` line counted after a task's first step, the README missing python3, an order the card left open, and a test counting leaves naively; ruling: 2026-09-26; one fix wave, scoped by a second opinion from Codex, landed as `b12ab97`, `57d66f8` and `2f296f0`, and its one scoped re-review found every item addressed; the script's remaining minors (withdrawn-since lines per citation, a malformed `**Defers:**` read as an identifier, one long method) wait for a later change From 5b57f5b9028cabae5a4d32c4b9740e5e1f052073 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:03:35 +0200 Subject: [PATCH 092/143] docs(working-process): plan-coverage spec, fourth audit dispositions --- docs/specs/2026-09-25-plan-coverage-design.md | 109 ++++++++++++------ 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 0bc1e4f..8ec5773 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -63,7 +63,9 @@ What stands today: - **D5** — An entry carries one state token or none: `withdrawn , ruling: [; replaced by ]`. Only a leaf carries it, and the session writes it only on the developer's - explicit decision. Argued in *Entry states*. + explicit decision; on a spec whose loop has closed, a withdrawal tied + to no hit or finding is recorded by its tombstone alone. Argued in + *Entry states*. - **D6** — The spec author creates the register, with or without a grilling session; the grilling session updates it as decisions land. Argued in *Who writes the register*. @@ -155,8 +157,10 @@ What stands today: formed, not counted, or not checked. Argued in *The coverage duty* and *The report*. - **D23** — A pre-round gate episode that holds a hit writes all its - lines before the first round heading, where they stay. Argued in - *Gate lines for a held hit*. + lines before the first round heading, where they stay; every later + pre-round episode writes there too, and a document with no + `## Review rounds` section gains one at its end with its first gate + line. Argued in *Gate lines for a held hit*. - **D24** — Withdrawing, repairing, sharpening or adding a decision of an `implemented` spec is a revision through a newer spec's `revises:`, never an edit of the frozen body. Argued in *Entry @@ -191,8 +195,9 @@ What stands today: - **D31** — The coverage duty's derivation is a script the plugin ships, `scripts/decision-coverage.py`, run by the auditor, whose output is the duty's hits and `decision-coverage:` lines; the auditor reports - that output and never derives the sets itself. Argued in *The - coverage duty*. + that output and never derives the sets itself. The script is tested + from the repository's `tests/working-process/`, outside the plugin. + Argued in *The coverage duty*. ## The register @@ -220,7 +225,9 @@ A state token is recognized by its reserved opening word alone: the first segment after the full stop that ends the decision's statement — and after any "Argued in" pointer, which belongs to the statement — that begins with `withdrawn`. The token runs from that word to the -paragraph's end, so its `` may hold a full stop. A segment +paragraph's end, so its `` may hold a full stop. A full stop +here is one followed by whitespace, so the dot in `decision-coverage.py` +or `../plans/.md` ends no segment. A segment so opening that does not match the token's grammar is a hit, never prose. Any other text is prose, so a statement whose own words happen to end in "withdrawn" @@ -233,7 +240,11 @@ identity paragraph. A Markdown numbered list is refused on purpose: its numbers are positions, and a positional reference breaks when an item is inserted above it. A child nested under a parent other than its own, a child whose parent does not exist, and a register written as a -numbered list are each malformed (see *The coverage duty*). +numbered list are each malformed (see *The coverage duty*). So is a +list item at the entries' own level that opens no identifier, and a +list item at any depth that opens like one — `**D` and a digit — but +does not match the token, such as `**D2.1.1**`; a list inside an entry's +prose, below its blank line, is prose. An entry states the decision briefly and, where the argument is long, names the section that makes it. The grammar enforces the paragraph's @@ -282,9 +293,9 @@ records it (see *Deferral*). A tombstone keeps the identifier visibly taken, so a reuse shows up as a duplicate, and a plan still citing it gets a hit that says why. Two -constraints bind `replaced by`: it names an identifier that exists in -the same register and differs from its own, and a chain of successors -never forms a cycle. A successor may itself be withdrawn. +constraints bind `replaced by`: it names a leaf that exists in the +same register and differs from its own, and a chain of successors never +forms a cycle. A successor may itself be withdrawn. A withdrawal is not a disposition of a hit: it changes what the spec decides, so the session writes the token only on the developer's @@ -462,7 +473,8 @@ withdrawn — the spec, still open to edits, took the `withdrawn` token after the predecessor shipped (D30). The frozen predecessor cannot change, so that inherited citation is skipped: it is not counted, it covers nothing, and it raises no hit against the later plan. It is not -hidden either — the report lists it after the map, outside the counted +hidden either — the report lists it after the map, indented as the +map's lines are and one line per skipped citation, outside the counted identifiers and the fraction, as `withdrawn since: D4 ← ../plans/.md (Task 2)`. The exception covers inheritance alone: a `**Realizes:**` in the later plan that cites the withdrawn identifier @@ -525,17 +537,19 @@ A tenth propagation duty runs on a plan. For each spec the plan's 6. Collect the counted set: the leaf identifiers that are not withdrawn, less those the `**Defers:**` lines step 5 accepted name. 7. Report every counted identifier the cited set lacks: one hit per - identifier. + identifier. A task — a `### Task ` heading — whose `**Realizes:**` + line is missing before its first step, in a plan the convention + binds, is a hit too, raised once for the plan rather than once per + spec. A line that is a hit gains the plan nothing: an invalid predecessor covers no decision, and an invalid deferral excuses none, while the hit itself stays in the report. -The decision coverage map is derived, never tallied. The auditor writes +The decision coverage map is derived, never tallied. The script writes the map from identifier to citing sites, and the fraction is that map's -summary; a bare count would pass one omission offset by one duplicate. A -task carrying no `**Realizes:**` line in a plan the convention binds is -a hit. An identifier cited by several tasks is legal — one decision, +summary; a bare count would pass one omission offset by one duplicate. +An identifier cited by several tasks is legal — one decision, many tasks — and a task split or merged in a fix wave passes as long as the union of its identifiers survives. @@ -547,18 +561,24 @@ report* gives the line it writes. The derivation runs as a script the plugin ships (D31). Measured on 2026-09-26, the cheapest family walking these steps from prose read the same plan three times as 39/39, 34/37 and — on a copy missing one -annotation — 31/32: it missed the annotations opening Global -Constraints entries and miscounted the register's leaves wherever the -harness denied the compound commands it reached for. Bounding the -section, telling groups from leaves and taking set differences are -exactly the operations it got wrong, and a parse settles them. The -script reads the document it is given — a plan, or a design spec audited -alone — with the specs and predecessors it names, performs the steps -above, and prints the duty's hits and `decision-coverage:` lines in the -report's shapes; it reports a document it cannot parse rather than -guessing. The auditor runs it once per audited document, copies its -output into the report, and never derives the sets itself. The steps -stay the duty's definition; the script is their one implementation. +annotation — 31/32: it missed the annotations opening Global Constraints +entries and miscounted the register's leaves wherever the harness denied +the compound commands it reached for. Bounding the section, telling +groups from leaves and taking set differences are exactly the operations +it got wrong, and a parse settles them. The script reads the document it +is given — a plan, or a design spec audited alone — with the specs and +predecessors it names, performs the steps above, and prints the duty's +hits and `decision-coverage:` lines in the report's shapes. The auditor +runs it from the plugin's root with `python3` — Python 3.9 or later, the +standard library only — passing the audited document's path as its one +argument, once per audited plan or design spec and never on a technical +design, copies its output into the report below the `model:` line, and +never derives the sets itself. A document the script cannot read ends it +with a non-zero exit and a message; the auditor reports that as a hit +and writes no `decision-coverage:` line for the document. The spec path +on a summary line is written as the plan's `spec:` writes it, and on a +design spec audited alone as the auditor passed it. The steps stay the +duty's definition; the script is their one implementation. The duty proves that every *declared* decision has an owner. Whether the register faithfully lists the spec's decisions is judgment, and @@ -729,7 +749,7 @@ dispatcher classifies its fix by license: brief names it among the previous wave's fixes, which the round attacks first. - **(c)** Writing the task needs a choice the decision does not make. - The question is that missing decision — not the coverage gap, which + The question is that missing decision — not the missing owner, which is already established. The hit is held for the developer. A held hit blocks the dispatch its gate guards, as a held finding @@ -924,14 +944,14 @@ design spec and its absence is reported, not refused. All under `plugins/working-process/` unless noted. -- `agents/propagation-auditor.md` — duties 10 (coverage) and 11 (table - closure, naming the rule heading that holds the declarations); the - `decision-coverage:` block, its `inherited [..]` slot, its +- `agents/propagation-auditor.md` — duties 10 (decision coverage) and 11 + (table closure, naming the rule heading that holds the declarations); + the `decision-coverage:` block, its `inherited [..]` slot, its `withdrawn since` listing and the three outcomes on a spec audited alone (`register well formed`, `not counted`, `not checked`); the `table-closure:` line; the "exactly two lines" sentence; running - `scripts/decision-coverage.py` for duty 10; the - coverage exception in *What becomes of your hits*. + `scripts/decision-coverage.py` for duty 10; the coverage exception in + *What becomes of your hits*. - `agents/plan-adversary.md` — the realization-site dimension. - `agents/integrity-auditor.md` — one sentence naming the register as a declared rule for lens 1. @@ -1097,3 +1117,24 @@ that the derivation becomes a script in this change. - fixed 2026-09-26 — the coverage duty's derivation, walked from prose on the cheapest family, read one plan as 39/39, 34/37 and 31/32 across runs; ruling: 2026-09-26; D31 added, *The coverage duty* gives the script and the measurement, *Changes by file* lists the script and its tests — the plan's line under its Task 13 record points here +### 2026-09-28 — integrity audit, fable, after D31 (fourth) + +Coverage tell: 1099 lines read, highest line cited 1098 — a +whole-document read after D31 left the third audit's stamp stale. Seven +defects and ten implementer questions; the quotes were checked against +the spec before disposition. Most dispositions state in the spec what +the implementation already does under rulings the plan records. + +- fixed 2026-09-28 — the map-writing sentence still named the auditor as the one who writes the map, against D31; license: D31; the script writes it +- fixed 2026-09-28 — the missing-`**Realizes:**` hit stood outside the numbered steps the script implements; license: D31 ("The steps stay the duty's definition"); step 7 now carries it, with what counts as a task, its place before the first step, and once per plan (implementer questions 4 and 8) +- fixed 2026-09-28 — *Entry states* gave two constraints on `replaced by` while step 2 also rejects a group; license: step 2; the successor is a leaf +- fixed 2026-09-28 — the closed-loop withdrawal record, D23's later episodes and the section a document gains, and the script's tests had no register entry; license: the register's declared rule; D5, D23 and D31 sharpened under their own identifiers +- fixed 2026-09-28 — bare "coverage" named the missing owner and duty 10 in *Changes by file*; license: glossary **Decision coverage** `_Avoid_`; reworded +- fixed 2026-09-28 — implementer questions 1, 2, 3, 5, 6 and 7; license: D31 and the plan's rulings of 2026-09-26 that the implementation carries (Tasks 14–16 and the final-review record); the invocation, interpreter and argument, the failure path, what a full stop is, where `withdrawn since` lines stand, which list items in a register are malformed, and how a summary line writes its spec path are now stated + +Implementer questions 9 and 10 need no change: the workflow rule's +propagation gate defines the gate episode and its re-dispatch bound, and +*Disposing of a coverage hit* already gives a fresh episode its own +bound; the lifecycle rule's frontmatter block places `decisions:` and +names `registered` as its one value. + From d4925c1b78e7117f0aee4caa921f83104de601e8 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:07:22 +0200 Subject: [PATCH 093/143] docs(working-process): plan-coverage spec, gate fix --- docs/specs/2026-09-25-plan-coverage-design.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 8ec5773..4f785c7 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -569,16 +569,17 @@ it got wrong, and a parse settles them. The script reads the document it is given — a plan, or a design spec audited alone — with the specs and predecessors it names, performs the steps above, and prints the duty's hits and `decision-coverage:` lines in the report's shapes. The auditor -runs it from the plugin's root with `python3` — Python 3.9 or later, the -standard library only — passing the audited document's path as its one -argument, once per audited plan or design spec and never on a technical -design, copies its output into the report below the `model:` line, and -never derives the sets itself. A document the script cannot read ends it -with a non-zero exit and a message; the auditor reports that as a hit -and writes no `decision-coverage:` line for the document. The spec path -on a summary line is written as the plan's `spec:` writes it, and on a -design spec audited alone as the auditor passed it. The steps stay the -duty's definition; the script is their one implementation. +runs the plugin's copy with `python3` from the repository root — Python +3.9 or later, the standard library only — passing the audited document's +path as its one argument, once per audited plan or design spec and never +on a technical design, copies its output into the report below the +`model:` line, and never derives the sets itself. A document the script +cannot read ends it with a non-zero exit and a message; the auditor +reports that as a hit and writes no `decision-coverage:` line for the +document. The spec path on a summary line is written as the plan's +`spec:` writes it, and on a design spec audited alone as the auditor +passed it. The steps stay the duty's definition; the script is their one +implementation. The duty proves that every *declared* decision has an owner. Whether the register faithfully lists the spec's decisions is judgment, and @@ -1137,4 +1138,4 @@ propagation gate defines the gate episode and its re-dispatch bound, and *Disposing of a coverage hit* already gives a fresh episode its own bound; the lifecycle rule's frontmatter block places `decisions:` and names `registered` as its one value. - +- hit fixed 2026-09-28 — the D31 paragraph said the script runs "from the plugin's root" while the card runs it from the repository root; it now runs the plugin's copy from the repository root From 77918815eec27b4cbc56c866f8583313dce49a19 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:11:51 +0200 Subject: [PATCH 094/143] docs(working-process): restamp plan-coverage spec integrity --- docs/specs/2026-09-25-plan-coverage-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/2026-09-25-plan-coverage-design.md b/docs/specs/2026-09-25-plan-coverage-design.md index 4f785c7..79655bc 100644 --- a/docs/specs/2026-09-25-plan-coverage-design.md +++ b/docs/specs/2026-09-25-plan-coverage-design.md @@ -4,7 +4,7 @@ date: 2026-09-25 status: draft grilled: 2026-09-25 architect: concerns (resolved 2026-09-26) -integrity: 2026-09-26 (sha: 4263a22) +integrity: 2026-09-28 (sha: 1b036ec) decisions: registered branch: feature/plan-coverage base: develop From 96fb5e84e98cebe1cd23063bb624798659431459 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:38:54 +0200 Subject: [PATCH 095/143] fix(working-process): annotate, docstring and pathlib decision-coverage.py; exit 1 on unreadable referenced doc --- .../scripts/decision-coverage.py | 127 ++++++++++++------ 1 file changed, 88 insertions(+), 39 deletions(-) diff --git a/plugins/working-process/scripts/decision-coverage.py b/plugins/working-process/scripts/decision-coverage.py index 3e8872f..35e4d4b 100755 --- a/plugins/working-process/scripts/decision-coverage.py +++ b/plugins/working-process/scripts/decision-coverage.py @@ -10,15 +10,17 @@ Output goes to stdout: every hit, one line each, then one `decision-coverage:` block per spec. Exit 0 whenever the derivation ran, -hits or not; 2 on a usage error or an unreadable file. A technical -design — a document under a `technical-designs` directory directly -under a `docs` directory — produces no output; this duty does not cover -it. +hits or not; 2 on a usage error; 1 when the argument or a document it +names cannot be read. A technical design — a document under a +`technical-designs` directory directly under a `docs` directory — +produces no output; this duty does not cover it. """ -import os +from __future__ import annotations + import re import sys +from pathlib import Path TOOL = "decision-coverage.py" @@ -37,12 +39,13 @@ class Unreadable(Exception): - pass + """Raised when a document, or one it names, cannot be read.""" # --- reading ------------------------------------------------------------- -def read_lines(path): +def read_lines(path: str) -> list[str]: + """Read `path` and split it into lines, raising `Unreadable` on failure.""" try: with open(path, encoding="utf-8") as handle: return handle.read().splitlines() @@ -50,7 +53,7 @@ def read_lines(path): raise Unreadable(f"cannot read {path}: {error.strerror or error}") -def frontmatter(lines): +def frontmatter(lines: list[str]) -> tuple[dict[str, tuple[str, int]], int]: """Fields between the `---` on line 1 and the next `---`. Returns ({key: (value, line number)}, index of the first body line). @@ -67,7 +70,7 @@ def frontmatter(lines): return fields, len(lines) -def spec_paths(value): +def spec_paths(value: str) -> list[str]: """`spec:` as a single path or an inline list `[a, b]`.""" value = value.strip() if value.startswith("[") and value.endswith("]"): @@ -75,7 +78,7 @@ def spec_paths(value): return [item.strip().strip("'\"") for item in value.split(",") if item.strip()] -def unfenced(lines, start): +def unfenced(lines: list[str], start: int) -> list[tuple[int, str]]: """(line number, text) pairs of the body, fenced code blocks removed.""" kept, in_fence = [], False for index in range(start, len(lines)): @@ -88,19 +91,45 @@ def unfenced(lines, start): return kept -def resolve(base_display, written): +def _collapse(path: Path) -> str: + """Lexically fold `..` segments, mirroring `os.path.normpath`.""" + root = path.root + kept: list[str] = [] + for part in path.parts: + if part == root and part: + continue + if part == "..": + if kept and kept[-1] != "..": + kept.pop() + elif not root: + kept.append(part) + else: + kept.append(part) + return (root + "/".join(kept)) if kept or root else "." + + +def _abspath(path: str) -> str: + """Make `path` absolute against the current directory, lexically.""" + given = Path(path) + return _collapse(given if given.is_absolute() else Path.cwd() / path) + + +def resolve(base_display: str, written: str) -> str: """A path a document names, resolved against that document's directory.""" - return os.path.normpath(os.path.join(os.path.dirname(base_display), written)) + return _collapse(Path(base_display).parent / written) -def same_file(a, b): - return os.path.abspath(a) == os.path.abspath(b) +def same_file(a: str, b: str) -> bool: + """Whether `a` and `b` name the same file once made absolute.""" + return _abspath(a) == _abspath(b) # --- the register -------------------------------------------------------- class Entry: - def __init__(self, ident, line, child): + """One register entry: an identifier, its line, and its nesting.""" + + def __init__(self, ident: str, line: int, child: bool) -> None: self.ident = ident self.line = line self.child = child @@ -110,27 +139,35 @@ def __init__(self, ident, line, child): self.replaced_by = None @property - def group(self): + def group(self) -> bool: + """Whether this entry is a group (has children).""" return bool(self.children) class Register: """Outcome of steps 1 and 2 for one spec: legacy, malformed or counted.""" - def __init__(self, state, entries=None, hits=None): + def __init__( + self, + state: str, + entries: dict[str, Entry] | None = None, + hits: list[str] | None = None, + ) -> None: self.state = state # "legacy" | "malformed" | "ok" self.entries = entries or {} self.hits = hits or [] - def leaves(self): + def leaves(self) -> list[Entry]: + """The register's leaf entries — those without children.""" return [e for e in self.entries.values() if not e.group] -def hit(path, line, claim, step): +def hit(path: str, line: int, claim: str, step: str) -> str: + """Format one derivation hit line.""" return f"{path}:{line} — {claim} — derivation: {TOOL}, {step}" -def read_register(display, lines): +def read_register(display: str, lines: list[str]) -> Register: fields, body_start = frontmatter(lines) if "decisions" not in fields: return Register("legacy") @@ -155,7 +192,7 @@ def read_register(display, lines): return parse_register(display, section) -def parse_register(display, section): +def parse_register(display: str, section: list[tuple[int, str]]) -> Register: hits, entries, parent = [], {}, None def bad(line, claim): @@ -253,14 +290,18 @@ def bad(line, claim): # --- the plan ------------------------------------------------------------ class Citation: - def __init__(self, token, line, site): + """One `**Realizes:**`/Global-Constraints citation of a decision.""" + + def __init__(self, token: str, line: int, site: str) -> None: self.token = token self.line = line self.site = site class Plan: - def __init__(self, display, lines): + """A parsed plan document: its specs, citations, tasks, defers and follows.""" + + def __init__(self, display: str, lines: list[str]) -> None: self.display = display fields, body_start = frontmatter(lines) self.fields = fields @@ -316,11 +357,12 @@ def _parse(self, body): self.follows.append((number, text[len("**Follows:** "):].strip())) -def split_ids(value): +def split_ids(value: str) -> list[str]: + """Split a comma-separated list of identifiers, dropping blanks.""" return [item.strip() for item in value.split(",") if item.strip()] -def scoped(plan, token, spec_abs): +def scoped(plan: Plan, token: str, spec_abs: str) -> str | None: """The identifier `token` names for the spec at spec_abs, or None. A plan naming one spec writes bare identifiers; one naming two or more @@ -339,22 +381,25 @@ def scoped(plan, token, spec_abs): # --- the derivation ------------------------------------------------------ class Audit: - def __init__(self, plan): + """Runs the coverage derivation for one plan, collecting hits and blocks.""" + + def __init__(self, plan: Plan) -> None: self.plan = plan self.hits = [] self.blocks = [] self._predecessors = None - def add(self, line): + def add(self, line: str) -> None: + """Record a hit line, once per distinct line.""" if line not in self.hits: self.hits.append(line) - def predecessors(self): + def predecessors(self) -> list[tuple[str, Plan, list[str]]]: """Step 3, once per plan: the accepted `**Follows:**` lines.""" if self._predecessors is not None: return self._predecessors accepted = [] - own = [os.path.abspath(resolve(self.plan.display, s)) for s in self.plan.specs] + own = [_abspath(resolve(self.plan.display, s)) for s in self.plan.specs] for line, written in self.plan.follows: display = resolve(self.plan.display, written) try: @@ -363,7 +408,7 @@ def predecessors(self): self.add(hit(self.plan.display, line, f"`**Follows:**` names {written}, which does not exist", "step 3")) continue - theirs = [os.path.abspath(resolve(display, s)) for s in pred.specs] + theirs = [_abspath(resolve(display, s)) for s in pred.specs] shared = [s for s in own if s in theirs] if not shared: self.add(hit(self.plan.display, line, @@ -379,10 +424,11 @@ def predecessors(self): self._predecessors = accepted return accepted - def check_spec(self, written): + def check_spec(self, written: str) -> None: + """Run steps 3 through 7 for one spec the plan names.""" plan = self.plan spec_display = resolve(plan.display, written) - spec_abs = os.path.abspath(spec_display) + spec_abs = _abspath(spec_display) register = read_register(spec_display, read_lines(spec_display)) for line in register.hits: self.add(line) @@ -482,7 +528,7 @@ def slot(idents): for ident, site in withdrawn_since: self.blocks.append(f" withdrawn since: {show(ident)} ← {site}") - def plan_wide(self, registered): + def plan_wide(self, registered: bool) -> None: """Step 7's plan-wide hits: missing annotations and bare identifiers.""" plan = self.plan if registered: @@ -499,7 +545,8 @@ def plan_wide(self, registered): f"{len(plan.specs)} specs", "step 7")) -def audit_plan(display, lines): +def audit_plan(display: str, lines: list[str]) -> list[str]: + """Run the derivation for a plan document, returning its output lines.""" plan = Plan(display, lines) audit = Audit(plan) registered = False @@ -513,7 +560,8 @@ def audit_plan(display, lines): return audit.hits + audit.blocks -def audit_spec(display, lines): +def audit_spec(display: str, lines: list[str]) -> list[str]: + """Run the derivation for a spec audited alone, returning its output lines.""" register = read_register(display, lines) outcome = { "legacy": "not checked — no decision register", @@ -523,14 +571,15 @@ def audit_spec(display, lines): return register.hits + [f"decision-coverage: {display} {outcome}"] -def is_technical_design(display): +def is_technical_design(display: str) -> bool: """The resolved path has `docs/technical-designs/` among its parents.""" - parts = os.path.abspath(display).split(os.sep) + parts = Path(_abspath(display)).parts return any(a == "docs" and b == "technical-designs" for a, b in zip(parts, parts[1:])) -def main(argv): +def main(argv: list[str]) -> int: + """Parse argv, dispatch on document type, and print the derivation's output.""" sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") if len(argv) != 2: @@ -546,7 +595,7 @@ def main(argv): output = audit_plan(display, lines) if "spec" in fields else audit_spec(display, lines) except Unreadable as error: print(f"decision-coverage: {error}", file=sys.stderr) - return 2 + return 1 for line in output: print(line) return 0 From a2dbf18012b38681169f0833d38d439e250ce0d0 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:39:06 +0200 Subject: [PATCH 096/143] test(working-process): annotate, docstring, pathlib and parametrize decision-coverage tests --- .../working-process/test_decision_coverage.py | 158 ++++++++++-------- 1 file changed, 89 insertions(+), 69 deletions(-) diff --git a/tests/working-process/test_decision_coverage.py b/tests/working-process/test_decision_coverage.py index 6696e6a..611fb71 100644 --- a/tests/working-process/test_decision_coverage.py +++ b/tests/working-process/test_decision_coverage.py @@ -4,6 +4,8 @@ on one of them and asserts the exact lines the contract names. """ +from __future__ import annotations + import os import re import subprocess @@ -73,11 +75,12 @@ """ -def dedent(text): +def dedent(text: str) -> str: + """Shorthand for `textwrap.dedent`.""" return textwrap.dedent(text) -def line_of(text, needle): +def line_of(text: str, needle: str) -> int: """1-based number of the first line containing needle.""" for number, line in enumerate(text.splitlines(), 1): if needle in line: @@ -86,7 +89,7 @@ def line_of(text, needle): class Result: - def __init__(self, proc): + def __init__(self, proc: subprocess.CompletedProcess[str]) -> None: self.proc = proc self.lines = proc.stdout.splitlines() self.hits = [line for line in self.lines if DERIVATION in line] @@ -95,21 +98,23 @@ def __init__(self, proc): class CoverageTest(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self._tmp = tempfile.TemporaryDirectory() self.root = Path(self._tmp.name) (self.root / "specs").mkdir() (self.root / "plans").mkdir() - def tearDown(self): + def tearDown(self) -> None: self._tmp.cleanup() - def write(self, relative, text): + def write(self, relative: str, text: str) -> Path: + """Write `text` to `relative` under the temp root, returning its path.""" path = self.root / relative path.write_text(text, encoding="utf-8") return path - def run_script(self, relative): + def run_script(self, relative: str) -> Result: + """Run the script on `relative` under the temp root, asserting it exits 0.""" proc = subprocess.run( [sys.executable, SCRIPT, str(self.root / relative)], capture_output=True, @@ -119,9 +124,10 @@ def run_script(self, relative): self.assertEqual(proc.stderr, "") return Result(proc) - def assert_hit_at(self, result, filename, line, needle=None): + def assert_hit_at(self, result: Result, filename: str, line: int, needle: str | None = None) -> None: + """Assert a hit exists at `filename:line`, optionally containing `needle`.""" prefix = f"{filename}:{line} — " - found = [h for h in result.hits if os.path.basename(h.split(" — ")[0].rsplit(":", 1)[0]) == filename + found = [h for h in result.hits if Path(h.split(" — ")[0].rsplit(":", 1)[0]).name == filename and h.split(" — ")[0].endswith(f":{line}")] self.assertTrue(found, f"no hit at {prefix} in {result.lines}") if needle is not None: @@ -249,7 +255,8 @@ def test_05_withdrawn_entry(self): self.assertEqual(result.map, [" D1 → Task 1"]) # 6 - def assert_malformed(self, register, bad_line_needle): + def assert_malformed(self, register: str, bad_line_needle: str) -> None: + """Assert a `register` body is malformed at `bad_line_needle` and counts nothing.""" spec = "---\ndecisions: registered\n---\n\n## Decisions\n\n" + dedent(register) self.write("specs/s.md", spec) self.write("plans/p.md", dedent("""\ @@ -268,58 +275,75 @@ def assert_malformed(self, register, bad_line_needle): ["decision-coverage: ../specs/s.md not counted — malformed register"]) self.assertEqual(result.map, []) - def test_06a_duplicate_identifier(self): - self.assert_malformed("""\ + # Cases 06a-06h and 06j-06m share assert_malformed's exact shape (one + # hit, the malformed block, an empty map) and differ only in the + # register body and the expected bad-line needle, so they fold into + # one table-driven test. 06i differs — it is malformed through the + # `decisions:` field rather than the register body, and does not + # check `result.map` — so it stays its own test, as does 06n, which + # exercises a different (non-malformed) behavior. + MALFORMED_REGISTER_CASES = [ + ("test_06a_duplicate_identifier", """\ - **D1** — One. - **D2** — Two. - **D1** — One again. - """, "One again") - - def test_06b_child_without_parent(self): - self.assert_malformed("""\ + """, "One again"), + ("test_06b_child_without_parent", """\ - **D1** — One. - **D4.1** — Orphan. - """, "Orphan") - - def test_06c_group_with_token(self): - self.assert_malformed("""\ + """, "Orphan"), + ("test_06c_group_with_token", """\ - **D1** — One. - **D3** — Group. withdrawn old, ruling: 2026-01-01 - **D3.1** — Child. - """, "Group.") - - def test_06d_replaced_by_itself(self): - self.assert_malformed("""\ + """, "Group."), + ("test_06d_replaced_by_itself", """\ - **D1** — One. - **D2** — Two. withdrawn old, ruling: 2026-01-01; replaced by D2 - """, "Two.") - - def test_06e_replacement_cycle(self): - self.assert_malformed("""\ + """, "Two."), + ("test_06e_replacement_cycle", """\ - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D2 - **D2** — Two. withdrawn old, ruling: 2026-01-01; replaced by D1 - **D3** — Three. - """, "One.") - - def test_06f_numbered_list(self): - self.assert_malformed("""\ + """, "One."), + ("test_06f_numbered_list", """\ 1. **D1** — One. 2. **D2** — Two. - """, "1. **D1**") - - def test_06g_unsupported_nesting_depth(self): - self.assert_malformed("""\ + """, "1. **D1**"), + ("test_06g_unsupported_nesting_depth", """\ - **D1** — One. - **D2** — Two. - **D2.1** — Child. - **D2.1.1** — x. - """, "D2.1.1") - - def test_06h_bullet_without_identifier(self): - self.assert_malformed("""\ + """, "D2.1.1"), + ("test_06h_bullet_without_identifier", """\ - **D1** — One. - note - """, "- note") + """, "- note"), + ("test_06j_child_nested_under_wrong_parent", """\ + - **D1** — One. + - **D1.1** — Child of D1. + - **D2** — Two. + - **D1.2** — Wrongly placed. + """, "Wrongly placed"), + ("test_06k_withdrawn_segment_malformed", """\ + - **D1** — One. + - **D2** — Two. withdrawn reason without a ruling date. + """, "withdrawn reason"), + ("test_06l_replaced_by_missing_identifier", """\ + - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D9 + """, "One."), + ("test_06m_replaced_by_group", """\ + - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D2 + - **D2** — Group. + - **D2.1** — Child. + """, "One."), + ] + + def test_06_malformed_register_shared_shape(self): + for name, register, needle in self.MALFORMED_REGISTER_CASES: + with self.subTest(name): + self.assert_malformed(register, needle) def test_06i_decisions_field_not_registered(self): spec = "---\ndecisions: draft\n---\n\n## Decisions\n\n- **D1** — One.\n" @@ -339,32 +363,6 @@ def test_06i_decisions_field_not_registered(self): self.assertEqual(result.blocks, ["decision-coverage: ../specs/s.md not counted — malformed register"]) - def test_06j_child_nested_under_wrong_parent(self): - self.assert_malformed("""\ - - **D1** — One. - - **D1.1** — Child of D1. - - **D2** — Two. - - **D1.2** — Wrongly placed. - """, "Wrongly placed") - - def test_06k_withdrawn_segment_malformed(self): - self.assert_malformed("""\ - - **D1** — One. - - **D2** — Two. withdrawn reason without a ruling date. - """, "withdrawn reason") - - def test_06l_replaced_by_missing_identifier(self): - self.assert_malformed("""\ - - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D9 - """, "One.") - - def test_06m_replaced_by_group(self): - self.assert_malformed("""\ - - **D1** — One. withdrawn old, ruling: 2026-01-01; replaced by D2 - - **D2** — Group. - - **D2.1** — Child. - """, "One.") - def test_06n_legal_nested_list_in_free_prose_is_not_a_hit(self): spec = "---\ndecisions: registered\n---\n\n## Decisions\n\n" + dedent("""\ - **D1** — One. @@ -454,7 +452,8 @@ def test_09_deferrals(self): self.assertIn(" D1 → Task 1", result.lines) # 10 - def follows_case(self, spec, predecessor_status): + def follows_case(self, spec: str, predecessor_status: str) -> tuple[str, Result]: + """Run a plan that follows a predecessor at `predecessor_status`.""" self.write("specs/s.md", spec) self.write("plans/p0.md", dedent(f"""\ --- @@ -686,6 +685,27 @@ def test_16_realizes_after_first_step_not_counted(self): result.lines) self.assertIn(" D1 → —", result.lines) + # 17 + def test_17_plan_names_unreadable_spec_exits_1(self): + plan = dedent("""\ + --- + spec: ../specs/missing.md + --- + + ### Task 1: one + + **Realizes:** D1 + """) + self.write("plans/p.md", plan) + proc = subprocess.run( + [sys.executable, SCRIPT, str(self.root / "plans" / "p.md")], + capture_output=True, + text=True, + ) + self.assertEqual(proc.returncode, 1, proc.stderr) + self.assertEqual(len(proc.stderr.strip().splitlines()), 1) + self.assertTrue(proc.stderr.startswith("decision-coverage: "), proc.stderr) + class UsageTest(unittest.TestCase): def test_usage_error(self): @@ -696,7 +716,7 @@ def test_usage_error(self): def test_unreadable_file(self): proc = subprocess.run([sys.executable, SCRIPT, "/nonexistent/x.md"], capture_output=True, text=True) - self.assertEqual(proc.returncode, 2) + self.assertEqual(proc.returncode, 1) self.assertEqual(len(proc.stderr.strip().splitlines()), 1) self.assertTrue(proc.stderr.startswith("decision-coverage: "), proc.stderr) From cb4044182ff7a160481cad61b5bc7da640aaa108 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:41:49 +0200 Subject: [PATCH 097/143] fix(working-process): lexical path folding back on os.path.normpath --- .../scripts/decision-coverage.py | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/plugins/working-process/scripts/decision-coverage.py b/plugins/working-process/scripts/decision-coverage.py index 35e4d4b..1ee3c1d 100755 --- a/plugins/working-process/scripts/decision-coverage.py +++ b/plugins/working-process/scripts/decision-coverage.py @@ -18,6 +18,7 @@ from __future__ import annotations +import os import re import sys from pathlib import Path @@ -91,37 +92,18 @@ def unfenced(lines: list[str], start: int) -> list[tuple[int, str]]: return kept -def _collapse(path: Path) -> str: - """Lexically fold `..` segments, mirroring `os.path.normpath`.""" - root = path.root - kept: list[str] = [] - for part in path.parts: - if part == root and part: - continue - if part == "..": - if kept and kept[-1] != "..": - kept.pop() - elif not root: - kept.append(part) - else: - kept.append(part) - return (root + "/".join(kept)) if kept or root else "." - - -def _abspath(path: str) -> str: - """Make `path` absolute against the current directory, lexically.""" - given = Path(path) - return _collapse(given if given.is_absolute() else Path.cwd() / path) - - def resolve(base_display: str, written: str) -> str: """A path a document names, resolved against that document's directory.""" - return _collapse(Path(base_display).parent / written) + # pathlib has no lexical `..`-fold that leaves the filesystem + # untouched (Path.resolve() makes the path absolute and follows + # symlinks), so the fold itself stays on os.path.normpath; the join + # above it uses Path. + return os.path.normpath(Path(base_display).parent / written) def same_file(a: str, b: str) -> bool: """Whether `a` and `b` name the same file once made absolute.""" - return _abspath(a) == _abspath(b) + return os.path.abspath(a) == os.path.abspath(b) # --- the register -------------------------------------------------------- @@ -399,7 +381,7 @@ def predecessors(self) -> list[tuple[str, Plan, list[str]]]: if self._predecessors is not None: return self._predecessors accepted = [] - own = [_abspath(resolve(self.plan.display, s)) for s in self.plan.specs] + own = [os.path.abspath(resolve(self.plan.display, s)) for s in self.plan.specs] for line, written in self.plan.follows: display = resolve(self.plan.display, written) try: @@ -408,7 +390,7 @@ def predecessors(self) -> list[tuple[str, Plan, list[str]]]: self.add(hit(self.plan.display, line, f"`**Follows:**` names {written}, which does not exist", "step 3")) continue - theirs = [_abspath(resolve(display, s)) for s in pred.specs] + theirs = [os.path.abspath(resolve(display, s)) for s in pred.specs] shared = [s for s in own if s in theirs] if not shared: self.add(hit(self.plan.display, line, @@ -428,7 +410,7 @@ def check_spec(self, written: str) -> None: """Run steps 3 through 7 for one spec the plan names.""" plan = self.plan spec_display = resolve(plan.display, written) - spec_abs = _abspath(spec_display) + spec_abs = os.path.abspath(spec_display) register = read_register(spec_display, read_lines(spec_display)) for line in register.hits: self.add(line) @@ -573,7 +555,7 @@ def audit_spec(display: str, lines: list[str]) -> list[str]: def is_technical_design(display: str) -> bool: """The resolved path has `docs/technical-designs/` among its parents.""" - parts = Path(_abspath(display)).parts + parts = Path(os.path.abspath(display)).parts return any(a == "docs" and b == "technical-designs" for a, b in zip(parts, parts[1:])) From 1f3add64e0745657c6abc8843cf5dbc48dece765 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 10:45:39 +0200 Subject: [PATCH 098/143] docs(working-process): plan-coverage plan, python review record --- docs/plans/2026-09-26-plan-coverage.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plans/2026-09-26-plan-coverage.md b/docs/plans/2026-09-26-plan-coverage.md index f0ec572..5e41dfe 100644 --- a/docs/plans/2026-09-26-plan-coverage.md +++ b/docs/plans/2026-09-26-plan-coverage.md @@ -2680,4 +2680,5 @@ evidence is the run's record in the dispatcher's store. - fixed 2026-09-26 — Task 13's second run measured duty 10 unstable on the cheapest family (34/37 and 31/32 where 39 leaves stand), each miss following a denied compound command; ruling: 2026-09-26; the derivation becomes a script — spec D31, recorded in the spec under `### 2026-09-26 — fix from ../plans/2026-09-26-plan-coverage.md` — and Tasks 14–16 add it, point the card at it and re-run Task 13 twice; Task 13's run allows that one script, and the narration before `model:` is accepted as a known departure of the cheapest family, the workflow rule's reading of the body guarding it - fixed 2026-09-26 — Task 16's first double run matched every number but printed a `decision-coverage:` line on the technical design and dropped the `model:` line from two reports; ruling: 2026-09-26; the script skips a document under `docs/technical-designs/` and the card runs it on plans and design specs only, with its output copied below the `model:` line (`d737769`, test `4bf5c3a`); the second double run met every expectation, numbers exact both times - fixed 2026-09-26 — the final whole-branch review (fable, "With fixes") found the script failing on a non-UTF-8 stdout, a register list item that is no entry vanishing without a hit — a plan defect, since Task 14's contract narrowed the spec's step 2 ("an identity paragraph that does not parse") — a `**Realizes:**` line counted after a task's first step, the README missing python3, an order the card left open, and a test counting leaves naively; ruling: 2026-09-26; one fix wave, scoped by a second opinion from Codex, landed as `b12ab97`, `57d66f8` and `2f296f0`, and its one scoped re-review found every item addressed; the script's remaining minors (withdrawn-since lines per citation, a malformed `**Defers:**` read as an identifier, one long method) wait for a later change +- fixed 2026-09-28 — the python-code-reviewer run (report under `docs/code-review/`, ignored mode) found the script and tests unannotated, an unreadable document exiting with the usage code, missing docstrings, `os.path` where pathlib serves, and twelve same-shape tests; ruling: 2026-09-28; one fix wave (`96fb5e8`, `a2dbf18`, round 1 `cb40441`) annotates both files, exits `1` for an unreadable document where Task 14's contract said `2`, adds docstrings, moves to pathlib except the lexical `..` fold that stays on `os.path.normpath`, and folds the tests into one `subTest` table; output over the repository's documents is unchanged byte for byte, and its scoped re-review found every item addressed From 8df5e97b9caee962b1bf350da735fb817450995a Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 11:11:51 +0200 Subject: [PATCH 099/143] chore(working-process): dogfooding version for plan coverage --- plugins/working-process/.claude-plugin/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/working-process/.claude-plugin/plugin.json b/plugins/working-process/.claude-plugin/plugin.json index 21050f6..9bba5c6 100644 --- a/plugins/working-process/.claude-plugin/plugin.json +++ b/plugins/working-process/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "working-process", "description": "Spec-driven working process on top of superpowers: grilling-session, architect-session, system-designer-session, process-status and sync-rules skills, architect and plan-adversary verdict agents, architect-consult and system-designer-consult consultation agents, propagation-auditor and integrity-auditor audit agents, and process rules distributed as a Rules payload; domain plugins hook in via *-plan-review checklist skills and their own rules/ payloads", - "version": "0.18.0-dev.1.technical-design-step", + "version": "0.18.0-dev.2.plan-coverage", "author": { "name": "Missing Bits (Jacek Nakonieczny)" }, "license": "MIT", "keywords": ["process", "workflow", "spec", "plan", "review", "glossary", "adr", "architecture"], From c2739d62cde9518215cc8cf2e85b2eac09604fce Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 15:24:25 +0200 Subject: [PATCH 100/143] docs(working-process): process-setup design spec --- docs/specs/2026-09-28-process-setup-design.md | 604 ++++++++++++++++++ 1 file changed, 604 insertions(+) create mode 100644 docs/specs/2026-09-28-process-setup-design.md diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md new file mode 100644 index 0000000..1405240 --- /dev/null +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -0,0 +1,604 @@ +--- +ticket: none +date: 2026-09-28 +status: draft +decisions: registered +branch: feature/process-setup +base: develop +--- + +# process-setup — one sitting for a project's standing process answers + +The working-process rules ask the same standing questions again and +again: whether a Process directory is tracked, whether the review loop +may run on its own, whether a technical design is offered. Each answer +is a stable property of a project or a person, yet most have no place +to live, so every fresh context asks again. This spec gives those +answers one home, `.working-process/`, loads them at every session start +through the plugin's own hook, and adds a `process-setup` skill that +collects them in one sitting. The rules then read a named key instead of +"the developer's own instructions". + +## Problem + +An inventory of the shipped plugins found 39 sites that ask or read a +standing answer. Most already have a visible home — a frontmatter stamp, +a manifest, a heading annotation. These do not; each asks a question +with no named place to record the answer: + +- persona consultation consent, asked once per conversation; +- review-loop autonomy with per-round commits as its second clause, + asked once per session; +- fast-forward or squash of the `.docs` branch, asked at every + implementation-ready gate unless a `CLAUDE.md` note exists; +- the technical-design offer, re-offered at every consumption gate + unless a `CLAUDE.md` declaration exists; +- the tracked or ignored mode of each Process directory — one question + asked separately for `docs/specs/`, `docs/technical-designs/`, + `docs/plans/`, `docs/domain/`, `docs/code-review/`, `docs/memory/` and + the `.superpowers/` family. + +Where the rules name a home at all, they name prose: "a durable +preference belongs in the developer's own instructions", or "a +`CLAUDE.md` note". Neither gives a line format a rule could find, and +neither separates what a team decides from what a person consents to. +Each fresh context therefore re-derives or re-asks, and each question +spends an interruption the review loop is otherwise careful with. + +The inventory is kept with this spec's working records; its sources are +the rule files named in *Changes by file*. + +## Decisions + +- **D1** — Standing answers live in `.working-process/settings.md` + (team, committed) and `.working-process/settings.local.md` (personal, + git-ignored), at the repository root. Neither `CLAUDE.md` nor + `AGENTS.md` carries them. Argued in *The settings files*. +- **D2** — Both files use one grammar: a `key: value` line at the start + of a line, outside a fenced block; a key contains a dot; a value is a + plain token; every other line is commentary. Argued in *The settings + files*. +- **D3** — Every key has one scope, team or personal, fixed by the + registry. A team key is read from the team file only; a personal key + from the personal file only. A personal key in the team file is a + suggestion — it proposes a default answer and never grants consent. + Argued in *Scope and precedence*. +- **D4** — An invalid or duplicated value in the file that decides a key + makes the key unset; it never exposes a value from another file. + Argued in *Scope and precedence*. +- **D5** — In a worktree with no personal file, the loader reads the + main checkout's personal file, found as the first entry of + `git worktree list --porcelain`. A personal file created by hand in a + worktree replaces that fallback wholly, and the loader says so. + Argued in *Scope and precedence*. +- **D6** — One key registry in the plugin defines every key: name, + scope, allowed values, default, the rule that reads it, and the + question the skill asks. The skill, the loader and the rules keep no + other key list. Argued in *The key registry*. +- **D7** — The version-one keys. Argued in *The keys*. + - **D7.1** — `dir.default`: `tracked` | `ignored`, team. + - **D7.2** — `dir.docs/specs`: `tracked` | `ignored`, team, an + optional exception to `dir.default`. + - **D7.3** — `design.technical-design-offer`: `on` | `off`, team. + - **D7.4** — `dispatch.propagation-auditor-model`: `cheapest` | + `one-above-cheapest` | `most-capable`, team; default `cheapest`. + - **D7.5** — `docs-branch.merge`: `squash` | `fast-forward`, team. + - **D7.6** — `consult.personas`: `yes` | `no`, personal. + - **D7.7** — `review.autonomy`: `yes` | `no`, personal. + - **D7.8** — `review.per-round-commit`: `yes` | `no`, personal. + - **D7.9** — `dir.docs/technical-designs`, as D7.2. + - **D7.10** — `dir.docs/plans`, as D7.2. + - **D7.11** — `dir.docs/domain`, as D7.2. + - **D7.12** — `dir.docs/code-review`, as D7.2. + - **D7.13** — `dir..superpowers`, as D7.2. + - **D7.14** — `dir.docs/memory`, as D7.2, registered only where the + project-memory plugin is installed. +- **D8** — Keys are never renamed. A retired key stays in the registry + marked `withdrawn `, and the loader reports a withdrawn key + it meets. Argued in *The key registry*. +- **D9** — A SessionStart hook runs a loader that emits the effective + value and source of every registered key, plus warnings, as the + hook's additional context. Argued in *The loader*. +- **D10** — The loader stays silent when `.working-process/` does not + exist, exits 0 on any error of its own, and caps its output at 4 KB, + saying so when it truncates. Argued in *The loader*. +- **D11** — The loader is its own hook handler, registered without a + matcher so it also runs on resume and compaction; it finds the + registry relative to its own path and uses POSIX `sh`, `sed` and + `awk`. Argued in *The loader*. +- **D12** — A new always-on rule, `process-settings.md`, is the only + definition of the files, the grammar, the block and the procedure for + reading a key. Argued in *Reading a key*. +- **D13** — A rule reads a key from the block in its context; without a + block, from the two files; an unset key means the rule's behaviour + before this change. Argued in *Reading a key*. +- **D14** — An answer given in the current session outranks the + settings, compaction included. Argued in *Reading a key*. +- **D15** — Background agents never read settings; the dispatcher + resolves every key it acts on. Argued in *Reading a key*. +- **D16** — Each site that asks a standing question today names its key + and its behaviour when the key is unset, replacing "the developer's + own instructions". Argued in *Changes to the rules*. +- **D17** — A settings key becomes a declared signal for a Process + directory. At the first touch of a directory: a visible signal + governs; a visible signal contradicting the key is reported and the + developer asked, with nothing changed; a key without a visible signal + is applied unasked; with neither, the rule asks. Argued in *Directory + modes*. +- **D18** — `.working-process/` is not a Process directory: it is never + asked about, and the rule says so. Argued in *Directory modes*. +- **D19** — When a standing question is asked in ordinary work, its + answers include "yes, and record", which writes the answer to the + file its scope names. There is no separate "record it?" question. + Argued in *Recording an answer*. +- **D20** — A `CLAUDE.md` note that declares a key's value keeps binding + until it is migrated; where it and a settings value disagree, the + settings value wins and the session says so. Argued in *Migration*. +- **D21** — The `process-setup` skill runs only on request. It shows + the effective settings, asks only about unset keys, previews its + changes, and writes by replacing a key's line in place. Argued in + *The skill*. +- **D22** — The skill materializes a declared mode only in a Process + directory that already exists and does not contradict it; it creates + no directory. Argued in *The skill*. +- **D23** — On its first run the skill offers existing declarations and + signals as candidates: directory signals as certain, prose notes in + `CLAUDE.md` as the model's reading, to be confirmed. It never deletes + a `CLAUDE.md` note without consent. Argued in *Migration*. +- **D24** — The skill writes `.working-process/.gitignore` holding the + single line `settings.local.md` at its first write. Argued in *The + skill*. +- **D25** — The skill asks about `dir.docs/memory` only when the + project-memory plugin is installed, and the project-memory rule reads + that key only when the working-process rules are installed. Argued in + *The keys*. +- **D26** — Version one reads the working-process registry alone; no + domain registry is read or discovered. Argued in *Out of scope*. +- **D27** — A repository test checks that every registered key is cited + by the rule its entry names, that every key cited under the plugin's + `rules/`, `skills/` and `agents/` is registered, that defaults + validate, and that names are unique. Argued in *Verification*. +- **D28** — Repository tests run the loader on fixtures for + precedence, suggestions, invalid and duplicate values, unknown keys, + the worktree fallback, the size cap, silence and error exit. Argued + in *Verification*. +- **D29** — A Claude Code dogfood run proves the block reaches a new + session, a recorded question is not asked, and the block returns after + compaction. Argued in *Verification*. +- **D30** — A Codex run proves the same loader delivers its block + through a repository-level Codex hook, after trust and after + compaction or resume. Argued in *Verification*. +- **D31** — The glossary's **First-create question** entry gains the + settings key as a signal. Argued in *Changes by file*. +- **D32** — An absent `dir.` exception inherits `dir.default`; an + invalid one is unset and the rule asks, never inheriting. Argued in + *The keys*. +- **D33** — The loader finds the repository root through + `git rev-parse --show-toplevel`, then `CLAUDE_PROJECT_DIR`, then the + working directory. Argued in *The loader*. +- **D34** — The loader offers `--print`, which emits the block to + standard output, and `--validate --scope team|personal `, which + exits non-zero with its warnings when the file would not validate. + Argued in *The loader*. +- **D35** — The propagation-auditor card stops prescribing the cheapest + family and accepts the tier the dispatcher resolved, still reporting + its family for the dispatcher's comparison. Argued in *Reading a key*. +- **D36** — Every line shaped like a key — a dotted name and a colon at + the start of a line, outside a fence — is a declaration, validated, + and diagnosed when its value is invalid; it is never commentary. + Argued in *The settings files*. +- **D37** — A write inserts an absent key, replaces a present one, and + on a duplicate asks which line stands; after writing, the writer + prints the fresh block, which supersedes the old one for the rest of + the session. Argued in *The skill*. +- **D38** — Without a trustworthy block — none, or one marked + incomplete — a session resolves keys by the loader's own procedure, + through `--print` or by applying the rule's full resolution by hand. + Argued in *Reading a key*. +- **D39** — The skill asks about directory exceptions only when the + developer asks for them. Argued in *The skill*. +- **D40** — The python and salesforce review commands and the + project-memory rule, which restate the first-create predicate, adopt + the settings key and its conflict question. Argued in *Changes to the + rules*. + +## The settings files + +Two files at the repository root, in a directory named for the plugin +rather than for any tool, since the plugin will also run natively in +Codex: + +- `.working-process/settings.md` — the team's answers; committed like + any other project file. +- `.working-process/settings.local.md` — one person's answers; kept out + of git by `.working-process/.gitignore`, which holds the single line + `settings.local.md`. The directory ignores its own local file, so the + repository's root `.gitignore` is never touched. + +Keeping the answers out of `CLAUDE.md` and `AGENTS.md` does three +things: the plugin loads them without depending on import support +(Codex documents none), a project's shared instructions stay free of +process machinery, and the answers fit a format a script can resolve. + +A settings line is a `key: value` pair at the start of a line and +outside a fenced block. A key holds a dot — `review.autonomy`, +`dir.docs/specs` — so a line of prose such as `Note: see below` never +reads as a key. A value is a plain token: letters, digits, `.`, `_`, +`/` and `-`, with no quotes. + +Recognizing a declaration and validating it are separate steps. Every +line shaped like a key — a dotted name and a colon at the start of a +line, outside a fence — is a declaration, whatever follows the colon, +and a value that is not a valid token for that key is diagnosed rather +than read past. So `review.autonomy: "no"` below `review.autonomy: yes` +is a duplicate, and the key is unset; it cannot hide as commentary and +leave the earlier consent standing. Fences open and close on lines +starting with three backticks; leading whitespace makes a line +commentary. Every other line is commentary, and the skill writes each +key's question as a comment line above it, so the file explains itself. The +grammar follows the one precedent in the marketplace, the +`trigger-framework:` declaration of salesforce-standards: one line, one +key, found by a line-anchored match. + +## Scope and precedence + +Each key has one scope, and the scope decides which file is read: + +| Key scope | Decided by | Ignored, with a warning | +|---|---|---| +| team | the team file, then the registry default | a line in the personal file | +| personal | the personal file, then the registry default | a line in the team file, shown as a suggestion | + +A team can say that it would like personas consulted, but only each +person's own file grants that consent. A personal key in the team file +therefore surfaces as `unset [team suggests: yes]`, and the question for +that key offers the suggestion as its default answer. + +An invalid value or a duplicated key in the deciding file leaves the key +unset, with a warning. It never lets a value from elsewhere through. The +failure this rules out is a mistyped `review.autonomy: noo` in a +personal file letting a team suggestion stand in for consent. + +A worktree is a separate checkout, and an ignored file does not follow +into it. Where a worktree has no personal file of its own, the loader +reads the main checkout's, whose path is the first entry of +`git worktree list --porcelain` wherever the worktree lives. (The +parent of `git rev-parse --git-common-dir` is not a checkout path in +every layout, so it is not used.) One set of personal answers then +serves every worktree of a repository. The skill, run in a worktree, +writes personal answers to the main checkout's file, so a worktree +never gains a file of its own through the skill. A personal file +created in a worktree by hand replaces the fallback wholly — no +per-key merge — and the loader's first line says which file it read. + +A home-level file of personal defaults for every project is out of +version one. + +## The key registry + +One file in the plugin defines every key. Each entry carries the key's +name, its scope, its allowed values, its default (a value, or `unset` +where the rule asks), the rule file that reads it, and the question the +skill asks. The shape is fixed, one field per line under a heading +naming the key, so that the loader's `sed` and the repository test both +parse it without a Markdown parser — the same discipline the rules +manifest follows. + +The skill takes its questions from the registry, the loader takes its +validation from it, and the rules name keys and nothing more; no second +key list exists to drift. A key is never renamed: renaming would +silently orphan every file that set it. A retired key keeps its entry +with `withdrawn `, and the loader reports it as withdrawn +rather than unknown. The registry's exact path and heading shape are the +plan's to fix. + +## The keys + +Version one registers the keys whose question has no home today: + +| Key | Scope | Values | Unset means | Read by | +|---|---|---|---|---| +| `dir.default` | team | `tracked` \| `ignored` | ask at first create | process-artifacts | +| `dir.` | team | `tracked` \| `ignored` | absent: `dir.default` applies; invalid: ask | process-artifacts | +| `design.technical-design-offer` | team | `on` \| `off` | today's manifest-raised offer | workflow | +| `dispatch.propagation-auditor-model` | team | `cheapest` \| `one-above-cheapest` \| `most-capable` | `cheapest` | workflow | +| `docs-branch.merge` | team | `squash` \| `fast-forward` | ask at the gate | spec-plan-lifecycle | +| `consult.personas` | personal | `yes` \| `no` | ask once per conversation | workflow | +| `review.autonomy` | personal | `yes` \| `no` | ask at the first verdict dispatch | workflow | +| `review.per-round-commit` | personal | `yes` \| `no` | ask with the autonomy question | workflow, spec-plan-lifecycle | + +The `dir.` exceptions exist for `docs/specs`, +`docs/technical-designs`, `docs/plans`, `docs/domain`, +`docs/code-review`, `.superpowers` and `docs/memory`. One default with +exceptions collapses what is today one question asked separately for +each directory into one question with rare exceptions. An exception has +three states. Absent, it inherits `dir.default`. Valid, it decides its +directory. Invalid, it is unset — the rule asks at that directory's +first create, and it does not inherit, for the reason D4 gives: an +error never lets another source's value through, and a directory's mode +decides what enters the repository. `docs/memory` is +the project-memory plugin's directory: the skill asks about it only +where that plugin is installed, and the project-memory rule reads the +key only where the working-process rules are installed, the pattern its +rule already uses for Process directories. + +The gate model is a tier, not a model name, so a Codex port can map it +onto its own families. Consent values are `yes` and `no` alone. A +session-scoped answer — "not now", "not in this session" — is never a +key, because it dies with the session by definition. + +Deliberately not keys in version one: process weight, whose readers do +not exist yet; the rules install level and commit mode, whose answer +the rules manifest's position already records; the review loop's round +cap, which stays fixed by the rule — making it a key would change the +rule's behaviour, not only where an answer is recorded; and every +per-document artifact (stamps, chain debt, `ticket:`), which has its +own home. + +## The loader + +A script shipped with the plugin, registered as a second SessionStart +handler beside the rules-drift check. It reads the two settings files, +applies scope and precedence against the registry, and emits one block +as the hook's additional context: + +``` +working-process settings (root: ; team: settings.md; local: settings.local.md) +dir.default: tracked [team] +review.autonomy: yes [local] +consult.personas: unset [team suggests: yes] +warning: settings.md:14 unknown key `review.autonmy` — ignored +``` + +The first line names the block, so a reader can find it among other +hooks' context, whose order is undocumented, and understand it without +the rule. Every registered key is listed, set or not, so a rule has one +place to read and never needs the registry. Warnings live in the block, +because nobody reads a hook's stderr: unknown keys, invalid values, +duplicates, a key in the wrong scope's file, a withdrawn key. + +The output is capped at 4 KB. That is a byte cap, not a token +guarantee: Codex's documented per-handler threshold is about 2,500 +tokens, above which it stores the context on disk and shows a preview, +and 4 KB of ordinary output sits under it with room to spare. The +dogfood checks it rather than the spec promising it. Where the cap +truncates, the last line says the block is incomplete; it never +truncates silently. A block for the version-one keys runs to about +1 KB. Paths and warnings in the block are escaped as JSON requires +when the hook emits its JSON envelope. + +The loader stays silent — no output at all — when `.working-process/` +does not exist, so a project that never adopted the settings pays +nothing. An error of the loader's own ends in silence and exit 0, as +the drift check does, and the rule's fallback (*Reading a key*) covers +the missing block. + +It finds the repository root through `git rev-parse --show-toplevel`, +then `CLAUDE_PROJECT_DIR`, then the working directory — in a worktree, +the worktree's own root — and the registry +relative to its own path rather than through `CLAUDE_PLUGIN_ROOT`, which +a Codex port may not provide. It is registered without a matcher, so it +runs on startup, resume, clear and compaction alike, and a compacted +conversation gets its settings back. It is its own handler rather than a +branch of the drift check, because Codex budgets context per handler. +It uses POSIX `sh` with `sed` and `awk`, the drift check's toolchain, +and no `jq`. Two modes serve the skill and the no-hook path: +`--print` emits the same block to standard output, and +`--validate --scope team|personal ` checks a candidate file +against the scope it is meant for, printing its warnings and exiting +non-zero when the file would not validate. Only the hook mode swallows +its errors and exits 0; the other two report failure. + +## Reading a key + +A new always-on rule, `process-settings.md`, is the one definition of +the files, their grammar, the block and the reading procedure. The +rules that read a key cite it and restate none of it. + +To read a key, a session: + +1. takes its value from the settings block in its context, unless the + block says it is incomplete; +2. without a trustworthy block — none arrived because hooks are + disabled or a Codex plugin is not yet trusted, or the block says it + is incomplete — resolves the key by the loader's own procedure: + running `--print` where the script is reachable, otherwise applying + `process-settings.md`'s full resolution by hand — scope, the + worktree fallback, defaults, duplicates and invalid values — never a + shortcut through the files; +3. where the key is unset, behaves as the rule did before this change: + the question is asked, or the offer made, exactly as today. + +An answer given in the current session outranks the settings, so a +developer who says "not in this session" after the block granted +consent is not overruled by the next compaction. The rule's existing +clause for a compacted conversation whose consent state is unclear now +covers only such session answers; a recorded key is simply read again. + +Background agents — the verdict agents, the auditors, the consult +agents — never read settings. Every version-one key is consumed by the +dispatcher: it picks the gate's model, decides the loop, makes the +offer, settles the directory. Nothing depends on a subagent-start hook. +One card changes all the same: the propagation auditor's states that it +runs on the cheapest family and treats an upward mismatch as a fault, +which would contradict a team that set a higher tier. It instead +accepts the tier its dispatcher resolved and still opens its report +with its family, so the dispatcher's comparison keeps working. + +## Changes to the rules + +Each site that asks a standing question today names its key and says +what happens when the key is unset. "The developer's own instructions" +in those sentences becomes the key: + +- `workflow.md` — persona consultation (`consult.personas`), loop + autonomy and per-round commits (`review.autonomy`, + `review.per-round-commit`), the technical-design offer + (`design.technical-design-offer`; the sentence that reads a + declaration "until a standing home for a project's process answers + exists" is rewritten to cite the key), and the propagation auditor's + tier (`dispatch.propagation-auditor-model`). +- `spec-plan-lifecycle.md` — the per-round commit consent + (`review.per-round-commit`) and the fast-forward or squash of the + `.docs` branch (`docs-branch.merge`). +- `process-artifacts.md` — the settings key joins the directory + signals (*Directory modes*). +- `review-reports.md` and the grilling-session skill defer to + `process-artifacts.md`'s signal list and gain nothing of their own. +- The python and salesforce review commands restate the predicate so + each stands alone, and today skip the question whenever a visible + signal exists. They adopt the settings key and D17's order, including + the conflict question. +- `project-memory.md` — `dir.docs/memory` counts as a declaration when + the working-process rules are installed, and its "never ask when a + prior decision is present" gains the conflict question: a visible + signal that contradicts the key is reported and the developer asked. +- `agents/propagation-auditor.md` — the tier clause, as *Reading a key* + says. + +## Directory modes + +`process-artifacts.md` today reads two visible signals — a +`.gitignore` holding exactly `*` means ignored, a git-tracked file means +tracked — and a declared instruction, "materialized by whoever first +acts on it". A settings key becomes that declared instruction's named +form. At the first touch of a Process directory: + +1. a visible signal governs; +2. a visible signal that contradicts the key is reported, and the + developer asked which stands; nothing is changed until they answer; +3. a key with no visible signal is applied without asking — the ignored + mode by writing the `*` `.gitignore`, the tracked mode by the first + committed file; +4. with neither, the rule asks, as today. + +`.working-process/` itself is configuration, not a Process directory: +its team file is committed by definition and its local file ignored by +its own `.gitignore`. It is never asked about, and `process-artifacts.md` +says so, so it cannot become one more first-create question. + +## Recording an answer + +When a standing question is asked in ordinary work, its answers gain +one option: "yes, and record" (or "no, and record"). Choosing it writes +the answer to the file the key's scope names, creating the file and its +`.gitignore` where they are missing. There is no second question: the +developer is interrupted once, as the review loop's one-batch-per-round +contract intends. A session never records an answer the developer did +not choose to record. + +## Migration + +Some projects already hold answers elsewhere: directory signals, a +`CLAUDE.md` note declining the technical-design offer, a note fixing the +squash choice. A `CLAUDE.md` note keeps binding until it is migrated. +Where a note and a settings value disagree, the settings value wins and +the session says which note it overrode. + +On its first run the skill gathers the existing answers as candidates. +Directory signals are mechanical and shown as settled. Prose notes are +not parseable, so the skill offers its reading of each as the model's +reading, with the note's location, for the developer to confirm. It +never deletes a note without consent; after a confirmed move it offers +to remove the note that now duplicates the key. + +## The skill + +`process-setup` runs only when the developer asks for it; nothing +nudges them to run it. A run: + +1. calls the loader's `--print` and shows the effective settings as one + table — key, value, source — together with the first-run candidates + (*Migration*); +2. asks only about unset keys, one at a time, with a recommendation, + saying for each which file the answer goes to; a team suggestion is + offered as the default answer to a personal key; project-memory's key + is skipped where that plugin is not installed. Directory exceptions + are not unset answers but optional ones: the skill asks + `dir.default` and offers exceptions only when the developer asks for + them; +3. previews the change to each file, validates it with + `--validate --scope`, and writes: an absent key is added, under its + question as a comment; a present key's line is replaced in place; a + duplicated key is shown with both lines and the developer chooses + which stands. Every other line and comment stays untouched, and an + unchanged file is not written. After writing, it prints the fresh + block, which supersedes the one from session start for the rest of + the session — the same holds for a "yes, and record" answer; +4. materializes a declared mode in each Process directory that already + exists and does not contradict it, and reports every contradiction; + it creates no directory; +5. writes `.working-process/.gitignore` at its first write. + +A second run shows the same table and asks only about what is unset or +what the developer wants to change. It never replays the whole +questionnaire. + +## Verification + +- **Consistency test** (repository, beside the decision-coverage + tests): every registered key is cited by the rule its entry names; + every key reference is registered, where a reference is a + backticked token with a registered key's shape — a dotted name + starting with one of the registry's prefixes — under the `rules/`, + `skills/`, `agents/` and `commands/` of working-process, + project-memory, python-standards and salesforce-standards, examples in + fenced blocks excluded; every default validates; no name repeats. +- **Loader tests** on fixtures: team and personal precedence; a + personal key in the team file shown as a suggestion; an invalid value + in the deciding file leaving the key unset without exposing another + file's value; a duplicate key leaving it unset; an unknown key warned + about; the worktree fallback, on a real `git worktree` in a temporary + directory; the 4 KB cap and its truncation line; silence without + `.working-process/`; silence and exit 0 on an error of its own. +- **Claude Code dogfood**, with the plugin loaded from the checkout: the + skill records settings; a new session receives the block; a recorded + question is not asked; after `/compact` the block is back. The run + also settles whether the SessionStart hook takes plain standard output, + the JSON `additionalContext` shape the drift check uses, or both. +- **Codex check**: in a scratch project, a repository-level + `.codex/hooks.json` runs the same loader; the block reaches the model + once the hook is trusted, and again after compaction or resume. No + Codex plugin packaging is involved. + +## Out of scope + +- Process weight — its own package, which adds the key together with the + rules that read it. +- Home-level personal defaults shared by every project. +- Domain registries. A domain plugin may one day add its own registry, + discovered the way the plan-adversary discovers `*-plan-review` + skills; version one reads only the working-process registry, and no + convention is fixed for the others yet. salesforce-standards' + `trigger-framework:` and `vendor-paths:` declarations stay where they + are. +- Keys for the rules install level and commit mode. +- A configurable round cap. +- Packaging working-process as a Codex plugin; the Codex check proves + only the loader. + +## Changes by file + +- New: the `process-setup` skill; the key registry; the loader script; + the `process-settings.md` rule; a second SessionStart handler in + `hooks/hooks.json`. +- `rules/workflow.md`, `rules/spec-plan-lifecycle.md`, + `rules/process-artifacts.md` — as *Changes to the rules* and + *Directory modes* say. +- `agents/propagation-auditor.md` — the tier clause. +- `plugins/project-memory/rules/project-memory.md` — the conditional + `dir.docs/memory` declaration and its conflict question. +- `plugins/python-standards/commands/python-review.md`, + `plugins/salesforce-standards/commands/salesforce-review.md` — the + restated first-create predicate. +- `docs/domain/glossary.md` — **First-create question** gains the + settings key as a signal, through a grilling session on this spec. +- Tests under `tests/working-process/`. +- README and CHANGELOG entries under `## Unreleased` for working-process + and project-memory; a `-dev` dogfood version. + +## Open questions + +- The name `one-above-cheapest` for the middle gate tier. +- `dir..superpowers` carries a double dot because the path starts with + one; the plan may keep it or fix a mapping for leading-dot paths. From f0a57d4132c5377d06f594c5e71688dd00ab500d Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 16:22:42 +0200 Subject: [PATCH 101/143] docs(working-process): process-setup spec grilled, glossary terms --- docs/domain/glossary.md | 58 ++++++++-- docs/specs/2026-09-28-process-setup-design.md | 100 +++++++++++++----- 2 files changed, 123 insertions(+), 35 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index c90684b..a3c0be1 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -18,12 +18,14 @@ _Avoid_: artifact folder The question — ignored mode or tracked mode — asked when a process directory is created for the first time, or exists with no prior decision: neither an observable signal (a `.gitignore` containing -exactly `*`, a git-tracked file) nor an explicit project instruction -declaring the mode (e.g. a CLAUDE.md note). Never asked when any of -these signals is present. A declared ignored mode is materialized by -whoever first acts on it — writing the `*` `.gitignore` — making the -decision observable; a declared tracked mode becomes observable with -the first committed file. +exactly `*`, a git-tracked file), nor a `dir.*` **Settings key**, nor +an explicit project instruction declaring the mode (e.g. a CLAUDE.md +note). Never asked when any of these signals is present; an observable +signal that contradicts a settings key is reported and the developer +asked which stands. A declared ignored mode is materialized by whoever +first acts on it — writing the `*` `.gitignore` — making the decision +observable; a declared tracked mode becomes observable with the first +committed file. _Avoid_: self-ignore **Ignored mode**: @@ -36,6 +38,50 @@ mode. A process directory whose files are committed; detected by any git-tracked file under it. +**Standing answer**: +An answer to a process question that is a stable property of a project +or a person — a directory's mode, loop autonomy consent, the +technical-design offer — rather than of one document or one session. +Recorded as a **Settings line**; a session-scoped answer ("not in this +session") is never one. +_Avoid_: preference, config + +**Settings key**: +A dotted name defined in the **Key registry** (`review.autonomy`, +`dir.docs/specs`), carrying one scope: a **Team key** or a **Personal +key**. +_Avoid_: option, flag + +**Team key**: +A settings key decided by the team file, `.working-process/settings.md`. +A line for it in the personal file is ignored. +_Avoid_: shared setting + +**Personal key**: +A settings key decided by one person's file, +`.working-process/settings.local.md`. A line for it in the team file is +a suggestion — it proposes a default answer and never grants consent. +_Avoid_: private setting + +**Settings line**: +A `key: value` line at the start of a line, outside a fence, in a +settings file; every line shaped like one is validated. Distinct from a +declaration, which is a prose note in `CLAUDE.md` that a standing answer +may still be migrated from. +_Avoid_: declaration (for a settings line) + +**Key registry**: +The one definition of every settings key in the working-process plugin +— scope, values, default, reading rule, question. The loader, the +`process-setup` skill and the rules keep no other key list. +_Avoid_: schema, key list + +**Settings block**: +What the settings loader emits at session start: every registered key +with its effective value and source, plus warnings. A block marked +incomplete is not trusted. +_Avoid_: settings dump, config context + **Contract probe**: The ordered path check a domain review skill — or a dispatching review command, pre-dispatch — runs to find the installed diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 1405240..625c5c2 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -2,6 +2,7 @@ ticket: none date: 2026-09-28 status: draft +grilled: 2026-09-28 decisions: registered branch: feature/process-setup base: develop @@ -80,8 +81,8 @@ the rule files named in *Changes by file*. - **D7.2** — `dir.docs/specs`: `tracked` | `ignored`, team, an optional exception to `dir.default`. - **D7.3** — `design.technical-design-offer`: `on` | `off`, team. - - **D7.4** — `dispatch.propagation-auditor-model`: `cheapest` | - `one-above-cheapest` | `most-capable`, team; default `cheapest`. + - **D7.4** — `dispatch.propagation-auditor-tier`: `cheapest` | + `mid` | `most-capable`, team; default `cheapest`. - **D7.5** — `docs-branch.merge`: `squash` | `fast-forward`, team. - **D7.6** — `consult.personas`: `yes` | `no`, personal. - **D7.7** — `review.autonomy`: `yes` | `no`, personal. @@ -91,8 +92,8 @@ the rule files named in *Changes by file*. - **D7.11** — `dir.docs/domain`, as D7.2. - **D7.12** — `dir.docs/code-review`, as D7.2. - **D7.13** — `dir..superpowers`, as D7.2. - - **D7.14** — `dir.docs/memory`, as D7.2, registered only where the - project-memory plugin is installed. + - **D7.14** — `dir.docs/memory`, as D7.2; always registered, asked + and read only as D25 says. - **D8** — Keys are never renamed. A retired key stays in the registry marked `withdrawn `, and the loader reports a withdrawn key it meets. Argued in *The key registry*. @@ -109,9 +110,9 @@ the rule files named in *Changes by file*. - **D12** — A new always-on rule, `process-settings.md`, is the only definition of the files, the grammar, the block and the procedure for reading a key. Argued in *Reading a key*. -- **D13** — A rule reads a key from the block in its context; without a - block, from the two files; an unset key means the rule's behaviour - before this change. Argued in *Reading a key*. +- **D13** — A rule reads a key from the block in its context; without + a trustworthy block, by the procedure D38 names; an unset key means + the rule's behaviour before this change. Argued in *Reading a key*. - **D14** — An answer given in the current session outranks the settings, compaction included. Argued in *Reading a key*. - **D15** — Background agents never read settings; the dispatcher @@ -130,14 +131,15 @@ the rule files named in *Changes by file*. - **D19** — When a standing question is asked in ordinary work, its answers include "yes, and record", which writes the answer to the file its scope names. There is no separate "record it?" question. + Anyone may record a team key this way; the write names the team file. Argued in *Recording an answer*. - **D20** — A `CLAUDE.md` note that declares a key's value keeps binding until it is migrated; where it and a settings value disagree, the settings value wins and the session says so. Argued in *Migration*. - **D21** — The `process-setup` skill runs only on request. It shows - the effective settings, asks only about unset keys, previews its - changes, and writes by replacing a key's line in place. Argued in - *The skill*. + the effective settings, asks about unset keys other than optional + directory exceptions (D39), previews its changes, and writes by the + rules D37 names. Argued in *The skill*. - **D22** — The skill materializes a declared mode only in a Process directory that already exists and does not contradict it; it creates no directory. Argued in *The skill*. @@ -184,7 +186,7 @@ the rule files named in *Changes by file*. family and accepts the tier the dispatcher resolved, still reporting its family for the dispatcher's comparison. Argued in *Reading a key*. - **D36** — Every line shaped like a key — a dotted name and a colon at - the start of a line, outside a fence — is a declaration, validated, + the start of a line, outside a fence — is a settings line, validated, and diagnosed when its value is invalid; it is never commentary. Argued in *The settings files*. - **D37** — A write inserts an absent key, replaces a present one, and @@ -201,6 +203,17 @@ the rule files named in *Changes by file*. project-memory rule, which restate the first-create predicate, adopt the settings key and its conflict question. Argued in *Changes to the rules*. +- **D41** — Personal answers are written where the loader reads them: + in a worktree, to the main checkout's personal file, with its + `.gitignore` beside it — for a skill write and a "yes, and record" + answer alike. Where a hand-made worktree file shadows that + destination, the preview says the write will not change the current + worktree's answer. In a bare repository with linked worktrees there + is no main checkout: the loader has no fallback and says so, and + personal answers go to the current worktree's file, binding that + worktree only, as the skill says. Argued in *Scope and precedence*. +- **D42** — `--print` is uncapped; only the hook's output carries the + 4 KB cap. Argued in *The loader*. ## The settings files @@ -226,9 +239,9 @@ outside a fenced block. A key holds a dot — `review.autonomy`, reads as a key. A value is a plain token: letters, digits, `.`, `_`, `/` and `-`, with no quotes. -Recognizing a declaration and validating it are separate steps. Every +Recognizing a settings line and validating it are separate steps. Every line shaped like a key — a dotted name and a colon at the start of a -line, outside a fence — is a declaration, whatever follows the colon, +line, outside a fence — is a settings line, whatever follows the colon, and a value that is not a valid token for that key is diagnosed rather than read past. So `review.autonomy: "no"` below `review.autonomy: yes` is a duplicate, and the key is unset; it cannot hide as commentary and @@ -265,11 +278,22 @@ reads the main checkout's, whose path is the first entry of `git worktree list --porcelain` wherever the worktree lives. (The parent of `git rev-parse --git-common-dir` is not a checkout path in every layout, so it is not used.) One set of personal answers then -serves every worktree of a repository. The skill, run in a worktree, -writes personal answers to the main checkout's file, so a worktree -never gains a file of its own through the skill. A personal file -created in a worktree by hand replaces the fallback wholly — no -per-key merge — and the loader's first line says which file it read. +serves every worktree of a repository. A personal answer is written +where the loader reads it: the skill, run in a worktree, writes to the +main checkout's personal file and puts the `.gitignore` beside it, and +a "yes, and record" answer goes to the same place, so a worktree never +gains a file of its own through a write. A personal file created in a +worktree by hand replaces the fallback wholly — no per-key merge — and +the loader's first line says which file it read; a write made from that +worktree still goes to the main checkout, and its preview says it will +not change the current worktree's answer. + +A bare repository with linked worktrees has no main checkout: the first +porcelain entry is the bare repository itself, marked `bare`. There the +loader has no fallback and its first line says so, and a personal answer +goes to the current worktree's own file, with the skill saying plainly +that it binds that worktree only. Nothing is ever written inside the +bare repository. A home-level file of personal defaults for every project is out of version one. @@ -301,7 +325,7 @@ Version one registers the keys whose question has no home today: | `dir.default` | team | `tracked` \| `ignored` | ask at first create | process-artifacts | | `dir.` | team | `tracked` \| `ignored` | absent: `dir.default` applies; invalid: ask | process-artifacts | | `design.technical-design-offer` | team | `on` \| `off` | today's manifest-raised offer | workflow | -| `dispatch.propagation-auditor-model` | team | `cheapest` \| `one-above-cheapest` \| `most-capable` | `cheapest` | workflow | +| `dispatch.propagation-auditor-tier` | team | `cheapest` \| `mid` \| `most-capable` | `cheapest` | workflow | | `docs-branch.merge` | team | `squash` \| `fast-forward` | ask at the gate | spec-plan-lifecycle | | `consult.personas` | personal | `yes` \| `no` | ask once per conversation | workflow | | `review.autonomy` | personal | `yes` \| `no` | ask at the first verdict dispatch | workflow | @@ -322,8 +346,19 @@ where that plugin is installed, and the project-memory rule reads the key only where the working-process rules are installed, the pattern its rule already uses for Process directories. -The gate model is a tier, not a model name, so a Codex port can map it -onto its own families. Consent values are `yes` and `no` alone. A +A key is `dir.` followed by the path exactly, so the `.superpowers` +exception is `dir..superpowers`; the double dot is the price of one +mapping with no special case for paths that start with a dot. + +The gate's value is a tier in the glossary's sense — a relative rung, +never a model name — and its three values use the glossary's own words +for the rungs. Each host resolves the tier at dispatch through a table +of its own; version one ships only Claude Code's (Haiku for `cheapest`, +the family one below the most capable for `mid`, the most capable +available for `most-capable`). A Codex port adds its table; Codex's +current ladder has three rungs, which fit the three values, and its +separate reasoning-effort setting is the port's concern, not a +settings value. Consent values are `yes` and `no` alone. A session-scoped answer — "not now", "not in this session" — is never a key, because it dies with the session by definition. @@ -383,7 +418,9 @@ conversation gets its settings back. It is its own handler rather than a branch of the drift check, because Codex budgets context per handler. It uses POSIX `sh` with `sed` and `awk`, the drift check's toolchain, and no `jq`. Two modes serve the skill and the no-hook path: -`--print` emits the same block to standard output, and +`--print` emits the same block to standard output, uncapped — the 4 KB +cap guards the hook's context budget, and a skill or a session +recovering from a truncated block needs the whole of it — and `--validate --scope team|personal ` checks a candidate file against the scope it is meant for, printing its warnings and exiting non-zero when the file would not validate. Only the hook mode swallows @@ -437,7 +474,7 @@ in those sentences becomes the key: (`design.technical-design-offer`; the sentence that reads a declaration "until a standing home for a project's process answers exists" is rewritten to cite the key), and the propagation auditor's - tier (`dispatch.propagation-auditor-model`). + tier (`dispatch.propagation-auditor-tier`). - `spec-plan-lifecycle.md` — the per-round commit consent (`review.per-round-commit`) and the fast-forward or squash of the `.docs` branch (`docs-branch.merge`). @@ -449,7 +486,7 @@ in those sentences becomes the key: each stands alone, and today skip the question whenever a visible signal exists. They adopt the settings key and D17's order, including the conflict question. -- `project-memory.md` — `dir.docs/memory` counts as a declaration when +- `project-memory.md` — `dir.docs/memory` counts as the declared mode when the working-process rules are installed, and its "never ask when a prior decision is present" gains the conflict question: a visible signal that contradicts the key is reported and the developer asked. @@ -487,6 +524,11 @@ developer is interrupted once, as the review loop's one-batch-per-round contract intends. A session never records an answer the developer did not choose to record. +Anyone may record a team key this way, not only whoever ran the setup. +The team file is an ordinary committed file, so the change shows in the +diff and passes the same review as any other; the session says, when +it writes, that the answer lands in the team's file. + ## Migration Some projects already hold answers elsewhere: directory signals, a @@ -528,7 +570,9 @@ nudges them to run it. A run: 4. materializes a declared mode in each Process directory that already exists and does not contradict it, and reports every contradiction; it creates no directory; -5. writes `.working-process/.gitignore` at its first write. +5. writes the `.gitignore` holding `settings.local.md` beside the + personal file it first writes, which in a worktree is the main + checkout's (D41). A second run shows the same table and asks only about what is unset or what the developer wants to change. It never replays the whole @@ -587,7 +631,7 @@ questionnaire. *Directory modes* say. - `agents/propagation-auditor.md` — the tier clause. - `plugins/project-memory/rules/project-memory.md` — the conditional - `dir.docs/memory` declaration and its conflict question. + `dir.docs/memory` key and its conflict question. - `plugins/python-standards/commands/python-review.md`, `plugins/salesforce-standards/commands/salesforce-review.md` — the restated first-create predicate. @@ -599,6 +643,4 @@ questionnaire. ## Open questions -- The name `one-above-cheapest` for the middle gate tier. -- `dir..superpowers` carries a double dot because the path starts with - one; the plan may keep it or fix a mapping for leading-dot paths. +None. From 3db8141de31c336c0cf5b57ca7a92fcd46e0f9c8 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 16:51:21 +0200 Subject: [PATCH 102/143] docs(working-process): process-setup spec, architect round 1 and gate fixes --- docs/domain/glossary.md | 2 +- docs/specs/2026-09-28-process-setup-design.md | 68 +++++++++++++++---- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index a3c0be1..0bd8e8b 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -73,7 +73,7 @@ _Avoid_: declaration (for a settings line) **Key registry**: The one definition of every settings key in the working-process plugin — scope, values, default, reading rule, question. The loader, the -`process-setup` skill and the rules keep no other key list. +`process-setup` skill and the rules keep no second definition of a key. _Avoid_: schema, key list **Settings block**: diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 625c5c2..2eaba16 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -3,6 +3,7 @@ ticket: none date: 2026-09-28 status: draft grilled: 2026-09-28 +architect: blocking decisions: registered branch: feature/process-setup base: develop @@ -75,7 +76,7 @@ the rule files named in *Changes by file*. - **D6** — One key registry in the plugin defines every key: name, scope, allowed values, default, the rule that reads it, and the question the skill asks. The skill, the loader and the rules keep no - other key list. Argued in *The key registry*. + second definition of a key. Argued in *The key registry*. - **D7** — The version-one keys. Argued in *The keys*. - **D7.1** — `dir.default`: `tracked` | `ignored`, team. - **D7.2** — `dir.docs/specs`: `tracked` | `ignored`, team, an @@ -121,8 +122,9 @@ the rule files named in *Changes by file*. and its behaviour when the key is unset, replacing "the developer's own instructions". Argued in *Changes to the rules*. - **D17** — A settings key becomes a declared signal for a Process - directory. At the first touch of a directory: a visible signal - governs; a visible signal contradicting the key is reported and the + directory. At the first touch of a directory: a visible signal that + agrees with the key, or stands where no key is set, governs; one + contradicting the key is reported and the developer asked, with nothing changed; a key without a visible signal is applied unasked; with neither, the rule asks. Argued in *Directory modes*. @@ -157,9 +159,10 @@ the rule files named in *Changes by file*. - **D26** — Version one reads the working-process registry alone; no domain registry is read or discovered. Argued in *Out of scope*. - **D27** — A repository test checks that every registered key is cited - by the rule its entry names, that every key cited under the plugin's - `rules/`, `skills/` and `agents/` is registered, that defaults - validate, and that names are unique. Argued in *Verification*. + by the rule its entry names, that every key reference under the + `rules/`, `skills/`, `agents/` and `commands/` of working-process, + project-memory, python-standards and salesforce-standards is + registered, that defaults validate, and that names are unique. Argued in *Verification*. - **D28** — Repository tests run the loader on fixtures for precedence, suggestions, invalid and duplicate values, unknown keys, the worktree fallback, the size cap, silence and error exit. Argued @@ -310,7 +313,7 @@ manifest follows. The skill takes its questions from the registry, the loader takes its validation from it, and the rules name keys and nothing more; no second -key list exists to drift. A key is never renamed: renaming would +definition of a key exists to drift. A key is never renamed: renaming would silently orphan every file that set it. A retired key keeps its entry with `withdrawn `, and the loader reports it as withdrawn rather than unknown. The registry's exact path and heading shape are the @@ -350,9 +353,16 @@ A key is `dir.` followed by the path exactly, so the `.superpowers` exception is `dir..superpowers`; the double dot is the price of one mapping with no special case for paths that start with a dot. +`design.technical-design-offer: on` makes the offer at every +consumption gate whether or not a toolchain manifest is present — the +declaration the workflow rule already honours — while an unset key +leaves today's behaviour, where only a manifest raises the offer; `off` +suppresses it. + The gate's value is a tier in the glossary's sense — a relative rung, -never a model name — and its three values use the glossary's own words -for the rungs. Each host resolves the tier at dispatch through a table +never a model name. `mid` and `most-capable` are the glossary's own +words for those rungs; `cheapest` is the workflow rule's, which +prescribes "the cheapest available family" for this agent today. Each host resolves the tier at dispatch through a table of its own; version one ships only Claude Code's (Haiku for `cheapest`, the family one below the most capable for `mid`, the most capable available for `most-capable`). A Codex port adds its table; Codex's @@ -412,12 +422,18 @@ It finds the repository root through `git rev-parse --show-toplevel`, then `CLAUDE_PROJECT_DIR`, then the working directory — in a worktree, the worktree's own root — and the registry relative to its own path rather than through `CLAUDE_PLUGIN_ROOT`, which -a Codex port may not provide. It is registered without a matcher, so it +a Codex port may not provide. The git root comes first on purpose: the team file is a committed +property of the repository, so a project rooted below its git toplevel +still reads the settings at the git root. The drift check reads +`CLAUDE_PROJECT_DIR` first; the two hooks may differ there, and that +difference is deliberate. It is registered without a matcher, so it runs on startup, resume, clear and compaction alike, and a compacted conversation gets its settings back. It is its own handler rather than a branch of the drift check, because Codex budgets context per handler. -It uses POSIX `sh` with `sed` and `awk`, the drift check's toolchain, -and no `jq`. Two modes serve the skill and the no-hook path: +It uses POSIX `sh`, `sed` and `awk`, all part of a POSIX base system, +and no `jq`: unlike the drift check, whose foreign-payload branch needs +`jq` and skips itself without it, the loader has no optional +dependency. Two modes serve the skill and the no-hook path: `--print` emits the same block to standard output, uncapped — the 4 KB cap guards the hook's context budget, and a skill or a session recovering from a truncated block needs the whole of it — and @@ -501,7 +517,8 @@ tracked — and a declared instruction, "materialized by whoever first acts on it". A settings key becomes that declared instruction's named form. At the first touch of a Process directory: -1. a visible signal governs; +1. a visible signal that agrees with the key, or stands where no key + is set, governs; 2. a visible signal that contradicts the key is reported, and the developer asked which stands; nothing is changed until they answer; 3. a key with no visible signal is applied without asking — the ignored @@ -509,7 +526,10 @@ form. At the first touch of a Process directory: committed file; 4. with neither, the rule asks, as today. -`.working-process/` itself is configuration, not a Process directory: +`.working-process/` is not `.claude/working-process/`, the per-checkout +dispatch-record store; `process-settings.md` tells the two apart in one +sentence. `.working-process/` itself is configuration, not a Process +directory: its team file is committed by definition and its local file ignored by its own `.gitignore`. It is never asked about, and `process-artifacts.md` says so, so it cannot become one more first-create question. @@ -644,3 +664,23 @@ questionnaire. ## Open questions None. + +## Review rounds + +### 2026-09-28 — architect, fable 5.1, blocking (round 1, full-document) + +- hit fixed 2026-09-28 — spec used the newly banned "key list" at D6 and *The key registry*; reworded to "definition of a key" +- hit fixed 2026-09-28 — *The loader* called sh+sed+awk with no jq "the drift check's toolchain"; the drift check uses no awk and conditional jq; reworded +- hit fixed 2026-09-28 — D27's scope (one plugin, three directories) disagreed with *Verification* (four plugins, commands/ included); D27 widened +- hit fixed 2026-09-28 — the glossary's Key registry entry used its own banned term "key list"; reworded to "second definition of a key" (re-dispatch 1; its report carried CLEAN beside the hit, body governs) +- hit fixed 2026-09-28 — "its three values use the glossary's own words" was untrue for `cheapest`, which the glossary never uses; reworded to name the workflow rule as its source (re-dispatch 2, the episode's last; fix verified by grep, no third run) +- held — [Important] F1: the tier key changes the rule's behaviour, the criterion that excludes the round cap; D35 strips the card's rationale without a replacement; question: keep `dispatch.propagation-auditor-tier` in v1 with an evidence-based argument and a new card rationale, or move it to its own package?; options: (a) keep, argued on the gate-tier measurement and the developer's standing wish for a steerable tier, card rationale rewritten (recommended); (b) move D7.4/D35 out of v1 +- held — [Important] F2: the in-flow "yes, and record" write has no owner; D12 defines reading only, and the write logic would be restated in three places; question: who owns the write?; options: (a) a loader write mode `--set --scope team|personal ` owning destination, `.gitignore`, insert/replace/duplicate and the fresh-block print, the rule owning when to write (recommended); (b) the rule defines the write procedure in prose +- held — [Minor] F3: both hosts document plain stdout as context, and the spec both defers the choice to the dogfood and assumes a JSON envelope; question: plain stdout by design?; options: (a) plain stdout, the dogfood confirms (recommended); (b) JSON envelope as the drift check uses +- held — [Minor] F4: the by-hand resolution puts the loader's whole algorithm into an always-on rule for the rarest path; question: where does it live?; options: (a) the rule keeps the three-step read and `--print`, the full algorithm moves to a reference file read on the no-hook path (recommended); (b) keep it in the rule +- held — [Minor] F5: the `withdrawn ` token and the loader's withdrawn-key report have no instance in v1; question: defer them?; options: (a) keep never-rename, defer the token and its loader branch to the first retired key (recommended); (b) keep as specified +- fixed 2026-09-28 — [Minor] F6: the loader's root order differs from the drift hook's with no stated reason; license: D33 and D1 ("at the repository root"); *The loader* says the git root binds deliberately +- fixed 2026-09-28 — [Minor] F7: two `working-process` directories with different jobs; license: glossary **Dispatch record**; *Directory modes* names the record store and makes `process-settings.md` tell the two apart +- fixed 2026-09-28 — D17's first step was qualified by its second without saying so (integrity note, ungraded); license: D17; step 1 now names the signal that agrees or stands alone +- fixed 2026-09-28 — D7.3's `on` and unset both produced an offer with the difference unstated (integrity note, ungraded); license: the workflow rule's technical-design declaration; *The keys* states the three behaviours +- signal 2026-09-28 — another round earns its cost only after F1 and F2 are decided; one diff-scoped round over those changes should close the loop; the Minors alone do not justify one From 44f94f55a0ef959e4b5d020c1d02a597ad97c76f Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 17:31:16 +0200 Subject: [PATCH 103/143] docs(working-process): process-setup spec, architect round 2 --- docs/specs/2026-09-28-process-setup-design.md | 228 ++++++++++++------ 1 file changed, 155 insertions(+), 73 deletions(-) diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 2eaba16..d6dd562 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -95,12 +95,13 @@ the rule files named in *Changes by file*. - **D7.13** — `dir..superpowers`, as D7.2. - **D7.14** — `dir.docs/memory`, as D7.2; always registered, asked and read only as D25 says. -- **D8** — Keys are never renamed. A retired key stays in the registry - marked `withdrawn `, and the loader reports a withdrawn key - it meets. Argued in *The key registry*. +- **D8** — Keys are never renamed. How a retired key is marked, and how + the loader reports one, is settled with the first key actually + retired. Argued in *The key registry*. - **D9** — A SessionStart hook runs a loader that emits the effective - value and source of every registered key, plus warnings, as the - hook's additional context. Argued in *The loader*. + value and source of every registered key, plus warnings, as plain + standard output, which both hosts add to the session's context. + Argued in *The loader*. - **D10** — The loader stays silent when `.working-process/` does not exist, exits 0 on any error of its own, and caps its output at 4 KB, saying so when it truncates. Argued in *The loader*. @@ -108,9 +109,11 @@ the rule files named in *Changes by file*. matcher so it also runs on resume and compaction; it finds the registry relative to its own path and uses POSIX `sh`, `sed` and `awk`. Argued in *The loader*. -- **D12** — A new always-on rule, `process-settings.md`, is the only - definition of the files, the grammar, the block and the procedure for - reading a key. Argued in *Reading a key*. +- **D12** — A new always-on rule, `process-settings.md`, defines the + files, the grammar, the block, the three-step read, and when a write + happens; how a write happens belongs to the loader (D43), and the + full by-hand resolution to a reference file the rule points at, read + only on the no-hook path. Argued in *Reading a key*. - **D13** — A rule reads a key from the block in its context; without a trustworthy block, by the procedure D38 names; an unset key means the rule's behaviour before this change. Argued in *Reading a key*. @@ -131,8 +134,8 @@ the rule files named in *Changes by file*. - **D18** — `.working-process/` is not a Process directory: it is never asked about, and the rule says so. Argued in *Directory modes*. - **D19** — When a standing question is asked in ordinary work, its - answers include "yes, and record", which writes the answer to the - file its scope names. There is no separate "record it?" question. + answers include "yes, and record", which writes the answer, through + the loader's `--set`, to the file its scope names. There is no separate "record it?" question. Anyone may record a team key this way; the write names the team file. Argued in *Recording an answer*. - **D20** — A `CLAUDE.md` note that declares a key's value keeps binding @@ -149,9 +152,10 @@ the rule files named in *Changes by file*. signals as candidates: directory signals as certain, prose notes in `CLAUDE.md` as the model's reading, to be confirmed. It never deletes a `CLAUDE.md` note without consent. Argued in *Migration*. -- **D24** — The skill writes `.working-process/.gitignore` holding the - single line `settings.local.md` at its first write. Argued in *The - skill*. +- **D24** — The first personal answer written creates + `.working-process/.gitignore` holding the single line + `settings.local.md`, beside the personal file; the loader's `--set` + writes it (D43). Argued in *The loader*. - **D25** — The skill asks about `dir.docs/memory` only when the project-memory plugin is installed, and the project-memory rule reads that key only when the working-process rules are installed. Argued in @@ -165,8 +169,9 @@ the rule files named in *Changes by file*. registered, that defaults validate, and that names are unique. Argued in *Verification*. - **D28** — Repository tests run the loader on fixtures for precedence, suggestions, invalid and duplicate values, unknown keys, - the worktree fallback, the size cap, silence and error exit. Argued - in *Verification*. + the worktree fallback, the size cap, silence and error exit, and + `--set`'s insert, replace, duplicate report, destination and + `.gitignore`. Argued in *Verification*. - **D29** — A Claude Code dogfood run proves the block reaches a new session, a recorded question is not asked, and the block returns after compaction. Argued in *Verification*. @@ -186,20 +191,23 @@ the rule files named in *Changes by file*. exits non-zero with its warnings when the file would not validate. Argued in *The loader*. - **D35** — The propagation-auditor card stops prescribing the cheapest - family and accepts the tier the dispatcher resolved, still reporting - its family for the dispatcher's comparison. Argued in *Reading a key*. + family and accepts the tier the dispatcher resolved from + `dispatch.propagation-auditor-tier`, whose default stays `cheapest`; + it still reports its family for the dispatcher's comparison. Argued in + *Reading a key*. - **D36** — Every line shaped like a key — a dotted name and a colon at the start of a line, outside a fence — is a settings line, validated, and diagnosed when its value is invalid; it is never commentary. Argued in *The settings files*. - **D37** — A write inserts an absent key, replaces a present one, and - on a duplicate asks which line stands; after writing, the writer - prints the fresh block, which supersedes the old one for the rest of - the session. Argued in *The skill*. + on a duplicate stops and reports both lines, leaving the choice to + the developer; after writing, it prints the fresh block, which + supersedes the old one for the rest of the session. Argued in *The + loader*. - **D38** — Without a trustworthy block — none, or one marked incomplete — a session resolves keys by the loader's own procedure, - through `--print` or by applying the rule's full resolution by hand. - Argued in *Reading a key*. + through `--print` or, where the script is unreachable, by the + reference file's full resolution. Argued in *Reading a key*. - **D39** — The skill asks about directory exceptions only when the developer asks for them. Argued in *The skill*. - **D40** — The python and salesforce review commands and the @@ -217,6 +225,14 @@ the rule files named in *Changes by file*. worktree only, as the skill says. Argued in *Scope and precedence*. - **D42** — `--print` is uncapped; only the hook's output carries the 4 KB cap. Argued in *The loader*. +- **D43** — The loader owns every write through + `--set --scope team|personal `: the destination (D41), + the `.gitignore` beside a personal file, insertion, replacement and + duplicate detection (D37), validation, and the fresh block printed on + success, validating before it writes; `--set --dry-run` computes the + same write and applies nothing, which the skill's preview uses. The + skill, a "yes, and record" answer and migration all call it; none + writes a settings file directly. Argued in *The loader*. ## The settings files @@ -250,7 +266,7 @@ than read past. So `review.autonomy: "no"` below `review.autonomy: yes` is a duplicate, and the key is unset; it cannot hide as commentary and leave the earlier consent standing. Fences open and close on lines starting with three backticks; leading whitespace makes a line -commentary. Every other line is commentary, and the skill writes each +commentary. Every other line is commentary, and `--set` writes each key's question as a comment line above it, so the file explains itself. The grammar follows the one precedent in the marketplace, the `trigger-framework:` declaration of salesforce-standards: one line, one @@ -282,9 +298,9 @@ reads the main checkout's, whose path is the first entry of parent of `git rev-parse --git-common-dir` is not a checkout path in every layout, so it is not used.) One set of personal answers then serves every worktree of a repository. A personal answer is written -where the loader reads it: the skill, run in a worktree, writes to the -main checkout's personal file and puts the `.gitignore` beside it, and -a "yes, and record" answer goes to the same place, so a worktree never +where the loader reads it: `--set`, called from a worktree by the skill +or by a "yes, and record" answer alike, writes to the main checkout's +personal file and puts the `.gitignore` beside it, so a worktree never gains a file of its own through a write. A personal file created in a worktree by hand replaces the fallback wholly — no per-key merge — and the loader's first line says which file it read; a write made from that @@ -314,9 +330,9 @@ manifest follows. The skill takes its questions from the registry, the loader takes its validation from it, and the rules name keys and nothing more; no second definition of a key exists to drift. A key is never renamed: renaming would -silently orphan every file that set it. A retired key keeps its entry -with `withdrawn `, and the loader reports it as withdrawn -rather than unknown. The registry's exact path and heading shape are the +silently orphan every file that set it. Version one retires no key, so +the marker for a retired one and the loader's report of it wait for the +first key that is retired. The registry's exact path and heading shape are the plan's to fix. ## The keys @@ -360,9 +376,10 @@ leaves today's behaviour, where only a manifest raises the offer; `off` suppresses it. The gate's value is a tier in the glossary's sense — a relative rung, -never a model name. `mid` and `most-capable` are the glossary's own -words for those rungs; `cheapest` is the workflow rule's, which -prescribes "the cheapest available family" for this agent today. Each host resolves the tier at dispatch through a table +never a model name. `mid` is the glossary's own word for its rung, and +`most-capable` shortens the glossary's "most capable available"; +`cheapest` is the workflow rule's, which prescribes "the cheapest +available family" for this agent today. Each host resolves the tier at dispatch through a table of its own; version one ships only Claude Code's (Haiku for `cheapest`, the family one below the most capable for `mid`, the most capable available for `most-capable`). A Codex port adds its table; Codex's @@ -376,7 +393,9 @@ Deliberately not keys in version one: process weight, whose readers do not exist yet; the rules install level and commit mode, whose answer the rules manifest's position already records; the review loop's round cap, which stays fixed by the rule — making it a key would change the -rule's behaviour, not only where an answer is recorded; and every +rule's behaviour, not only where an answer is recorded, and unlike the +gate's tier (*Reading a key*) no evidence argues for that change; and +every per-document artifact (stamps, chain debt, `ticket:`), which has its own home. @@ -400,7 +419,7 @@ hooks' context, whose order is undocumented, and understand it without the rule. Every registered key is listed, set or not, so a rule has one place to read and never needs the registry. Warnings live in the block, because nobody reads a hook's stderr: unknown keys, invalid values, -duplicates, a key in the wrong scope's file, a withdrawn key. +duplicates, a key in the wrong scope's file. The output is capped at 4 KB. That is a byte cap, not a token guarantee: Codex's documented per-handler threshold is about 2,500 @@ -409,8 +428,11 @@ and 4 KB of ordinary output sits under it with room to spare. The dogfood checks it rather than the spec promising it. Where the cap truncates, the last line says the block is incomplete; it never truncates silently. A block for the version-one keys runs to about -1 KB. Paths and warnings in the block are escaped as JSON requires -when the hook emits its JSON envelope. +1 KB. The block is plain standard output, which Claude Code adds to a +SessionStart session's context and Codex adds as developer context; no +JSON envelope wraps it, so an arbitrary root path or warning needs no +escaping — the constraint the drift check lives under, whose messages +may carry no quotes or backslashes, never reaches the loader. The loader stays silent — no output at all — when `.working-process/` does not exist, so a project that never adopted the settings pays @@ -439,14 +461,35 @@ cap guards the hook's context budget, and a skill or a session recovering from a truncated block needs the whole of it — and `--validate --scope team|personal ` checks a candidate file against the scope it is meant for, printing its warnings and exiting -non-zero when the file would not validate. Only the hook mode swallows -its errors and exits 0; the other two report failure. +non-zero when the file would not validate. A third mode writes: +`--set --scope team|personal ` resolves the destination — +in a worktree the main checkout's personal file, in a bare-repository +worktree its own (*Scope and precedence*) — creates the file and, for a +personal file, the `.gitignore` beside it; inserts an absent key under +its question as a comment, replaces a present key's line in place, and +on a duplicate changes nothing and reports both lines with a non-zero +exit, so the caller asks which stands; validates the result against +the key's scope before anything is written, so no `--set` ever leaves +a file that would not validate; and on success prints the fresh +block. `--set --dry-run` resolves the same destination and prints what +the write would do — the file, the line it would insert or replace, the +`.gitignore` it would create, and the shadow warning where a +hand-made worktree file would hide the result — and writes nothing. Every other +line and comment stays untouched, and an unchanged file is not +rewritten. Writing lives in one place because its destination logic is +exactly what drifts when three writers restate it. Only the hook mode +swallows its errors and exits 0; the other three report failure. ## Reading a key -A new always-on rule, `process-settings.md`, is the one definition of -the files, their grammar, the block and the reading procedure. The -rules that read a key cite it and restate none of it. +A new always-on rule, `process-settings.md`, defines the files, their +grammar, the block, the three-step read below, and when a write +happens. The rules that read a key cite it and restate none of it. It +stays short because every session and every dispatched agent loads it: +the full by-hand resolution — needed only where no hook ran and the +loader cannot be reached — lives in a reference file beside the rule, +read on that path alone, and how a write happens belongs to the loader +(*The loader*). To read a key, a session: @@ -455,10 +498,10 @@ To read a key, a session: 2. without a trustworthy block — none arrived because hooks are disabled or a Codex plugin is not yet trusted, or the block says it is incomplete — resolves the key by the loader's own procedure: - running `--print` where the script is reachable, otherwise applying - `process-settings.md`'s full resolution by hand — scope, the - worktree fallback, defaults, duplicates and invalid values — never a - shortcut through the files; + running `--print` where the script is reachable, otherwise following + the full resolution — scope, the worktree fallback, defaults, + duplicates and invalid values — in a reference file the rule points + at, never a shortcut through the files; 3. where the key is unset, behaves as the rule did before this change: the question is asked, or the offer made, exactly as today. @@ -476,7 +519,26 @@ One card changes all the same: the propagation auditor's states that it runs on the cheapest family and treats an upward mismatch as a fault, which would contradict a team that set a higher tier. It instead accepts the tier its dispatcher resolved and still opens its report -with its family, so the dispatcher's comparison keeps working. +with its family, so the dispatcher's comparison keeps working. The +card's argument for the cheap family — that its duties are procedural +— becomes the argument for the default, and the card says a project +may choose otherwise. Its second clause, that "an over-tier run does +this work casually badly — a false clean line would then feed the +integrity gate unnoticed", is struck: the evidence below records the +opposite, and a card keeping it would call the evidence-backed choice +the dangerous one. + +This key does change the rule's behaviour where a project sets it, +which is the ground on which the round cap stays out of version one. It +enters anyway, on evidence rather than on "someone may want it": the +workflow rule already records that a report carrying hits beside +`CLEAN` "has happened more than once on the cheapest family", and a +measurement on 2026-09-28, over two states of another project's design +spec and technical design that a later integrity audit found defective, +had the cheapest family return `CLEAN` in all four of its runs, while +one tier up located real defects in every one of its runs. A project that +has seen that must be able to move its gate without editing the +plugin. The round cap has no such evidence behind it. ## Changes to the rules @@ -507,7 +569,7 @@ in those sentences becomes the key: prior decision is present" gains the conflict question: a visible signal that contradicts the key is reported and the developer asked. - `agents/propagation-auditor.md` — the tier clause, as *Reading a key* - says. + says, including the struck over-tier sentence. ## Directory modes @@ -537,12 +599,15 @@ says so, so it cannot become one more first-create question. ## Recording an answer When a standing question is asked in ordinary work, its answers gain -one option: "yes, and record" (or "no, and record"). Choosing it writes -the answer to the file the key's scope names, creating the file and its -`.gitignore` where they are missing. There is no second question: the +one option: "yes, and record" (or "no, and record"). Choosing it runs +the loader's `--set`, which writes the answer to the file the key's +scope names and prints the fresh block. The rule decides when a write +happens; the loader decides how. There is no second question: the developer is interrupted once, as the review loop's one-batch-per-round contract intends. A session never records an answer the developer did -not choose to record. +not choose to record. Where `--set` cannot run — the loader unreachable +on the no-hook path — the option is withheld: the answer holds for the +session, and the session says it was not recorded. Anyone may record a team key this way, not only whoever ran the setup. The team file is an ordinary committed file, so the change shows in the @@ -561,7 +626,8 @@ On its first run the skill gathers the existing answers as candidates. Directory signals are mechanical and shown as settled. Prose notes are not parseable, so the skill offers its reading of each as the model's reading, with the note's location, for the developer to confirm. It -never deletes a note without consent; after a confirmed move it offers +never deletes a note without consent; a confirmed move is a `--set`, +and after it the skill offers to remove the note that now duplicates the key. ## The skill @@ -579,20 +645,15 @@ nudges them to run it. A run: are not unset answers but optional ones: the skill asks `dir.default` and offers exceptions only when the developer asks for them; -3. previews the change to each file, validates it with - `--validate --scope`, and writes: an absent key is added, under its - question as a comment; a present key's line is replaced in place; a - duplicated key is shown with both lines and the developer chooses - which stands. Every other line and comment stays untouched, and an - unchanged file is not written. After writing, it prints the fresh - block, which supersedes the one from session start for the rest of - the session — the same holds for a "yes, and record" answer; +3. previews each answer with `--set --dry-run`, which also shows the + `.gitignore` it would create beside a personal file, then writes it + with `--set`; a duplicate `--set` reports is shown with both lines + and the developer chooses which stands. The fresh block `--set` + prints supersedes the one from session start for the rest of the + session; 4. materializes a declared mode in each Process directory that already exists and does not contradict it, and reports every contradiction; it creates no directory; -5. writes the `.gitignore` holding `settings.local.md` beside the - personal file it first writes, which in a worktree is the main - checkout's (D41). A second run shows the same table and asks only about what is unset or what the developer wants to change. It never replays the whole @@ -614,12 +675,15 @@ questionnaire. file's value; a duplicate key leaving it unset; an unknown key warned about; the worktree fallback, on a real `git worktree` in a temporary directory; the 4 KB cap and its truncation line; silence without - `.working-process/`; silence and exit 0 on an error of its own. + `.working-process/`; silence and exit 0 on an error of its own; + `--set` inserting an absent key, replacing a present one, refusing a + duplicate with both lines reported, writing a personal answer to the + main checkout from a worktree with its `.gitignore`, and to the + worktree's own file in a bare-repository layout. - **Claude Code dogfood**, with the plugin loaded from the checkout: the skill records settings; a new session receives the block; a recorded question is not asked; after `/compact` the block is back. The run - also settles whether the SessionStart hook takes plain standard output, - the JSON `additionalContext` shape the drift check uses, or both. + confirms that plain standard output reaches the session. - **Codex check**: in a scratch project, a repository-level `.codex/hooks.json` runs the same loader; the block reaches the model once the hook is trusted, and again after compaction or resume. No @@ -643,8 +707,10 @@ questionnaire. ## Changes by file -- New: the `process-setup` skill; the key registry; the loader script; - the `process-settings.md` rule; a second SessionStart handler in +- New: the `process-setup` skill; the key registry; the loader script + with its `--print`, `--validate` and `--set` modes; the + `process-settings.md` rule and the reference file holding its full + by-hand resolution; a second SessionStart handler in `hooks/hooks.json`. - `rules/workflow.md`, `rules/spec-plan-lifecycle.md`, `rules/process-artifacts.md` — as *Changes to the rules* and @@ -667,6 +733,18 @@ None. ## Review rounds +### 2026-09-28 — architect, fable 5.1, blocking (round 2, diff-scoped) + +- held — [Important] F8: the reference file "beside the rule" lands in the Rules payload, which Claude Code loads unconditionally, undoing F4; question: where does it live?; options: (a) a path-scoped rule in the payload with `paths: [".working-process/**"]`, loading exactly when a session reads the settings by hand (recommended); (b) outside `rules/`, under the skill or beside the loader, named by path from the rule +- held — [Important] F9: after `--set` refuses a duplicate, the write that enacts the developer's choice has no owner, and the in-flow path needs a second question; question: how is a duplicate resolved?; options: (a) `--set` replaces every line for the key with the value it writes and reports what it removed, the fresh answer being the resolution (recommended); (b) keep the refusal and add a loader mode that writes the chosen line +- fixed 2026-09-28 — [Important] F10: the skill's preview needed the write's destination logic, which only `--set` holds; license: D21 and D43 ("none writes a settings file directly"); `--set --dry-run` added to *The loader* and D43, *The skill* step 3 previews through it, step 5 folded in +- held — [Minor] F11: `--set --scope` duplicates the registry's fixed scope with no stated purpose; question: what is `--scope` for?; options: (a) drop it — the registry picks the file, and a team suggestion for a personal key is written by hand (recommended); (b) keep it solely for writing a suggestion into the team file, any other mismatch refused +- fixed 2026-09-28 — [Minor] F11 (part): validation order unstated; license: D43 ("validation"); `--set` validates before anything is written +- fixed 2026-09-28 — [Minor] F12: the card's over-tier clause survived into the default's argument though the spec's own evidence contradicts it; license: the round-1 F1 ruling; *Reading a key* and *Changes to the rules* strike it +- fixed 2026-09-28 — [Minor] F13: "yes, and record" had no behaviour where the loader is unreachable; license: D43; *Recording an answer* withholds the option and says the answer holds for the session only +- fixed 2026-09-28 — the key-exclusion criterion was absolute in *The keys* and qualified in *Reading a key* (integrity note, ungraded); license: the round-1 F1 ruling; *The keys* now carries the qualified form +- signal 2026-09-28 — one more diff-scoped round earns its cost: F8, F9 and F10 change contracts; if that wave lands as suggested the round after should close the loop; the leftovers are worth nothing alone + ### 2026-09-28 — architect, fable 5.1, blocking (round 1, full-document) - hit fixed 2026-09-28 — spec used the newly banned "key list" at D6 and *The key registry*; reworded to "definition of a key" @@ -674,13 +752,17 @@ None. - hit fixed 2026-09-28 — D27's scope (one plugin, three directories) disagreed with *Verification* (four plugins, commands/ included); D27 widened - hit fixed 2026-09-28 — the glossary's Key registry entry used its own banned term "key list"; reworded to "second definition of a key" (re-dispatch 1; its report carried CLEAN beside the hit, body governs) - hit fixed 2026-09-28 — "its three values use the glossary's own words" was untrue for `cheapest`, which the glossary never uses; reworded to name the workflow rule as its source (re-dispatch 2, the episode's last; fix verified by grep, no third run) -- held — [Important] F1: the tier key changes the rule's behaviour, the criterion that excludes the round cap; D35 strips the card's rationale without a replacement; question: keep `dispatch.propagation-auditor-tier` in v1 with an evidence-based argument and a new card rationale, or move it to its own package?; options: (a) keep, argued on the gate-tier measurement and the developer's standing wish for a steerable tier, card rationale rewritten (recommended); (b) move D7.4/D35 out of v1 -- held — [Important] F2: the in-flow "yes, and record" write has no owner; D12 defines reading only, and the write logic would be restated in three places; question: who owns the write?; options: (a) a loader write mode `--set --scope team|personal ` owning destination, `.gitignore`, insert/replace/duplicate and the fresh-block print, the rule owning when to write (recommended); (b) the rule defines the write procedure in prose -- held — [Minor] F3: both hosts document plain stdout as context, and the spec both defers the choice to the dogfood and assumes a JSON envelope; question: plain stdout by design?; options: (a) plain stdout, the dogfood confirms (recommended); (b) JSON envelope as the drift check uses -- held — [Minor] F4: the by-hand resolution puts the loader's whole algorithm into an always-on rule for the rarest path; question: where does it live?; options: (a) the rule keeps the three-step read and `--print`, the full algorithm moves to a reference file read on the no-hook path (recommended); (b) keep it in the rule -- held — [Minor] F5: the `withdrawn ` token and the loader's withdrawn-key report have no instance in v1; question: defer them?; options: (a) keep never-rename, defer the token and its loader branch to the first retired key (recommended); (b) keep as specified +- fixed 2026-09-28 — [Important] F1: the tier key changes the rule's behaviour, the criterion that excludes the round cap; D35 strips the card's rationale without a replacement; ruling: 2026-09-28; the key stays, argued in *Reading a key* on the recorded CLEAN-beside-hits history and the gate-tier measurement; D35 turns the card's cheap-family argument into the default's argument +- fixed 2026-09-28 — [Important] F2: the in-flow "yes, and record" write has no owner; ruling: 2026-09-28; new D43, the loader's `--set` owns every write; D12, D19, D37, *Recording an answer*, *The skill* and *Migration* call it +- fixed 2026-09-28 — [Minor] F3: plain stdout or a JSON envelope left undecided; ruling: 2026-09-28; D9 and *The loader* choose plain stdout, the dogfood confirms +- fixed 2026-09-28 — [Minor] F4: the by-hand resolution in an always-on rule; ruling: 2026-09-28; D12, D38 and *Reading a key* move it to a reference file read on the no-hook path +- fixed 2026-09-28 — [Minor] F5: the `withdrawn` marker and its loader report have no v1 instance; ruling: 2026-09-28; D8 and *The key registry* defer both to the first retired key; never-rename stays - fixed 2026-09-28 — [Minor] F6: the loader's root order differs from the drift hook's with no stated reason; license: D33 and D1 ("at the repository root"); *The loader* says the git root binds deliberately - fixed 2026-09-28 — [Minor] F7: two `working-process` directories with different jobs; license: glossary **Dispatch record**; *Directory modes* names the record store and makes `process-settings.md` tell the two apart - fixed 2026-09-28 — D17's first step was qualified by its second without saying so (integrity note, ungraded); license: D17; step 1 now names the signal that agrees or stands alone - fixed 2026-09-28 — D7.3's `on` and unset both produced an offer with the difference unstated (integrity note, ungraded); license: the workflow rule's technical-design declaration; *The keys* states the three behaviours +- hit fixed 2026-09-28 — D24 still had the skill write the personal `.gitignore`, which D43 gives to `--set`; D24 now names `--set` +- hit fixed 2026-09-28 — *The settings files* had the skill write each key's question comment; now `--set` +- hit fixed 2026-09-28 — *Scope and precedence* had the skill write in a worktree and place the `.gitignore`; now `--set`, called by the skill or a "yes, and record" answer +- hit fixed 2026-09-28 — "`mid` and `most-capable` are the glossary's own words" overclaimed for `most-capable`, which shortens "most capable available"; reworded (re-dispatch 1; the run self-reported haiku while its transcript metadata shows claude-sonnet-5, the dispatched model) - signal 2026-09-28 — another round earns its cost only after F1 and F2 are decided; one diff-scoped round over those changes should close the loop; the Minors alone do not justify one From 64e486c06fa8a4850c49e0fdfb44e3f5a5395dc1 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 17:49:33 +0200 Subject: [PATCH 104/143] docs(working-process): process-setup spec, architect round 3 --- docs/specs/2026-09-28-process-setup-design.md | 109 ++++++++++++------ 1 file changed, 76 insertions(+), 33 deletions(-) diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index d6dd562..1d02e62 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -3,7 +3,7 @@ ticket: none date: 2026-09-28 status: draft grilled: 2026-09-28 -architect: blocking +architect: concerns decisions: registered branch: feature/process-setup base: develop @@ -112,8 +112,10 @@ the rule files named in *Changes by file*. - **D12** — A new always-on rule, `process-settings.md`, defines the files, the grammar, the block, the three-step read, and when a write happens; how a write happens belongs to the loader (D43), and the - full by-hand resolution to a reference file the rule points at, read - only on the no-hook path. Argued in *Reading a key*. + full by-hand resolution to a second rule, `process-settings-resolution.md`, + path-scoped to `.working-process/**` so it loads only when a session + reads the settings by hand; `process-settings.md` names it and tells + the by-hand path to open it first. Argued in *Reading a key*. - **D13** — A rule reads a key from the block in its context; without a trustworthy block, by the procedure D38 names; an unset key means the rule's behaviour before this change. Argued in *Reading a key*. @@ -171,7 +173,8 @@ the rule files named in *Changes by file*. precedence, suggestions, invalid and duplicate values, unknown keys, the worktree fallback, the size cap, silence and error exit, and `--set`'s insert, replace, duplicate report, destination and - `.gitignore`. Argued in *Verification*. + `.gitignore`, and `--set --dry-run` writing nothing. Argued in + *Verification*. - **D29** — A Claude Code dogfood run proves the block reaches a new session, a recorded question is not asked, and the block returns after compaction. Argued in *Verification*. @@ -200,14 +203,16 @@ the rule files named in *Changes by file*. and diagnosed when its value is invalid; it is never commentary. Argued in *The settings files*. - **D37** — A write inserts an absent key, replaces a present one, and - on a duplicate stops and reports both lines, leaving the choice to - the developer; after writing, it prints the fresh block, which + on a duplicate replaces every line for the key with the value it + writes and reports the lines it removed — the fresh answer is the + resolution; after writing, it prints the fresh block, which supersedes the old one for the rest of the session. Argued in *The loader*. - **D38** — Without a trustworthy block — none, or one marked incomplete — a session resolves keys by the loader's own procedure, - through `--print` or, where the script is unreachable, by the - reference file's full resolution. Argued in *Reading a key*. + through `--print` or, where the script is unreachable, by the full + resolution in `process-settings-resolution.md`. Argued in *Reading a + key*. - **D39** — The skill asks about directory exceptions only when the developer asks for them. Argued in *The skill*. - **D40** — The python and salesforce review commands and the @@ -225,14 +230,17 @@ the rule files named in *Changes by file*. worktree only, as the skill says. Argued in *Scope and precedence*. - **D42** — `--print` is uncapped; only the hook's output carries the 4 KB cap. Argued in *The loader*. -- **D43** — The loader owns every write through - `--set --scope team|personal `: the destination (D41), +- **D43** — The loader owns every write of an answer through + `--set `, the registry choosing the file by the key's + scope: the destination (D41), the `.gitignore` beside a personal file, insertion, replacement and duplicate detection (D37), validation, and the fresh block printed on success, validating before it writes; `--set --dry-run` computes the same write and applies nothing, which the skill's preview uses. The skill, a "yes, and record" answer and migration all call it; none - writes a settings file directly. Argued in *The loader*. + writes a settings file directly; the one hand-written line is a team's + suggestion for a personal key, which `--validate --scope team` + warns about and still passes. Argued in *The loader*. ## The settings files @@ -461,20 +469,30 @@ cap guards the hook's context budget, and a skill or a session recovering from a truncated block needs the whole of it — and `--validate --scope team|personal ` checks a candidate file against the scope it is meant for, printing its warnings and exiting -non-zero when the file would not validate. A third mode writes: -`--set --scope team|personal ` resolves the destination — +non-zero when the file would not validate. A warning alone fails +nothing: a personal key in the team file is reported as a suggestion +and the file still validates, which is the check a hand-written +suggestion gets. A third mode writes: +`--set ` takes no scope argument — the registry fixes each +key's scope, so the file follows from the key — and resolves the +destination — in a worktree the main checkout's personal file, in a bare-repository worktree its own (*Scope and precedence*) — creates the file and, for a personal file, the `.gitignore` beside it; inserts an absent key under its question as a comment, replaces a present key's line in place, and -on a duplicate changes nothing and reports both lines with a non-zero -exit, so the caller asks which stands; validates the result against -the key's scope before anything is written, so no `--set` ever leaves +on a duplicate replaces every line for that key with the one it writes +and reports the lines it removed — the developer has just answered the +question the duplicate caused, so that answer resolves it without a +second question. The written line takes the first duplicate's place; +each later duplicate goes, with the question comment directly above +it, the one exception to leaving every other line untouched; validates the result against the key's registry scope +before anything is written, so no `--set` ever leaves a file that would not validate; and on success prints the fresh block. `--set --dry-run` resolves the same destination and prints what the write would do — the file, the line it would insert or replace, the -`.gitignore` it would create, and the shadow warning where a -hand-made worktree file would hide the result — and writes nothing. Every other +duplicate lines it would remove, the `.gitignore` it would create, and +the shadow warning where a hand-made worktree file would hide the +result — and writes nothing. Every other line and comment stays untouched, and an unchanged file is not rewritten. Writing lives in one place because its destination logic is exactly what drifts when three writers restate it. Only the hook mode @@ -487,9 +505,18 @@ grammar, the block, the three-step read below, and when a write happens. The rules that read a key cite it and restate none of it. It stays short because every session and every dispatched agent loads it: the full by-hand resolution — needed only where no hook ran and the -loader cannot be reached — lives in a reference file beside the rule, -read on that path alone, and how a write happens belongs to the loader -(*The loader*). +loader cannot be reached — lives in a second rule of the payload, +`process-settings-resolution.md`, scoped by `paths:` to +`.working-process/**`. The Rules engine copies every rule file and +Claude Code loads a rule without `paths:` at every launch, so a file +merely "beside the rule" would be always-on; path-scoped, it loads +exactly when a session reads a settings file by hand, which is the +by-hand path and nothing else — the shape `process-artifacts.md` +already uses. The trigger is a file read through the Read tool, not a +`cat` in a shell, so `process-settings.md` also names the file and +tells a session on the by-hand path to open it before touching the +settings, and correctness does not depend on which tool reads them. +How a write happens belongs to the loader (*The loader*). To read a key, a session: @@ -500,8 +527,8 @@ To read a key, a session: is incomplete — resolves the key by the loader's own procedure: running `--print` where the script is reachable, otherwise following the full resolution — scope, the worktree fallback, defaults, - duplicates and invalid values — in a reference file the rule points - at, never a shortcut through the files; + duplicates and invalid values — in `process-settings-resolution.md`, + never a shortcut through the files; 3. where the key is unset, behaves as the rule did before this change: the question is asked, or the offer made, exactly as today. @@ -647,14 +674,18 @@ nudges them to run it. A run: them; 3. previews each answer with `--set --dry-run`, which also shows the `.gitignore` it would create beside a personal file, then writes it - with `--set`; a duplicate `--set` reports is shown with both lines - and the developer chooses which stands. The fresh block `--set` - prints supersedes the one from session start for the rest of the + with `--set`; where the preview shows a duplicate, the lines the + write would remove are shown before it is applied. The fresh block + `--set` prints supersedes the one from session start for the rest of the session; 4. materializes a declared mode in each Process directory that already exists and does not contradict it, and reports every contradiction; it creates no directory; +A team's suggestion for a personal key is the one settings line +`--set` never writes: it goes into the team file by hand, and the +loader shows it as a suggestion. + A second run shows the same table and asks only about what is unset or what the developer wants to change. It never replays the whole questionnaire. @@ -676,8 +707,11 @@ questionnaire. about; the worktree fallback, on a real `git worktree` in a temporary directory; the 4 KB cap and its truncation line; silence without `.working-process/`; silence and exit 0 on an error of its own; - `--set` inserting an absent key, replacing a present one, refusing a - duplicate with both lines reported, writing a personal answer to the + `--set` inserting an absent key, replacing a present one, replacing + a duplicated key's every line and reporting the removed ones, + `--set --dry-run` printing the destination, the line, the lines a + duplicate would lose, the `.gitignore` and the shadow warning while + leaving every file byte-identical, writing a personal answer to the main checkout from a worktree with its `.gitignore`, and to the worktree's own file in a bare-repository layout. - **Claude Code dogfood**, with the plugin loaded from the checkout: the @@ -709,8 +743,8 @@ questionnaire. - New: the `process-setup` skill; the key registry; the loader script with its `--print`, `--validate` and `--set` modes; the - `process-settings.md` rule and the reference file holding its full - by-hand resolution; a second SessionStart handler in + always-on `process-settings.md` rule and the path-scoped + `process-settings-resolution.md` holding the full by-hand resolution; a second SessionStart handler in `hooks/hooks.json`. - `rules/workflow.md`, `rules/spec-plan-lifecycle.md`, `rules/process-artifacts.md` — as *Changes to the rules* and @@ -733,16 +767,25 @@ None. ## Review rounds +### 2026-09-28 — architect, fable 5.1, concerns (round 3, diff-scoped) + +- fixed 2026-09-28 — [Minor] F14: D43 stayed absolute after the F11 ruling made the suggestion hand-written, and `--validate` was not said to pass it; license: the round-2 F11 ruling; D43 covers every write of an answer and names the exception, *The loader* says a warning alone fails nothing +- fixed 2026-09-28 — [Minor] F15: a duplicate's rewrite had no stated shape; license: D37 and *The settings files* ("so the file explains itself"); the line takes the first duplicate's place, later duplicates go with their question comments +- fixed 2026-09-28 — [Minor] F16: the path-scoped rule loads on a Read, not a shell `cat`, and the pointer from `process-settings.md` was lost in the F8 rewrite; license: the round-2 F8 ruling; D12 and *Reading a key* restore the pointer +- signal 2026-09-28 — a further round would not earn its cost; land the three Minors and let the consumption-gate integrity audit read the whole + ### 2026-09-28 — architect, fable 5.1, blocking (round 2, diff-scoped) -- held — [Important] F8: the reference file "beside the rule" lands in the Rules payload, which Claude Code loads unconditionally, undoing F4; question: where does it live?; options: (a) a path-scoped rule in the payload with `paths: [".working-process/**"]`, loading exactly when a session reads the settings by hand (recommended); (b) outside `rules/`, under the skill or beside the loader, named by path from the rule -- held — [Important] F9: after `--set` refuses a duplicate, the write that enacts the developer's choice has no owner, and the in-flow path needs a second question; question: how is a duplicate resolved?; options: (a) `--set` replaces every line for the key with the value it writes and reports what it removed, the fresh answer being the resolution (recommended); (b) keep the refusal and add a loader mode that writes the chosen line +- fixed 2026-09-28 — [Important] F8: the reference file beside the rule would load at every launch; ruling: 2026-09-28; D12, D38, *Reading a key* and *Changes by file* make it `process-settings-resolution.md`, path-scoped to `.working-process/**` +- fixed 2026-09-28 — [Important] F9: a refused duplicate left the resolving write unowned and forced a second question; ruling: 2026-09-28; D37, *The loader*, *The skill* and the loader tests: `--set` replaces every line for the key and reports the removed ones - fixed 2026-09-28 — [Important] F10: the skill's preview needed the write's destination logic, which only `--set` holds; license: D21 and D43 ("none writes a settings file directly"); `--set --dry-run` added to *The loader* and D43, *The skill* step 3 previews through it, step 5 folded in -- held — [Minor] F11: `--set --scope` duplicates the registry's fixed scope with no stated purpose; question: what is `--scope` for?; options: (a) drop it — the registry picks the file, and a team suggestion for a personal key is written by hand (recommended); (b) keep it solely for writing a suggestion into the team file, any other mismatch refused +- fixed 2026-09-28 — [Minor] F11: `--set --scope` duplicated the registry's scope; ruling: 2026-09-28; `--set ` takes no scope, the registry picks the file, and a team suggestion for a personal key is written by hand (*The skill*) - fixed 2026-09-28 — [Minor] F11 (part): validation order unstated; license: D43 ("validation"); `--set` validates before anything is written - fixed 2026-09-28 — [Minor] F12: the card's over-tier clause survived into the default's argument though the spec's own evidence contradicts it; license: the round-1 F1 ruling; *Reading a key* and *Changes to the rules* strike it - fixed 2026-09-28 — [Minor] F13: "yes, and record" had no behaviour where the loader is unreachable; license: D43; *Recording an answer* withholds the option and says the answer holds for the session only - fixed 2026-09-28 — the key-exclusion criterion was absolute in *The keys* and qualified in *Reading a key* (integrity note, ungraded); license: the round-1 F1 ruling; *The keys* now carries the qualified form +- hit fixed 2026-09-28 — *The loader*'s list of what `--set --dry-run` prints lacked the duplicate lines *The skill* step 3 now previews; added +- hit fixed 2026-09-28 — `--set --dry-run` had no test in D28 or *Verification*; both now name it - signal 2026-09-28 — one more diff-scoped round earns its cost: F8, F9 and F10 change contracts; if that wave lands as suggested the round after should close the loop; the leftovers are worth nothing alone ### 2026-09-28 — architect, fable 5.1, blocking (round 1, full-document) From d43ae0ab44979685eb45e1c4a9a9739142f1df16 Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 17:51:39 +0200 Subject: [PATCH 105/143] docs(working-process): process-setup spec, architect loop closed --- docs/specs/2026-09-28-process-setup-design.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 1d02e62..634454e 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -3,7 +3,7 @@ ticket: none date: 2026-09-28 status: draft grilled: 2026-09-28 -architect: concerns +architect: concerns (resolved 2026-09-28) decisions: registered branch: feature/process-setup base: develop @@ -767,6 +767,14 @@ None. ## Review rounds +### Loop closed — 2026-09-28 + +Resolved without a fresh architect round, on the developer's choice +after round 3's stop signal: round 3 returned three Minor findings, all +fixed under cited licenses (F14–F16 above), and the architect judged a +further round not worth its cost. The consumption gate's integrity +audit reads the whole document next. + ### 2026-09-28 — architect, fable 5.1, concerns (round 3, diff-scoped) - fixed 2026-09-28 — [Minor] F14: D43 stayed absolute after the F11 ruling made the suggestion hand-written, and `--validate` was not said to pass it; license: the round-2 F11 ruling; D43 covers every write of an answer and names the exception, *The loader* says a warning alone fails nothing From cfc9b9a0209c253683479047e13406964ab1c2ea Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 19:15:35 +0200 Subject: [PATCH 106/143] docs(working-process): process-setup spec, integrity audit dispositions --- docs/specs/2026-09-28-process-setup-design.md | 222 ++++++++++++------ 1 file changed, 144 insertions(+), 78 deletions(-) diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 634454e..04030a1 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -56,8 +56,9 @@ the rule files named in *Changes by file*. (team, committed) and `.working-process/settings.local.md` (personal, git-ignored), at the repository root. Neither `CLAUDE.md` nor `AGENTS.md` carries them. Argued in *The settings files*. -- **D2** — Both files use one grammar: a `key: value` line at the start - of a line, outside a fenced block; a key contains a dot; a value is a +- **D2** — Both files use one grammar: a line at the start of a line, + outside a fenced block, that opens with a key — a dotted name — and a + colon is a settings line, validated as D36 says; a valid value is a plain token; every other line is commentary. Argued in *The settings files*. - **D3** — Every key has one scope, team or personal, fixed by the @@ -102,20 +103,18 @@ the rule files named in *Changes by file*. value and source of every registered key, plus warnings, as plain standard output, which both hosts add to the session's context. Argued in *The loader*. -- **D10** — The loader stays silent when `.working-process/` does not - exist, exits 0 on any error of its own, and caps its output at 4 KB, - saying so when it truncates. Argued in *The loader*. +- **D10** — In hook mode the loader stays silent when + `.working-process/` does not exist, exits 0 on any error of its own, + and caps its output at 4 KB, saying so when it truncates; its other + modes report failure. Argued in *The loader*. - **D11** — The loader is its own hook handler, registered without a matcher so it also runs on resume and compaction; it finds the registry relative to its own path and uses POSIX `sh`, `sed` and `awk`. Argued in *The loader*. - **D12** — A new always-on rule, `process-settings.md`, defines the - files, the grammar, the block, the three-step read, and when a write - happens; how a write happens belongs to the loader (D43), and the - full by-hand resolution to a second rule, `process-settings-resolution.md`, - path-scoped to `.working-process/**` so it loads only when a session - reads the settings by hand; `process-settings.md` names it and tells - the by-hand path to open it first. Argued in *Reading a key*. + files, the grammar, the block, the reading of a key, and when a write + happens; how a write happens belongs to the loader (D43). No second + rule resolves keys by hand. Argued in *Reading a key*. - **D13** — A rule reads a key from the block in its context; without a trustworthy block, by the procedure D38 names; an unset key means the rule's behaviour before this change. Argued in *Reading a key*. @@ -137,7 +136,8 @@ the rule files named in *Changes by file*. asked about, and the rule says so. Argued in *Directory modes*. - **D19** — When a standing question is asked in ordinary work, its answers include "yes, and record", which writes the answer, through - the loader's `--set`, to the file its scope names. There is no separate "record it?" question. + the loader's `--set`, to the file its scope names; where no settings + block names the loader, the option is withheld. There is no separate "record it?" question. Anyone may record a team key this way; the write names the team file. Argued in *Recording an answer*. - **D20** — A `CLAUDE.md` note that declares a key's value keeps binding @@ -157,15 +157,16 @@ the rule files named in *Changes by file*. - **D24** — The first personal answer written creates `.working-process/.gitignore` holding the single line `settings.local.md`, beside the personal file; the loader's `--set` - writes it (D43). Argued in *The loader*. + writes it (D43), and the file is committed with the team file. + Argued in *The loader*. - **D25** — The skill asks about `dir.docs/memory` only when the project-memory plugin is installed, and the project-memory rule reads that key only when the working-process rules are installed. Argued in *The keys*. - **D26** — Version one reads the working-process registry alone; no domain registry is read or discovered. Argued in *Out of scope*. -- **D27** — A repository test checks that every registered key is cited - by the rule its entry names, that every key reference under the +- **D27** — A repository test checks that every registered key is cited, + literally, by every rule its entry names, that every key reference under the `rules/`, `skills/`, `agents/` and `commands/` of working-process, project-memory, python-standards and salesforce-standards is registered, that defaults validate, and that names are unique. Argued in *Verification*. @@ -208,11 +209,10 @@ the rule files named in *Changes by file*. resolution; after writing, it prints the fresh block, which supersedes the old one for the rest of the session. Argued in *The loader*. -- **D38** — Without a trustworthy block — none, or one marked - incomplete — a session resolves keys by the loader's own procedure, - through `--print` or, where the script is unreachable, by the full - resolution in `process-settings-resolution.md`. Argued in *Reading a - key*. +- **D38** — A block marked incomplete is completed by running + `--print` through the loader path its first line names; with no block + at all, every key is unset and each rule behaves as it did before this + change. Argued in *Reading a key*. - **D39** — The skill asks about directory exceptions only when the developer asks for them. Argued in *The skill*. - **D40** — The python and salesforce review commands and the @@ -241,6 +241,15 @@ the rule files named in *Changes by file*. writes a settings file directly; the one hand-written line is a team's suggestion for a personal key, which `--validate --scope team` warns about and still passes. Argued in *The loader*. +- **D44** — The block's first line names the loader's path, which is + how a rule reaches `--print` and `--set`. Argued in *The loader*. +- **D45** — A first-create question answered "and record" writes the + exception for the directory asked about; only the skill writes + `dir.default`. Argued in *Recording an answer*. +- **D46** — `--validate` fails on an error — an invalid value, a + duplicate, an unknown key, a team key in the personal file — and never + on a notice, which a personal key's suggestion in the team file is. + Argued in *The loader*. ## The settings files @@ -263,8 +272,11 @@ process machinery, and the answers fit a format a script can resolve. A settings line is a `key: value` pair at the start of a line and outside a fenced block. A key holds a dot — `review.autonomy`, `dir.docs/specs` — so a line of prose such as `Note: see below` never -reads as a key. A value is a plain token: letters, digits, `.`, `_`, -`/` and `-`, with no quotes. +reads as a key. A key is lower-case: a first segment of letters, +digits and `-`, then one or more `.`-joined segments of letters, digits, +`-`, `/` and `.` — so `dir..superpowers` is a key, and `Note.this:`, +with its capital, is prose. A value is a plain token: letters, digits, +`.`, `_`, `/` and `-`, with no quotes. Recognizing a settings line and validating it are separate steps. Every line shaped like a key — a dotted name and a colon at the start of a @@ -329,8 +341,8 @@ version one. One file in the plugin defines every key. Each entry carries the key's name, its scope, its allowed values, its default (a value, or `unset` -where the rule asks), the rule file that reads it, and the question the -skill asks. The shape is fixed, one field per line under a heading +where the rule asks), the rule files that read it (a list), and the +question the skill asks. The shape is fixed, one field per line under a heading naming the key, so that the loader's `sed` and the repository test both parse it without a Markdown parser — the same discipline the rules manifest follows. @@ -351,6 +363,7 @@ Version one registers the keys whose question has no home today: |---|---|---|---|---| | `dir.default` | team | `tracked` \| `ignored` | ask at first create | process-artifacts | | `dir.` | team | `tracked` \| `ignored` | absent: `dir.default` applies; invalid: ask | process-artifacts | +| `dir.docs/memory` | team | `tracked` \| `ignored` | as `dir.` | project-memory | | `design.technical-design-offer` | team | `on` \| `off` | today's manifest-raised offer | workflow | | `dispatch.propagation-auditor-tier` | team | `cheapest` \| `mid` \| `most-capable` | `cheapest` | workflow | | `docs-branch.merge` | team | `squash` \| `fast-forward` | ask at the gate | spec-plan-lifecycle | @@ -362,7 +375,11 @@ The `dir.` exceptions exist for `docs/specs`, `docs/technical-designs`, `docs/plans`, `docs/domain`, `docs/code-review`, `.superpowers` and `docs/memory`. One default with exceptions collapses what is today one question asked separately for -each directory into one question with rare exceptions. An exception has +each directory into one question with rare exceptions. The block lists +each exception's effective value, inheritance applied, so a reader such +as the project-memory rule reads its own key and never recomputes the +default. `dir..superpowers` covers the whole `.superpowers/` family +`process-artifacts.md` names. An exception has three states. Absent, it inherits `dir.default`. Valid, it decides its directory. Invalid, it is unset — the rule asks at that directory's first create, and it does not inherit, for the reason D4 gives: an @@ -390,7 +407,8 @@ never a model name. `mid` is the glossary's own word for its rung, and available family" for this agent today. Each host resolves the tier at dispatch through a table of its own; version one ships only Claude Code's (Haiku for `cheapest`, the family one below the most capable for `mid`, the most capable -available for `most-capable`). A Codex port adds its table; Codex's +available for `most-capable`), and it lives in `workflow.md`, where the +dispatch tiers are already prescribed. A Codex port adds its table; Codex's current ladder has three rungs, which fit the three values, and its separate reasoning-effort setting is the port's concern, not a settings value. Consent values are `yes` and `no` alone. A @@ -415,19 +433,36 @@ applies scope and precedence against the registry, and emits one block as the hook's additional context: ``` -working-process settings (root: ; team: settings.md; local: settings.local.md) +working-process settings (root: ; loader: ; team: settings.md; local: settings.local.md) dir.default: tracked [team] +dir.docs/specs: tracked [inherited] +dispatch.propagation-auditor-tier: cheapest [default] review.autonomy: yes [local] consult.personas: unset [team suggests: yes] -warning: settings.md:14 unknown key `review.autonmy` — ignored +docs-branch.merge: unset [invalid in team] +error: settings.md:14 unknown key `review.autonmy` — ignored ``` +Each key's line carries one of a closed set of sources: `[team]`, +`[local]`, `[default]`, `[inherited]` (a directory exception taking +`dir.default`), `[team suggests: ]` beside `unset`, and +`[invalid in team]` or `[invalid in local]` beside `unset`. The first +line names the local file actually read — `settings.local.md (main +checkout)` in a worktree using the fallback, `settings.local.md +(worktree, bare repository)` where there is no main checkout — and the +loader's own path. A truncated block ends with `incomplete: run + --print`. + The first line names the block, so a reader can find it among other hooks' context, whose order is undocumented, and understand it without the rule. Every registered key is listed, set or not, so a rule has one place to read and never needs the registry. Warnings live in the block, -because nobody reads a hook's stderr: unknown keys, invalid values, -duplicates, a key in the wrong scope's file. +because nobody reads a hook's stderr. They come in two kinds. An +error — an invalid value, a duplicate, an unknown key, a team key in the +personal file — takes an `error:` line. A personal key's suggestion in +the team file is not an error: it shows in its key's line as +`[team suggests: …]` and takes no line of its own, so a deliberate +suggestion adds nothing to every session start. The output is capped at 4 KB. That is a byte cap, not a token guarantee: Codex's documented per-handler threshold is about 2,500 @@ -469,10 +504,12 @@ cap guards the hook's context budget, and a skill or a session recovering from a truncated block needs the whole of it — and `--validate --scope team|personal ` checks a candidate file against the scope it is meant for, printing its warnings and exiting -non-zero when the file would not validate. A warning alone fails -nothing: a personal key in the team file is reported as a suggestion -and the file still validates, which is the check a hand-written -suggestion gets. A third mode writes: +non-zero on any error; a suggestion is no error, so a hand-written +suggestion passes, and that is the check it gets. `--validate` serves a +developer who edits a settings file by hand, and the tests; `--set` +validates on its own. `--print` in a project without +`.working-process/` prints the first line alone, saying there is no +settings directory. A third mode writes: `--set ` takes no scope argument — the registry fixes each key's scope, so the file follows from the key — and resolves the destination — @@ -485,7 +522,9 @@ and reports the lines it removed — the developer has just answered the question the duplicate caused, so that answer resolves it without a second question. The written line takes the first duplicate's place; each later duplicate goes, with the question comment directly above -it, the one exception to leaving every other line untouched; validates the result against the key's registry scope +it — a comment line whose text is that key's question in the registry, +never a hand-written one — the one exception to leaving every other +line untouched; validates the result against the key's registry scope before anything is written, so no `--set` ever leaves a file that would not validate; and on success prints the fresh block. `--set --dry-run` resolves the same destination and prints what @@ -501,35 +540,25 @@ swallows its errors and exits 0; the other three report failure. ## Reading a key A new always-on rule, `process-settings.md`, defines the files, their -grammar, the block, the three-step read below, and when a write -happens. The rules that read a key cite it and restate none of it. It -stays short because every session and every dispatched agent loads it: -the full by-hand resolution — needed only where no hook ran and the -loader cannot be reached — lives in a second rule of the payload, -`process-settings-resolution.md`, scoped by `paths:` to -`.working-process/**`. The Rules engine copies every rule file and -Claude Code loads a rule without `paths:` at every launch, so a file -merely "beside the rule" would be always-on; path-scoped, it loads -exactly when a session reads a settings file by hand, which is the -by-hand path and nothing else — the shape `process-artifacts.md` -already uses. The trigger is a file read through the Read tool, not a -`cat` in a shell, so `process-settings.md` also names the file and -tells a session on the by-hand path to open it before touching the -settings, and correctness does not depend on which tool reads them. -How a write happens belongs to the loader (*The loader*). +grammar, the block, the read below, and when a write happens. The rules +that read a key cite it and restate none of it, and it stays short, +because every session and every dispatched agent loads it. How a write +happens belongs to the loader (*The loader*). To read a key, a session: -1. takes its value from the settings block in its context, unless the - block says it is incomplete; -2. without a trustworthy block — none arrived because hooks are - disabled or a Codex plugin is not yet trusted, or the block says it - is incomplete — resolves the key by the loader's own procedure: - running `--print` where the script is reachable, otherwise following - the full resolution — scope, the worktree fallback, defaults, - duplicates and invalid values — in `process-settings-resolution.md`, - never a shortcut through the files; -3. where the key is unset, behaves as the rule did before this change: +1. where the repository has no `.working-process/` directory, treats + every key as unset and calls nothing; +2. takes its value from the settings block in its context; a block + marked incomplete is completed by running `--print` through the + loader path the block's first line names; +3. with no block at all — hooks disabled, a Codex plugin not yet + trusted — treats every key as unset. It does not resolve the files + by hand: without the loader it cannot reach the registry, so it + would not know a key's scope or default, and a guess could read a + team suggestion as consent. A rule installed on a machine without + the plugin meets the same case; +4. where the key is unset, behaves as the rule did before this change: the question is asked, or the offer made, exactly as today. An answer given in the current session outranks the settings, so a @@ -571,7 +600,9 @@ plugin. The round cap has no such evidence behind it. Each site that asks a standing question today names its key and says what happens when the key is unset. "The developer's own instructions" -in those sentences becomes the key: +in those sentences becomes the key, and each site's unset branch keeps +today's text, including its reading of a `CLAUDE.md` declaration, for +as long as D20's transition lasts: - `workflow.md` — persona consultation (`consult.personas`), loop autonomy and per-round commits (`review.autonomy`, @@ -579,7 +610,8 @@ in those sentences becomes the key: (`design.technical-design-offer`; the sentence that reads a declaration "until a standing home for a project's process answers exists" is rewritten to cite the key), and the propagation auditor's - tier (`dispatch.propagation-auditor-tier`). + tier (`dispatch.propagation-auditor-tier`), together with the host's + tier table (*The keys*). - `spec-plan-lifecycle.md` — the per-round commit consent (`review.per-round-commit`) and the fast-forward or squash of the `.docs` branch (`docs-branch.merge`). @@ -596,7 +628,8 @@ in those sentences becomes the key: prior decision is present" gains the conflict question: a visible signal that contradicts the key is reported and the developer asked. - `agents/propagation-auditor.md` — the tier clause, as *Reading a key* - says, including the struck over-tier sentence. + says, including the struck over-tier sentence, and the `description:` + line's own "cheapest" prescription. ## Directory modes @@ -611,8 +644,11 @@ form. At the first touch of a Process directory: 2. a visible signal that contradicts the key is reported, and the developer asked which stands; nothing is changed until they answer; 3. a key with no visible signal is applied without asking — the ignored - mode by writing the `*` `.gitignore`, the tracked mode by the first - committed file; + mode by writing the `*` `.gitignore`; the tracked mode needs no act + and becomes visible with the first committed file. A step keyed to a + resolved mode rather than to the question — `review-reports.md`'s + local-pocket `.gitignore` for a tracked `docs/code-review/` — fires + whichever way the mode was settled; 4. with neither, the rule asks, as today. `.working-process/` is not `.claude/working-process/`, the per-checkout @@ -629,12 +665,16 @@ When a standing question is asked in ordinary work, its answers gain one option: "yes, and record" (or "no, and record"). Choosing it runs the loader's `--set`, which writes the answer to the file the key's scope names and prints the fresh block. The rule decides when a write -happens; the loader decides how. There is no second question: the +happens; the loader decides how. A first-create question answered "and +record" writes the exception for the directory asked about — +`dir.docs/specs`, say — because the answer was about that directory; +only the skill writes `dir.default`. The technical-design offer's "yes, +and record" writes `on` and its "no, and record" writes `off`. There is no second question: the developer is interrupted once, as the review loop's one-batch-per-round contract intends. A session never records an answer the developer did -not choose to record. Where `--set` cannot run — the loader unreachable -on the no-hook path — the option is withheld: the answer holds for the -session, and the session says it was not recorded. +not choose to record. Where no settings block names the loader, the +option is withheld: the answer holds for the session, and the session +says it was not recorded. Anyone may record a team key this way, not only whoever ran the setup. The team file is an ordinary committed file, so the change shows in the @@ -664,7 +704,11 @@ nudges them to run it. A run: 1. calls the loader's `--print` and shows the effective settings as one table — key, value, source — together with the first-run candidates - (*Migration*); + (*Migration*): on a run where no settings file exists yet, the + directory signals are offered as values to record and each prose + note as the model's reading; the project-memory plugin counts as + installed where its `project-memory.md` rule is found in the project + or user rules directory; 2. asks only about unset keys, one at a time, with a recommendation, saying for each which file the answer goes to; a team suggestion is offered as the default answer to a personal key; project-memory's key @@ -678,9 +722,14 @@ nudges them to run it. A run: write would remove are shown before it is applied. The fresh block `--set` prints supersedes the one from session start for the rest of the session; -4. materializes a declared mode in each Process directory that already - exists and does not contradict it, and reports every contradiction; - it creates no directory; +4. materializes a declared ignored mode in each Process directory that + already exists and does not contradict it — a tracked mode needs no + act — and creates no directory. For each contradiction it asks which + stands: where the visible signal stands, it records the matching + value with `--set`; where the key stands, it says what the developer + must do by hand, since it never untracks or force-adds a file; +5. reminds the developer that `.working-process/settings.md` and its + `.gitignore` belong in the work's commit; it never commits. A team's suggestion for a personal key is the one settings line `--set` never writes: it goes into the team file by hand, and the @@ -693,7 +742,8 @@ questionnaire. ## Verification - **Consistency test** (repository, beside the decision-coverage - tests): every registered key is cited by the rule its entry names; + tests): every registered key is cited literally by every rule its + entry names — the registry's reader field is a list; every key reference is registered, where a reference is a backticked token with a registered key's shape — a dotted name starting with one of the registry's prefixes — under the `rules/`, @@ -743,8 +793,7 @@ questionnaire. - New: the `process-setup` skill; the key registry; the loader script with its `--print`, `--validate` and `--set` modes; the - always-on `process-settings.md` rule and the path-scoped - `process-settings-resolution.md` holding the full by-hand resolution; a second SessionStart handler in + always-on `process-settings.md` rule; a second SessionStart handler in `hooks/hooks.json`. - `rules/workflow.md`, `rules/spec-plan-lifecycle.md`, `rules/process-artifacts.md` — as *Changes to the rules* and @@ -767,11 +816,28 @@ None. ## Review rounds +### 2026-09-28 — integrity audit, fable, at the consumption gate + +Coverage tell: 819 lines read, highest line cited 780 (the rest is +ledger). Seven defects and seventeen implementer questions; the quotes +were checked against the spec before disposition. + +- fixed 2026-09-28 — D10 gave every mode exit 0 on error while *The loader* confines it to the hook; license: *The loader* ("Only the hook mode swallows its errors"); D10 names hook mode +- fixed 2026-09-28 — D19 omitted the withheld option; license: *Recording an answer*; D19 carries it +- fixed 2026-09-28 — the `dir.` row named process-artifacts as reader of `dir.docs/memory`; license: D25; its own row names project-memory +- fixed 2026-09-28 — a project without `.working-process/` would run `--print` at every read; license: D10 (silence by design); *Reading a key* step 1 treats every key as unset and calls nothing +- fixed 2026-09-28 — the by-hand resolution needed the registry exactly where it is unreachable, a second definition of every key or none; ruling: 2026-09-28; D38 sharpened, new D44 (the block names the loader's path), `process-settings-resolution.md` dropped: with no block every key is unset (reverses the round-1 F4 and round-2 F8 placements, whose file no longer exists) +- fixed 2026-09-28 — D2 kept the pre-D36 grammar; license: D36; D2 now defers to it +- fixed 2026-09-28 — the *Loop closed* note pointed "above" at lines below it; license: the ledger itself; "below" +- fixed 2026-09-28 — implementer questions 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17; license: D9, D14, D17, D20, D25, D27, D36, D37, D43 and the rules the spec names; the block's source set and first line, errors against notices (new D46), the key's shape, each site's unset branch, the committed `.gitignore`, the skill's first run and contradiction handling, inheritance shown in the block and the project-memory probe, the tier table's home and the card's `description:`, the reader list, the question comment's identity, suggestions without a line, tracked mode needing no act, the `.superpowers/` family, the local pocket, and `--print` without a settings directory are now stated +- fixed 2026-09-28 — implementer question 5, which key a directory's "and record" writes; ruling: 2026-09-28; new D45, the exception for that directory; the technical-design offer's yes and no map to `on` and `off` +- fixed 2026-09-28 — implementer question 1, how a rule reaches the loader; ruling: 2026-09-28; D44 + ### Loop closed — 2026-09-28 Resolved without a fresh architect round, on the developer's choice after round 3's stop signal: round 3 returned three Minor findings, all -fixed under cited licenses (F14–F16 above), and the architect judged a +fixed under cited licenses (F14–F16 below), and the architect judged a further round not worth its cost. The consumption gate's integrity audit reads the whole document next. From adc554b731dbc1b98a9686ba7628d96cec2a736d Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 19:23:28 +0200 Subject: [PATCH 107/143] docs(working-process): process-setup spec, gate fix after integrity wave --- docs/specs/2026-09-28-process-setup-design.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 04030a1..2000336 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -624,7 +624,7 @@ as long as D20's transition lasts: signal exists. They adopt the settings key and D17's order, including the conflict question. - `project-memory.md` — `dir.docs/memory` counts as the declared mode when - the working-process rules are installed, and its "never ask when a + the working-process rules are installed, and its "Never ask when a prior decision is present" gains the conflict question: a visible signal that contradicts the key is reported and the developer asked. - `agents/propagation-auditor.md` — the tier clause, as *Reading a key* @@ -832,6 +832,7 @@ were checked against the spec before disposition. - fixed 2026-09-28 — implementer questions 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17; license: D9, D14, D17, D20, D25, D27, D36, D37, D43 and the rules the spec names; the block's source set and first line, errors against notices (new D46), the key's shape, each site's unset branch, the committed `.gitignore`, the skill's first run and contradiction handling, inheritance shown in the block and the project-memory probe, the tier table's home and the card's `description:`, the reader list, the question comment's identity, suggestions without a line, tracked mode needing no act, the `.superpowers/` family, the local pocket, and `--print` without a settings directory are now stated - fixed 2026-09-28 — implementer question 5, which key a directory's "and record" writes; ruling: 2026-09-28; new D45, the exception for that directory; the technical-design offer's yes and no map to `on` and `off` - fixed 2026-09-28 — implementer question 1, how a rule reaches the loader; ruling: 2026-09-28; D44 +- hit fixed 2026-09-28 — *Changes to the rules* quoted project-memory's clause as "never ask …" while the source opens "Never ask …"; capitalised ### Loop closed — 2026-09-28 From a480b8e9342bac1e294ffecb073c60534252e07b Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 20:16:30 +0200 Subject: [PATCH 108/143] docs(working-process): process-setup spec, second integrity audit dispositions --- docs/specs/2026-09-28-process-setup-design.md | 114 +++++++++++++++--- 1 file changed, 96 insertions(+), 18 deletions(-) diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 2000336..505fa53 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -75,8 +75,8 @@ the rule files named in *Changes by file*. worktree replaces that fallback wholly, and the loader says so. Argued in *Scope and precedence*. - **D6** — One key registry in the plugin defines every key: name, - scope, allowed values, default, the rule that reads it, and the - question the skill asks. The skill, the loader and the rules keep no + scope, allowed values, default, the files that read it (a list of + rules, commands and cards), and the question the skill asks. The skill, the loader and the rules keep no second definition of a key. Argued in *The key registry*. - **D7** — The version-one keys. Argued in *The keys*. - **D7.1** — `dir.default`: `tracked` | `ignored`, team. @@ -157,7 +157,8 @@ the rule files named in *Changes by file*. - **D24** — The first personal answer written creates `.working-process/.gitignore` holding the single line `settings.local.md`, beside the personal file; the loader's `--set` - writes it (D43), and the file is committed with the team file. + writes it (D43), and it is committed in the checkout where it was + written. Argued in *The loader*. - **D25** — The skill asks about `dir.docs/memory` only when the project-memory plugin is installed, and the project-memory rule reads @@ -183,7 +184,8 @@ the rule files named in *Changes by file*. through a repository-level Codex hook, after trust and after compaction or resume. Argued in *Verification*. - **D31** — The glossary's **First-create question** entry gains the - settings key as a signal. Argued in *Changes by file*. + settings key as a signal; the grilling of this spec already landed it, + and the plan verifies it. Argued in *Changes by file*. - **D32** — An absent `dir.` exception inherits `dir.default`; an invalid one is unset and the rule asks, never inheriting. Argued in *The keys*. @@ -217,7 +219,9 @@ the rule files named in *Changes by file*. developer asks for them. Argued in *The skill*. - **D40** — The python and salesforce review commands and the project-memory rule, which restate the first-create predicate, adopt - the settings key and its conflict question. Argued in *Changes to the + the settings key and its conflict question; in an install without the + working-process rules, a command reads the key only from a block in + context and otherwise keeps today's predicate. Argued in *Changes to the rules*. - **D41** — Personal answers are written where the loader reads them: in a worktree, to the main checkout's personal file, with its @@ -250,6 +254,26 @@ the rule files named in *Changes by file*. duplicate, an unknown key, a team key in the personal file — and never on a notice, which a personal key's suggestion in the team file is. Argued in *The loader*. +- **D47** — Claude Code's tier table lives in `workflow.md` and maps + each tier to a relative rung: the cheapest available family, the + family one below the most capable, the most capable available. + Argued in *The keys*. +- **D48** — The grilling-session skill stays as it is: it defers to + `process-artifacts.md`'s signal list. Argued in *Changes to the + rules*. +- **D49** — `--set` never introduces an error: on a file already + carrying one on another line it writes its own line and reports the + other. Argued in *The loader*. +- **D50** — A team key is written to the team file of the checkout + `--set` runs in. Argued in *Scope and precedence*. +- **D51** — The first write in a project without `.working-process/` is + the skill's: the hook stays silent there, so no block names the + loader, and the skill finds the loader relative to its own plugin + directory. Argued in *The skill*. +- **D52** — On the skill's first run, existing directory signals are + offered as `dir.default` set to the majority's mode, and each + directory in another mode as an exception, all for the developer to + confirm. Argued in *The skill*. ## The settings files @@ -327,6 +351,12 @@ the loader's first line says which file it read; a write made from that worktree still goes to the main checkout, and its preview says it will not change the current worktree's answer. +A team key is written to the team file of the checkout `--set` runs +in: the team file is committed on a branch, so a worktree's team answer +travels with that branch's work and is reviewed with it. The +`.gitignore` a personal write creates is committed in the checkout it +lands in. + A bare repository with linked worktrees has no main checkout: the first porcelain entry is the bare repository itself, marked `bare`. There the loader has no fallback and its first line says so, and a personal answer @@ -405,8 +435,9 @@ never a model name. `mid` is the glossary's own word for its rung, and `most-capable` shortens the glossary's "most capable available"; `cheapest` is the workflow rule's, which prescribes "the cheapest available family" for this agent today. Each host resolves the tier at dispatch through a table -of its own; version one ships only Claude Code's (Haiku for `cheapest`, -the family one below the most capable for `mid`, the most capable +of its own; version one ships only Claude Code's (the cheapest +available family for `cheapest`, the family one below the most capable +for `mid`, the most capable available for `most-capable`), and it lives in `workflow.md`, where the dispatch tiers are already prescribed. A Codex port adds its table; Codex's current ladder has three rungs, which fit the three values, and its @@ -524,9 +555,11 @@ second question. The written line takes the first duplicate's place; each later duplicate goes, with the question comment directly above it — a comment line whose text is that key's question in the registry, never a hand-written one — the one exception to leaving every other -line untouched; validates the result against the key's registry scope -before anything is written, so no `--set` ever leaves -a file that would not validate; and on success prints the fresh +line untouched; validates its own line against the key's registry +scope before anything is written, so no `--set` ever introduces an +error — where the file already carries one on another line, `--set` +writes its line and reports the other, rather than refusing a write it +did not break; and on success prints the fresh block. `--set --dry-run` resolves the same destination and prints what the write would do — the file, the line it would insert or replace, the duplicate lines it would remove, the `.gitignore` it would create, and @@ -617,8 +650,11 @@ as long as D20's transition lasts: `.docs` branch (`docs-branch.merge`). - `process-artifacts.md` — the settings key joins the directory signals (*Directory modes*). -- `review-reports.md` and the grilling-session skill defer to - `process-artifacts.md`'s signal list and gain nothing of their own. +- `review-reports.md` — its local-pocket step, today written "when the + first-create question resolves to tracked mode", fires on the + resolved mode however it was settled (*Directory modes*). +- The grilling-session skill defers to `process-artifacts.md`'s signal + list and gains nothing of its own. - The python and salesforce review commands restate the predicate so each stands alone, and today skip the question whenever a visible signal exists. They adopt the settings key and D17's order, including @@ -627,6 +663,9 @@ as long as D20's transition lasts: the working-process rules are installed, and its "Never ask when a prior decision is present" gains the conflict question: a visible signal that contradicts the key is reported and the developer asked. +- `agents/integrity-auditor.md` — its sentence that the propagation + pass runs "on the cheapest available family" follows the resolved + tier. - `agents/propagation-auditor.md` — the tier clause, as *Reading a key* says, including the struck over-tier sentence, and the `description:` line's own "cheapest" prescription. @@ -705,8 +744,9 @@ nudges them to run it. A run: 1. calls the loader's `--print` and shows the effective settings as one table — key, value, source — together with the first-run candidates (*Migration*): on a run where no settings file exists yet, the - directory signals are offered as values to record and each prose - note as the model's reading; the project-memory plugin counts as + directory signals are offered as `dir.default` set to the majority's + mode, with each directory in another mode as an exception, and each + prose note as the model's reading, all to confirm; the project-memory plugin counts as installed where its `project-memory.md` rule is found in the project or user rules directory; 2. asks only about unset keys, one at a time, with a recommendation, @@ -729,7 +769,14 @@ nudges them to run it. A run: value with `--set`; where the key stands, it says what the developer must do by hand, since it never untracks or force-adds a file; 5. reminds the developer that `.working-process/settings.md` and its - `.gitignore` belong in the work's commit; it never commits. + `.gitignore` belong in the work's commit, in the checkout they were + written in; it never commits. + +In a project without `.working-process/` the hook is silent and no +block names the loader, so "and record" is withheld there and the +skill makes the first write; it finds the loader relative to its own +plugin directory. Adopting the settings is a deliberate act, as +adopting a Project-memory store is. A team's suggestion for a personal key is the one settings line `--set` never writes: it goes into the team file by hand, and the @@ -763,7 +810,8 @@ questionnaire. duplicate would lose, the `.gitignore` and the shadow warning while leaving every file byte-identical, writing a personal answer to the main checkout from a worktree with its `.gitignore`, and to the - worktree's own file in a bare-repository layout. + worktree's own file in a bare-repository layout; `--validate` + exiting non-zero on each error kind and zero on a suggestion. - **Claude Code dogfood**, with the plugin loaded from the checkout: the skill records settings; a new session receives the block; a recorded question is not asked; after `/compact` the block is back. The run @@ -805,17 +853,47 @@ questionnaire. `plugins/salesforce-standards/commands/salesforce-review.md` — the restated first-create predicate. - `docs/domain/glossary.md` — **First-create question** gains the - settings key as a signal, through a grilling session on this spec. + settings key as a signal; already landed by this spec's grilling, and + verified by the plan. +- `rules/review-reports.md` — the local-pocket step keyed to the + resolved mode. - Tests under `tests/working-process/`. - README and CHANGELOG entries under `## Unreleased` for working-process and project-memory; a `-dev` dogfood version. ## Open questions -None. +None for the design. The plan settles the format edges the integrity +audit of 2026-09-28 listed: the key line's exact regular expression and +its whitespace; the source a `dir.` line carries when +`dir.default` is itself unset or invalid, and a duplicated key's +source; the first line's `local:` when no personal file exists; whether +a suggestion shows beside a `[local]` value; whether the truncation line +counts inside the 4 KB and that the cut falls on a line boundary; the +tier table's form in `workflow.md`; and the "and record" wording for a +non-yes/no answer and for the autonomy question's two clauses. ## Review rounds +### 2026-09-28 — integrity audit, fable, at the consumption gate (second) + +Coverage tell: 886 lines read, highest line cited 886; glossary 693 +lines, highest cited 315. Seven defects and fifteen implementer +questions; quotes checked before disposition. + +- fixed 2026-09-28 — D6 kept the reader field singular; license: *The key registry* and *Verification*; D6 names a list of rules, commands and cards (question 6) +- fixed 2026-09-28 — D24 and the skill's reminder could not both hold in a worktree, and a team key's destination was unstated; ruling: 2026-09-28; new D50, *Scope and precedence* and step 5 say which checkout +- fixed 2026-09-28 — the tier table named Haiku though the value is a relative rung; license: glossary **Tier**; the cheapest available family +- fixed 2026-09-28 — the host tier table had no register entry; license: the register's declared rule; new D47 +- fixed 2026-09-28 — the local-pocket step was claimed to fire on a key while `review-reports.md` stayed unchanged; license: *Directory modes* step 3; `review-reports.md` changes and joins *Changes by file* +- fixed 2026-09-28 — D31 prescribed an edit already at HEAD; license: glossary at f0a57d4; D31 and *Changes by file* say it landed and the plan verifies it +- fixed 2026-09-28 — the grilling-session skill's "stays as it is" had no entry; license: the register's declared rule; new D48 +- fixed 2026-09-28 — question 1, `--set` on a file already carrying an error; ruling: 2026-09-28; new D49, `--set` never introduces one and reports the other +- fixed 2026-09-28 — question 2, the first "and record" in a project without settings; ruling: 2026-09-28; new D51, the skill makes the first write and finds the loader from its own plugin directory +- fixed 2026-09-28 — question 3, directory signals on the first run; ruling: 2026-09-28; new D52, majority mode as `dir.default` with exceptions, to confirm +- fixed 2026-09-28 — questions 4, 7, 12, 13; license: D41, D46, D40 and the integrity-auditor card; the team-file destination (D50), a `--validate` test, standalone review commands, the integrity-auditor card joins *Changes by file* +- fixed 2026-09-28 — questions 5, 9, 10, 11, 14, 15, format edges; ruling: 2026-09-28; handed to the plan in *Open questions* + ### 2026-09-28 — integrity audit, fable, at the consumption gate Coverage tell: 819 lines read, highest line cited 780 (the rest is From 444936bc49c5ab75ee27298d39df6b7be871a8be Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Mon, 28 Sep 2026 20:23:37 +0200 Subject: [PATCH 109/143] docs(working-process): process-setup spec, integrity stamp --- docs/domain/glossary.md | 2 +- docs/specs/2026-09-28-process-setup-design.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index 0bd8e8b..9398ad7 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -72,7 +72,7 @@ _Avoid_: declaration (for a settings line) **Key registry**: The one definition of every settings key in the working-process plugin -— scope, values, default, reading rule, question. The loader, the +— scope, values, default, reading files, question. The loader, the `process-setup` skill and the rules keep no second definition of a key. _Avoid_: schema, key list diff --git a/docs/specs/2026-09-28-process-setup-design.md b/docs/specs/2026-09-28-process-setup-design.md index 505fa53..fde88d3 100644 --- a/docs/specs/2026-09-28-process-setup-design.md +++ b/docs/specs/2026-09-28-process-setup-design.md @@ -4,6 +4,7 @@ date: 2026-09-28 status: draft grilled: 2026-09-28 architect: concerns (resolved 2026-09-28) +integrity: 2026-09-28 (sha: 901ebf5) decisions: registered branch: feature/process-setup base: develop @@ -886,13 +887,14 @@ questions; quotes checked before disposition. - fixed 2026-09-28 — the tier table named Haiku though the value is a relative rung; license: glossary **Tier**; the cheapest available family - fixed 2026-09-28 — the host tier table had no register entry; license: the register's declared rule; new D47 - fixed 2026-09-28 — the local-pocket step was claimed to fire on a key while `review-reports.md` stayed unchanged; license: *Directory modes* step 3; `review-reports.md` changes and joins *Changes by file* -- fixed 2026-09-28 — D31 prescribed an edit already at HEAD; license: glossary at f0a57d4; D31 and *Changes by file* say it landed and the plan verifies it +- fixed 2026-09-28 — D31 prescribed an edit already at HEAD; license: glossary at f0a57d4; D31 and *Changes by file* say it landed and the plan verifies it (question 8) - fixed 2026-09-28 — the grilling-session skill's "stays as it is" had no entry; license: the register's declared rule; new D48 - fixed 2026-09-28 — question 1, `--set` on a file already carrying an error; ruling: 2026-09-28; new D49, `--set` never introduces one and reports the other - fixed 2026-09-28 — question 2, the first "and record" in a project without settings; ruling: 2026-09-28; new D51, the skill makes the first write and finds the loader from its own plugin directory - fixed 2026-09-28 — question 3, directory signals on the first run; ruling: 2026-09-28; new D52, majority mode as `dir.default` with exceptions, to confirm - fixed 2026-09-28 — questions 4, 7, 12, 13; license: D41, D46, D40 and the integrity-auditor card; the team-file destination (D50), a `--validate` test, standalone review commands, the integrity-auditor card joins *Changes by file* - fixed 2026-09-28 — questions 5, 9, 10, 11, 14, 15, format edges; ruling: 2026-09-28; handed to the plan in *Open questions* +- hit fixed 2026-09-28 — the glossary's **Key registry** entry kept a singular "reading rule" after D6 made it a list; reworded; and question 8's disposition was not named on its line; now named ### 2026-09-28 — integrity audit, fable, at the consumption gate From 1598f9002ad66a284cdb6bfcf544ceb1939aba8a Mon Sep 17 00:00:00 2001 From: Jacek Nakonieczny Date: Tue, 29 Sep 2026 07:19:14 +0200 Subject: [PATCH 110/143] docs(working-process): process-setup implementation plan --- docs/plans/2026-09-28-process-setup.md | 3235 ++++++++++++++++++++++++ 1 file changed, 3235 insertions(+) create mode 100644 docs/plans/2026-09-28-process-setup.md diff --git a/docs/plans/2026-09-28-process-setup.md b/docs/plans/2026-09-28-process-setup.md new file mode 100644 index 0000000..0ad0d6d --- /dev/null +++ b/docs/plans/2026-09-28-process-setup.md @@ -0,0 +1,3235 @@ +--- +ticket: none +date: 2026-09-28 +status: draft +spec: ../specs/2026-09-28-process-setup-design.md +branch: feature/process-setup +base: develop +--- + +# process-setup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Land `docs/specs/2026-09-28-process-setup-design.md` — the +two settings files, the key registry, the loader with its four modes and +its SessionStart hook, the always-on `process-settings.md` rule, the +`process-setup` skill, and the rule, card and command edits that read a +named key instead of "the developer's own instructions". + +**Architecture:** One POSIX `sh` script, `scripts/load-settings.sh`, +owns every read and every write of a settings file; it parses the +registry `SETTINGS_REGISTRY.md` at the plugin root with `sed` and +`awk`, and the same registry is what the skill asks from and the +consistency test checks. The rules cite a key by name and read it from +the block the hook puts in context; nothing else resolves a key. The +loader and its tests are code with a test cycle each; every other +deliverable is prose, whose test is the check block it publishes, run +before and after the edit. + +**Tech Stack:** POSIX `sh` (dash), `sed`, `awk` and `git` for the +loader; Python 3.9+ `unittest` via `subprocess` for its tests and the +consistency test, following `tests/working-process/test_decision_coverage.py`; +Markdown rules, cards, skill and README distributed as a Claude Code +plugin and Rules payload; `tr`, `grep`, `awk`, `shasum` and +`claude plugin validate` for the prose checks. + +## Global Constraints + +- **The spec is the source.** Where this plan and the spec disagree, the + spec wins and the plan is wrong — except where *Deviations from the + spec* below records a departure and its reason. +- **Realizes:** D26 — **Version one reads the working-process registry + alone.** The loader opens exactly one registry, found relative to its + own path; no task discovers, lists or reads a registry of another + plugin, and `salesforce-standards`' `trigger-framework:` and + `vendor-paths:` declarations are not touched. +- **Realizes:** D8 — **Keys are never renamed, and no key is retired.** + Every key this plan registers keeps its name in every task; the + registry's header says so, and no task writes a retirement marker or a + loader report for one — both wait for the first key actually retired. +- **Realizes:** D48 — **The grilling-session skill is not modified.** + `plugins/working-process/skills/grilling-session/SKILL.md` keeps + deferring to `process-artifacts.md`'s signal list; Task 16 checks its + hash against `develop`. +- **The loader is POSIX.** `#!/bin/sh`, `set -u`, `export LC_ALL=C`, + and only `sh`, `sed`, `awk`, `git`, `mkdir`, `mv`, `rm`, `cat`, + `printf`, `dirname`, `basename`, `pwd` and `head`; no `jq`, no + `bash`-only syntax, no GNU-only flags. Every step that runs the loader + runs it through `/bin/sh` (dash on the development machine) so a + bashism fails here rather than on macOS. +- **The loader never reads stdin** — Claude Code and Codex feed a hook + JSON on stdin, and a loader that waited on it would hang. +- **Prose wraps at 72 characters** in every rule, agent card, skill and + README. Match the surrounding paragraph; never reflow a paragraph this + plan does not change. Indented grammar examples, table rows, one-line + `description:` values and lines inside fenced blocks are exempt, as + they are everywhere in the rules. +- **Copy every Replace block verbatim.** The blocks already carry the + elements-of-style pass; a block an implementer rewords owes the pass + again, and its checks may stop matching. +- **Checks normalise whitespace and match fixed strings.** A prose check + pipes the file through `tr -s '[:space:]' ' '` and counts with + `grep -oF … | wc -l`, so a phrase matches wherever a line wraps and + the count is the same under GNU grep and ugrep. Only a check anchoring + something that cannot wrap — a heading, a table row, a command, a + key line — reads the file plain. +- **The glossary binds.** `docs/domain/glossary.md` terms and `_Avoid_` + bans govern every new sentence: **Standing answer**, **Settings key**, + **Team key**, **Personal key**, **Settings line**, **Key registry**, + **Settings block**, **First-create question**, **Process directory** + and **Tier** are canonical; "preference", "config", "option", "flag", + "shared setting", "private setting", "schema", "key list", "settings + dump", "config context", "artifact folder" and "model level" are + banned. A settings line is never called a declaration; a declaration + is the prose note in `CLAUDE.md`. +- **Public repo hygiene**: no machine-specific paths, no company or + client names, all committed text in English. Test fixtures use + `tempfile` paths and `fixture@example.invalid` identities. +- **Commit messages are one line** — a conventional-commit subject, no + body and no trailer, not even `Co-Authored-By`. A subagent + implementer's brief says so outright, since subagents add the trailer + by default. +- **Commits land on `feature/process-setup`.** The document branch + `feature/process-setup.docs` merges into it at the implementation-ready + gate, before Task 1. +- **Do not run `sync-rules`, do not move the spec's or this plan's + `status`, and do not commit anything under `.claude/working-process/`.** + All three are the developer's. The dogfood version string is the one + version edit this plan makes (Task 15); the final bump belongs to the + release PR. +- **`cp`, `mv` and `rm` are aliased `-i` on the development machine.** + Every scripted copy or removal in this plan uses `command cp -f`, + `command mv -f` or `command rm -rf`, and checks the effect on disk. + +## Deviations from the spec + +Recorded here and beside the text they concern. + +1. **Task 8 also strikes "which a capable model does casually badly" + from the first paragraph of the propagation auditor's tier section.** + The spec strikes the card's second clause, "an over-tier run does + this work casually badly — a false clean line would then feed the + integrity gate unnoticed"; the first paragraph makes the same claim + in five words, and a card keeping one copy would still call the + evidence-backed choice the dangerous one. +2. **Task 15 writes `## Unreleased` entries for `python-standards` and + `salesforce-standards` as well.** The spec's *Changes by file* names + README and CHANGELOG entries for working-process and project-memory; + the repository's plugin-versioning rule requires an entry for every + plugin a topic changes, and Task 12 changes both review commands. + Only working-process takes the `-dev` version, since only it is + loaded from the checkout in the dogfood. +3. **Task 15 changes the plugin's `description:` in three places.** The + spec names README and CHANGELOG; the description lists the plugin's + skills, so a new skill changes it, and the marketplace-sync rule + makes `plugin.json`, `marketplace.json` and the root README row one + edit. +4. **Task 5 appends `settings.local.md` to an existing + `.working-process/.gitignore` that lacks the line, and reports it.** + D24 covers the file's creation and says nothing of one already there; + the directory ignores its own local file by definition (*The + settings files*), and a personal answer written into a file git would + track breaks that definition. +5. **Task 5 opens a settings file it creates with one title line.** The + spec says a created file holds the question and the key; a Markdown + file that opens with a lower-case key line reads as a fragment, and + the title is commentary under the grammar, so it changes no reading. +6. **Task 2 lists a duplicated key with the source `[invalid in team]` + or `[invalid in local]`.** The spec hands the duplicated key's source + to the plan and names a closed source set without a duplicate token; + the `error:` line carries the distinction, so the set stays closed. + +## Settled format edges + +The spec's *Open questions* hands these to the plan. Each is settled +here and realized where the task named says. + +1. **The key line's regular expression and its whitespace** — Task 2. + A line is a settings line when it matches the POSIX ERE + `^[a-z0-9-]+\.[a-z0-9./-]+:` at column one, outside a fence: no + leading whitespace, no whitespace before the colon. It is valid when + the whole line matches + `^[a-z0-9-]+\.[a-z0-9./-]+:[ \t]*[A-Za-z0-9._/-]+[ \t\r]*$` — any + spaces or tabs after the colon, one token, then only spaces, tabs or + a carriage return. Values compare exactly; `Yes` is not `yes`. A + fence is a line starting with three backticks at column one; an + indented one is commentary. +2. **A `dir.` line's source when `dir.default` is unset or + invalid, and a duplicated key's source** — Task 2. An absent + exception always carries `[inherited]` and `dir.default`'s effective + value, `unset` included, so the reader sees that it follows the + default and that the default decides nothing. A duplicated key + carries `[invalid in team]` or `[invalid in local]` (Deviation 6), + and its `error:` line names every line of the duplicate. +3. **The first line's `local:` when no personal file exists** — Tasks 2 + and 3. Each file the loader looked for and did not find carries + `(absent)` in the first line: `team: settings.md (absent)`, + `local: settings.local.md (absent)`, and in a worktree + `local: settings.local.md (main checkout, absent)` or + `local: settings.local.md (worktree, bare repository, absent)`. +4. **A suggestion beside a `[local]` value** — Task 2. Never: a + suggestion shows only beside `unset`. A personal key the personal file + decides shows its value and `[local]` and nothing else; the team's + suggestion was a default answer for a question the person has + answered. +5. **The truncation line and the 4 KB cut** — Task 2. The cap is 4096 + bytes over the whole hook output, the `incomplete:` line included; + the cut falls on a line boundary — the loader emits whole lines while + the running byte count plus the `incomplete:` line's length stays at + or under 4096, then the `incomplete:` line — and the first line is + never dropped. +6. **The tier table's form in `workflow.md`** — Task 8. A three-row + Markdown table after the model-selection paragraph, keyed by the + value and giving the Claude Code rung in the glossary's words. +7. **The "and record" wording for a non-yes/no answer and for the + autonomy question's two clauses** — Tasks 7, 9, 10, 11 and 12. The + option reads `, and record`, whatever the answer: + "tracked, and record", "squash, and record", "on" and "off" behind + the technical-design offer's "yes, and record" and "no, and record". + The autonomy question records per clause: an answer may say "yes, + and record; commits: not now", which writes `review.autonomy` and + nothing for `review.per-round-commit`; "not now" and "not in this + session" are never recorded, since they die with the session. + +## File structure + +Created: + +- `plugins/working-process/SETTINGS_REGISTRY.md` — Task 1: the key + registry, at the plugin root beside `PERSONA_COMMON.md`, since three + consumers read it (the loader, the skill, the consistency test). +- `plugins/working-process/scripts/load-settings.sh` — Tasks 2–5: the + loader; hook mode, `--print`, `--validate`, `--set`, `--set --dry-run`. +- `tests/working-process/test_settings_registry.py` — Tasks 1 and 14: + the consistency test. +- `tests/working-process/test_load_settings.py` — Tasks 2–5: the loader + tests. +- `plugins/working-process/rules/process-settings.md` — Task 6: the + always-on rule. +- `plugins/working-process/skills/process-setup/SKILL.md` — Task 13. + +Modified, under `plugins/working-process/` unless said otherwise: + +- `hooks/hooks.json` — Task 2: the second SessionStart handler. +- `rules/workflow.md` — Task 7 (persona consent, the technical-design + declaration, the autonomy question, the compaction clause) and Task 8 + (the propagation auditor's tier and the tier table). +- `agents/propagation-auditor.md`, `agents/integrity-auditor.md` — + Task 8: the tier clauses. +- `rules/spec-plan-lifecycle.md` — Task 9: per-round commit consent, + the `.docs` branch merge. +- `rules/process-artifacts.md`, `rules/review-reports.md` — Task 10: + the settings key among the directory signals, the local pocket keyed + to the resolved mode. +- `plugins/project-memory/rules/project-memory.md` — Task 11. +- `plugins/python-standards/commands/python-review.md`, + `plugins/salesforce-standards/commands/salesforce-review.md` — + Task 12. +- `README.md`, `CHANGELOG.md`, `.claude-plugin/plugin.json`, + `.claude-plugin/marketplace.json` (repository root), root `README.md`, + `plugins/project-memory/CHANGELOG.md`, + `plugins/python-standards/CHANGELOG.md`, + `plugins/salesforce-standards/CHANGELOG.md` — Task 15. + +Not modified: `skills/grilling-session/SKILL.md` (D48), +`docs/domain/glossary.md` (D31 — already landed; Task 16 checks), +`scripts/check-rules-drift.sh`, `scripts/ruleset-hash.sh`, +`scripts/write-manifest.sh`, `skills/sync-rules/SKILL.md`. Adding +`rules/process-settings.md` changes the Rules payload hash +`ruleset-hash.sh` computes, so every installed copy gets the drift +nudge after this ships; the CHANGELOG says so. + +## Order and independence + +- Task 1 comes first: the loader (Tasks 2–5), the skill (Task 13) and + the consistency test (Task 14) all read the registry's exact shape. +- Tasks 2, 3, 4 and 5 build one script in that order; each adds a mode + or a layout and its tests, and the earlier tests keep passing. +- Task 6 names the block's first line and source tokens exactly as + Task 2 prints them; run Task 2 first. +- Tasks 7, 8, 9, 10, 11 and 12 are prose edits, one file or one decision + each, and depend on nothing but Task 1's key names. Task 8 edits + `workflow.md` after Task 7; its anchor is untouched by Task 7. +- Task 13 needs Tasks 2–5 (it calls every loader mode) and Task 6 (it + cites the rule). +- Task 14 needs every prose task, since it checks that each registry + entry's readers cite the key. +- Task 15 needs every name the earlier tasks produced. Task 16 verifies + the whole branch; Tasks 17 and 18 run the loader in the two hosts and + need everything before them. + +--- + +### Task 1: The key registry and its shape test + +**Files:** +- Create: `plugins/working-process/SETTINGS_REGISTRY.md` +- Create: `tests/working-process/test_settings_registry.py` + +**Interfaces:** +- Consumes: nothing from earlier tasks. +- Produces: the registry path `${CLAUDE_PLUGIN_ROOT}/SETTINGS_REGISTRY.md` + (`$(dirname "$0")/../SETTINGS_REGISTRY.md` from the loader); the + heading shape `## ` and the five fields `scope:`, `values:`, + `default:`, `read-by:`, `question:`, one per line in that order; the + fourteen key names every later task cites; the parser + `read_registry(path) -> dict[str, dict[str, str]]` in the test module, + which Task 14 extends. + +**Realizes:** D6, D7.1, D7.2, D7.3, D7.4, D7.5, D7.6, D7.7, D7.8, D7.9, D7.10, D7.11, D7.12, D7.13, D7.14 + +- [ ] **Step 1: Write the failing test** + +`tests/working-process/test_settings_registry.py`, `unittest`, standard +library only. The parser is a module-level function so Task 14 can +reuse it: + +```python +"""Consistency test for plugins/working-process/SETTINGS_REGISTRY.md. + +The registry's shape is fixed — one `## ` heading per key, then the +five fields, one per line, in order — so this test and the loader's sed +parse it without a Markdown parser. +""" + +from __future__ import annotations + +import re +import unittest +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] +PLUGINS = REPO / "plugins" +REGISTRY = PLUGINS / "working-process" / "SETTINGS_REGISTRY.md" +FIELDS = ("scope", "values", "default", "read-by", "question") +KEY_RE = re.compile(r"^[a-z0-9-]+\.[a-z0-9./-]+$") +HEADING_RE = re.compile(r"^## (\S+)$") + + +def read_registry(path: Path) -> dict[str, dict[str, str]]: + """Parse the registry into {key: {field: value}}, in file order. + + A heading opens an entry; the next five non-blank lines are its + fields, `name: value`, in FIELDS order. Anything else is a defect and + raises AssertionError with the line number. + """ + entries: dict[str, dict[str, str]] = {} + lines = path.read_text(encoding="utf-8").splitlines() + index = 0 + while index < len(lines): + heading = HEADING_RE.match(lines[index]) + if not heading: + index += 1 + continue + key = heading.group(1) + if key in entries: + raise AssertionError(f"line {index + 1}: duplicate key {key}") + fields: dict[str, str] = {} + index += 1 + for name in FIELDS: + while index < len(lines) and lines[index].strip() == "": + index += 1 + if index >= len(lines) or not lines[index].startswith(f"{name}: "): + raise AssertionError(f"line {index + 1}: expected `{name}: ` under {key}") + fields[name] = lines[index][len(name) + 2:].strip() + index += 1 + entries[key] = fields + return entries + + +class RegistryShapeTest(unittest.TestCase): + def setUp(self) -> None: + self.entries = read_registry(REGISTRY) + + def test_fourteen_keys_in_order(self) -> None: + self.assertEqual(list(self.entries), [ + "dir.default", "dir.docs/specs", "dir.docs/technical-designs", + "dir.docs/plans", "dir.docs/domain", "dir.docs/code-review", + "dir..superpowers", "dir.docs/memory", + "design.technical-design-offer", "dispatch.propagation-auditor-tier", + "docs-branch.merge", "consult.personas", "review.autonomy", + "review.per-round-commit", + ]) + + def test_every_entry_is_well_formed(self) -> None: + for key, fields in self.entries.items(): + with self.subTest(key=key): + self.assertRegex(key, KEY_RE) + self.assertIn(fields["scope"], ("team", "personal")) + values = fields["values"].split(" | ") + self.assertTrue(all(re.fullmatch(r"[A-Za-z0-9._/-]+", v) for v in values), values) + self.assertEqual(len(values), len(set(values))) + self.assertTrue(fields["default"] == "unset" or fields["default"] in values, + fields["default"]) + for reader in fields["read-by"].split(", "): + self.assertTrue((PLUGINS / reader).is_file(), reader) + self.assertTrue(fields["question"].endswith("?"), fields["question"]) + self.assertFalse(KEY_RE.match(fields["question"].split(":")[0]), + "a question must not read as a key line") + + def test_scopes_and_defaults(self) -> None: + personal = {k for k, f in self.entries.items() if f["scope"] == "personal"} + self.assertEqual(personal, {"consult.personas", "review.autonomy", "review.per-round-commit"}) + self.assertEqual(self.entries["dispatch.propagation-auditor-tier"]["default"], "cheapest") + others = {k: f["default"] for k, f in self.entries.items() + if k != "dispatch.propagation-auditor-tier"} + self.assertEqual(set(others.values()), {"unset"}) + + +if __name__ == "__main__": + unittest.main() +``` + +- [ ] **Step 2: Run the test to see it fail** + +Run: `python3 -m unittest discover -s tests/working-process -p 'test_settings_registry.py' -v` +Expected: every test errors with `FileNotFoundError` on the registry. +(The directory name carries a hyphen, so discovery is the only form +that runs it; `python3 -m unittest tests.working-process…` cannot.) + +- [ ] **Step 3: Write the registry** + +`plugins/working-process/SETTINGS_REGISTRY.md`, exactly: + +``` +# working-process settings — the key registry + +The one definition of every settings key. The `process-setup` skill +takes its questions from here, the loader `scripts/load-settings.sh` +its validation and defaults, and the repository's +`test_settings_registry.py` its consistency check; the rules name keys +and nothing more. The shape +is fixed so that `sed` and the test parse it without a Markdown parser: +one `## ` heading per key, then exactly these five fields, one per +line, in this order — `scope:` (`team` or `personal`), `values:` (the +allowed values, separated by ` | `), `default:` (a value, or `unset` +where the rule asks), `read-by:` (the files that read the key, +comma-separated, relative to `plugins/`) and `question:` (what the +skill asks, and the comment `--set` writes above the key). A `dir.` +key other than `dir.default` is an exception: absent, it inherits +`dir.default`'s effective value. A key is never renamed; the marker for +a retired key, and how the loader reports one, wait for the first key +that is retired. + +## dir.default + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md +question: Which mode does a Process directory get unless an exception names it — tracked or ignored? + +## dir.docs/specs + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md +question: Which mode does docs/specs/ get, as an exception to dir.default — tracked or ignored? + +## dir.docs/technical-designs + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md +question: Which mode does docs/technical-designs/ get, as an exception to dir.default — tracked or ignored? + +## dir.docs/plans + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md +question: Which mode does docs/plans/ get, as an exception to dir.default — tracked or ignored? + +## dir.docs/domain + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md +question: Which mode does docs/domain/ get, as an exception to dir.default — tracked or ignored? + +## dir.docs/code-review + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md, python-standards/commands/python-review.md, salesforce-standards/commands/salesforce-review.md +question: Which mode does docs/code-review/ get, as an exception to dir.default — tracked or ignored? + +## dir..superpowers + +scope: team +values: tracked | ignored +default: unset +read-by: working-process/rules/process-artifacts.md +question: Which mode does the .superpowers/ family get, as an exception to dir.default — tracked or ignored? + +## dir.docs/memory + +scope: team +values: tracked | ignored +default: unset +read-by: project-memory/rules/project-memory.md +question: Which mode does docs/memory/ get, as an exception to dir.default — tracked or ignored? + +## design.technical-design-offer + +scope: team +values: on | off +default: unset +read-by: working-process/rules/workflow.md +question: Is the technical design offered at every consumption gate (on) or never (off)? + +## dispatch.propagation-auditor-tier + +scope: team +values: cheapest | mid | most-capable +default: cheapest +read-by: working-process/rules/workflow.md, working-process/agents/propagation-auditor.md +question: Which tier runs the propagation gate — cheapest, mid or most-capable? + +## docs-branch.merge + +scope: team +values: squash | fast-forward +default: unset +read-by: working-process/rules/spec-plan-lifecycle.md +question: How does the topic branch take the .docs branch at the implementation-ready gate — squash or fast-forward? + +## consult.personas + +scope: personal +values: yes | no +default: unset +read-by: working-process/rules/workflow.md +question: May the architect and system-designer personas be consulted as a design forms? + +## review.autonomy + +scope: personal +values: yes | no +default: unset +read-by: working-process/rules/workflow.md +question: May the review loop run autonomously, within the round cap? + +## review.per-round-commit + +scope: personal +values: yes | no +default: unset +read-by: working-process/rules/workflow.md, working-process/rules/spec-plan-lifecycle.md +question: May the review loop commit the reviewed document once per round? +``` + +The `read-by:` lists name files that cite the key only after Tasks 7–12 +land; Task 14 checks the citations, this task checks that the files +exist. + +- [ ] **Step 4: Run the test to see it pass** + +Run: `python3 -m unittest discover -s tests/working-process -p 'test_settings_registry.py' -v` +Expected: 3 tests, `OK`. + +Then confirm the loader's view of the same file — every heading is a +key and every key has exactly five fields: + +```bash +F=plugins/working-process/SETTINGS_REGISTRY.md +echo "A $(grep -c '^## ' $F)" +echo "B $(grep -Ec '^(scope|values|default|read-by|question): ' $F)" +echo "C $(grep -c '^default: unset$' $F)" +``` + +Expected: `A 14`, `B 70`, `C 13`. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/SETTINGS_REGISTRY.md tests/working-process/test_settings_registry.py +git commit -m "feat(working-process): settings key registry" +``` + +--- + +### Task 2: The loader — hook mode, `--print`, the block and the grammar + +**Files:** +- Create: `plugins/working-process/scripts/load-settings.sh` +- Create: `tests/working-process/test_load_settings.py` +- Modify: `plugins/working-process/hooks/hooks.json` — a second + SessionStart handler. + +**Interfaces:** +- Consumes: the registry from Task 1, at + `$(dirname "$0")/../SETTINGS_REGISTRY.md`. +- Produces: the command `load-settings.sh` (hook mode) and + `load-settings.sh --print`; the block's first line, key lines, + `error:` lines and `incomplete:` line exactly as *The block* below + prints them — Task 6's rule and Task 13's skill quote them; the test + helpers `LoaderTest`, `settings()`, `run()`, `git_init()` that + Tasks 3–5 extend. + +**Realizes:** D1, D2, D3, D4, D9, D10, D11, D28, D32, D33, D34, D36, D42, D44 + +This task is code, so its steps are test-driven. The contract below is +the requirement for the reading side; Tasks 3–5 add the worktree +layouts, `--validate` and `--set` to the same script. The script is +one file; keep its functions named as the contract names them so the +later tasks extend rather than restructure. + +**The contract — reading.** + +- **Invocation.** No argument: hook mode. `--print`: the same block on + stdout, uncapped, exit 0. Any other argument list not defined by + Tasks 4 and 5: a one-line usage message on stderr, exit 2 — except in + hook mode, which never prints to stderr and never exits non-zero. + The script begins `#!/bin/sh`, `set -u`, `export LC_ALL=C`, and + never reads stdin. +- **Paths.** `LOADER` is the script's own absolute path, + `$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")`; `PLUGIN` is + `$(cd "$(dirname "$LOADER")/.." && pwd -P)`; `REGISTRY` is + `$PLUGIN/SETTINGS_REGISTRY.md`. `CLAUDE_PLUGIN_ROOT` is not read. +- **Root.** `find_root` sets `ROOT` to `git rev-parse --show-toplevel` + when it succeeds (in a worktree, the worktree's own root), else + `$CLAUDE_PROJECT_DIR` when set and non-empty, else `$PWD`, each made + physical with `pwd -P`. `DIR` is `$ROOT/.working-process`, `TEAM` + `$DIR/settings.md`, `LOCAL` `$DIR/settings.local.md`. Task 3 adds the + layouts; in this task `LAYOUT` is always `main` and `LOCAL_LABEL` is + `settings.local.md`. +- **The registry.** `registry_keys` prints every `## ` heading's key in + file order; `registry_field ` prints the field's value + (`sed -n` from the key's heading to the next heading, then the + `: ` line, value after the two characters `: `). A registry + that is missing or yields no key is a loader error: hook mode prints + nothing and exits 0; every other mode prints + `error: registry not found or empty at ` on stderr and + exits 1. +- **Silence.** In hook mode, when `$DIR` is not a directory, print + nothing, exit 0. `--print` then prints exactly one line, + `working-process settings (root: ; loader: ; no settings directory)`, + and exits 0. +- **The grammar** (`scan_file `, one awk pass). A fence + toggles on a line matching `^```` `. Outside a fence, a line matching + the ERE `^[a-z0-9-]+\.[a-z0-9./-]+:` is a settings line; its key is + the text before the first `:`; it is valid when the whole line + matches `^[a-z0-9-]+\.[a-z0-9./-]+:[ \t]*[A-Za-z0-9._/-]+[ \t\r]*$`, + its value the token between; otherwise it is invalid and its raw + value is the text after the colon with spaces, tabs and a trailing + carriage return stripped, shown as `(empty)` when nothing remains. + Every other line — leading whitespace included — is commentary. The + pass prints one record per settings line: + `\t\t\t`. A missing + file prints no record. +- **Resolution** (`resolve`), per registered key in registry order: + - the deciding file is `$TEAM` for a team key, `$LOCAL` (or its + fallback, Task 3) for a personal key; the other file is the + foreign file; + - records in the deciding file for the key: none → the default + (` [default]`; for a `dir.` key other than `dir.default`, + `dir.default`'s effective value — `unset` included — with + `[inherited]`); exactly one, valid, value among `values:` → the + value with `[team]` or `[local]`; exactly one, invalid or a value + not among `values:` → `unset [invalid in team|local]` and + `error: : invalid value for ``: — unset`; + more than one → `unset [invalid in team|local]` and + `error: :,[,…] duplicate key `` — unset`, at + most one error line per key per file, the duplicate taking + precedence over an invalid value; + - records in the foreign file for a personal key (the team file): + exactly one and valid → where the key resolved to `unset [default]`, + the line becomes `unset [team suggests: ]`; where the + personal file decided it, nothing is shown; invalid → an `error:` + line ending `— ignored` and no suggestion; more than one → a + duplicate `error:` line ending `— ignored` and no suggestion; + - records in the foreign file for a team key (the personal file): + each → `error: settings.local.md: team key `` in the personal file — ignored`; + - a settings line whose key is not registered, in either file → + `error: : unknown key `` — ignored`. + Error lines come in file order, team file first, then line order; + the `` token is `settings.md` or `settings.local.md`, never a + path. +- **The block** (`emit_block`): the first line, + `working-process settings (root: ; loader: ; team: ; local: )`, + where a label is the file's basename followed by ` (absent)` when the + file does not exist (Task 3 adds the layout qualifiers, inside the + same parentheses, before `absent`); then one line per registered key + in registry order, `: []` — two spaces before + the bracket; then the `error:` lines. Nothing else, no blank lines. +- **The cap** (`cap_block`, hook mode only): 4096 bytes over the whole + output, the `incomplete:` line included. Lines are emitted whole, in + order, while the bytes so far plus the next line plus the length of + `incomplete: run --print` plus its newline stay at or under + 4096; where a line would exceed it, that line and every later one are + dropped and `incomplete: run --print` closes the block. The + first line is emitted whatever its length. `--print` never caps. +- **Errors of the loader's own.** Hook mode wraps everything: any + failure — registry missing, awk absent, an unreadable file — ends in + no output and exit 0, the way `check-rules-drift.sh` ends. The block + is assembled in a variable and printed once at the end, so a failure + midway prints nothing partial. + +- [ ] **Step 1: Write the failing tests** + +`tests/working-process/test_load_settings.py`. The helpers below are +the module; Tasks 3–5 add methods and cases to it and change none of +these: + +```python +"""Tests for plugins/working-process/scripts/load-settings.sh. + +Each test builds a project in a temporary directory, runs the loader +through /bin/sh from a chosen working directory, and asserts the exact +lines the contract names. +""" + +from __future__ import annotations + +import hashlib +import os +import subprocess +import unittest +import tempfile +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] +LOADER = (REPO / "plugins" / "working-process" / "scripts" / "load-settings.sh").resolve() +REGISTRY = REPO / "plugins" / "working-process" / "SETTINGS_REGISTRY.md" +GIT = ["git", "-c", "user.name=fixture", "-c", "user.email=fixture@example.invalid", + "-c", "init.defaultBranch=main", "-c", "commit.gpgsign=false"] +KEYS = [ # registry order + "dir.default", "dir.docs/specs", "dir.docs/technical-designs", "dir.docs/plans", + "dir.docs/domain", "dir.docs/code-review", "dir..superpowers", "dir.docs/memory", + "design.technical-design-offer", "dispatch.propagation-auditor-tier", + "docs-branch.merge", "consult.personas", "review.autonomy", "review.per-round-commit", +] + + +def tree_hash(root: Path) -> str: + """One digest over every file's path and bytes under root, for byte-identity checks.""" + digest = hashlib.sha256() + for path in sorted(p for p in root.rglob("*") if p.is_file()): + digest.update(str(path.relative_to(root)).encode()) + digest.update(path.read_bytes()) + return digest.hexdigest() + + +class Result: + def __init__(self, proc: subprocess.CompletedProcess[str]) -> None: + self.proc = proc + self.out = proc.stdout + self.err = proc.stderr + self.code = proc.returncode + self.lines = proc.stdout.splitlines() + self.errors = [l for l in self.lines if l.startswith("error: ")] + + def line(self, key: str) -> str: + """The block line for `key`, or an AssertionError.""" + for l in self.lines: + if l.startswith(f"{key}: "): + return l + raise AssertionError(f"no line for {key} in {self.lines}") + + +class LoaderTest(unittest.TestCase): + def setUp(self) -> None: + self._tmp = tempfile.TemporaryDirectory() + self.base = Path(self._tmp.name).resolve() + self.root = self.base / "proj" + self.root.mkdir() + + def tearDown(self) -> None: + self._tmp.cleanup() + + def git(self, cwd: Path, *args: str) -> str: + proc = subprocess.run(GIT + list(args), cwd=cwd, capture_output=True, text=True) + self.assertEqual(proc.returncode, 0, proc.stderr) + return proc.stdout + + def git_init(self, path: Path | None = None) -> Path: + """A repository with one empty commit at `path` (default: self.root).""" + path = path or self.root + path.mkdir(parents=True, exist_ok=True) + self.git(path, "init", "-q") + self.git(path, "commit", "-q", "--allow-empty", "-m", "fixture") + return path + + def settings(self, root: Path, team: str | None = None, local: str | None = None, + gitignore: str | None = None) -> Path: + """Create root/.working-process with the files given (None = absent).""" + d = root / ".working-process" + d.mkdir(parents=True, exist_ok=True) + for name, text in (("settings.md", team), ("settings.local.md", local), (".gitignore", gitignore)): + if text is not None: + (d / name).write_text(text, encoding="utf-8") + return d + + def run_loader(self, cwd: Path, *args: str, project_dir: str | None = None, + loader: Path = LOADER) -> Result: + env = {k: v for k, v in os.environ.items() if k not in ("CLAUDE_PROJECT_DIR", "CLAUDE_PLUGIN_ROOT")} + if project_dir is not None: + env["CLAUDE_PROJECT_DIR"] = project_dir + proc = subprocess.run(["/bin/sh", str(loader), *args], cwd=cwd, env=env, + capture_output=True, text=True, stdin=subprocess.DEVNULL) + return Result(proc) + + def first_line(self, root: Path, team: str = "settings.md", local: str = "settings.local.md", + loader: Path = LOADER) -> str: + return f"working-process settings (root: {root}; loader: {loader}; team: {team}; local: {local})" +``` + +Then the cases, one test method each (or a `subTest` table where the +cases share a shape), asserting the exact lines: + +1. **Precedence and defaults.** `git_init()`; team `dir.default: tracked\n`, + local `review.autonomy: yes\n`; hook mode from `self.root` → exit 0, + empty stderr, `lines[0] == first_line(root)`, then exactly the 14 key + lines in `KEYS` order and nothing after; among them + `dir.default: tracked [team]`, `dir.docs/specs: tracked [inherited]`, + `dir.docs/memory: tracked [inherited]`, + `dispatch.propagation-auditor-tier: cheapest [default]`, + `consult.personas: unset [default]`, `review.autonomy: yes [local]`. + `--print` from the same place prints the identical text. +2. **Suggestion.** Team `consult.personas: yes\n`, no local file → + `consult.personas: unset [team suggests: yes]`, no `error:` line, + first line ends `local: settings.local.md (absent))`. With local + `consult.personas: no\n` → `consult.personas: no [local]` and the + string `suggests` appears nowhere in the output. +3. **Invalid in the deciding file.** Team `consult.personas: yes\n`, + local `review.autonomy: noo\nconsult.personas: "no"\n` → + `review.autonomy: unset [invalid in local]`, + `consult.personas: unset [invalid in local]`, and the errors, in + this order: + ``` + error: settings.local.md:1 invalid value for `review.autonomy`: noo — unset + error: settings.local.md:2 invalid value for `consult.personas`: "no" — unset + ``` + The `consult.personas` line contains neither `yes` nor `suggests`. +4. **Duplicate.** Local `review.autonomy: yes\n\nreview.autonomy: no\n` + → `review.autonomy: unset [invalid in local]` and + `error: settings.local.md:1,3 duplicate key `review.autonomy` — unset`; + a duplicate where one line is also invalid gives the duplicate line + alone. +5. **Unknown key, and a team key in the personal file.** Team + `review.autonmy: yes\n` → + `error: settings.md:1 unknown key `review.autonmy` — ignored`; local + `dir.default: ignored\n` → + `error: settings.local.md:1 team key `dir.default` in the personal file — ignored` + and `dir.default: unset [default]`. +6. **Inheritance.** Team `dir.default: ignored\ndir.docs/plans: tracked\n` + → `dir.docs/plans: tracked [team]`, `dir.docs/specs: ignored [inherited]`, + `dir..superpowers: ignored [inherited]`. Team `dir.default: bogus\n` + → `dir.default: unset [invalid in team]`, + `dir.docs/specs: unset [inherited]`. Team + `dir.default: tracked\ndir.docs/specs: bogus\n` → + `dir.docs/specs: unset [invalid in team]`. +7. **Fences and commentary.** Team file: + ```` + # Team settings + Note: dir.default: ignored + dir.default: ignored + ``` + dir.default: ignored + ``` + dir.default: tracked + ```` + → `dir.default: tracked [team]` and no `error:` line. The same file + with the last line removed → `dir.default: unset [default]`. +8. **Whitespace.** Team `dir.default:\ttracked \r\n` → `[team]`; team + `dir.default : tracked\n` → no settings line (commentary), so + `[default]` and no error; team `dir.default:\n` → + `error: settings.md:1 invalid value for `dir.default`: (empty) — unset`. +9. **Absent files and the first line.** `.working-process/` with only a + team file → first line ends `team: settings.md; local: settings.local.md (absent))`; + with only a local file → `team: settings.md (absent); local: settings.local.md)`. + No `.working-process/` at all: hook mode → `out == ""`, exit 0; + `--print` → exactly one line, + `working-process settings (root: {root}; loader: {LOADER}; no settings directory)`, + exit 0. +10. **Root resolution.** No git, `project_dir=str(self.root)`, cwd a + subdirectory `self.root / "sub"` → the first line names `self.root`. + No git and no project dir, cwd `self.root / "sub"` → names + `self.root / "sub"`. `git_init()`, cwd `self.root / "sub"`, project + dir pointing elsewhere → names `self.root` (git wins). +11. **An error of the loader's own.** Copy `LOADER` to + `self.base / "alone" / "scripts" / "load-settings.sh"` with no + registry beside it; `.working-process/` present in `self.root`; + hook mode → `out == ""`, `err == ""`, exit 0; `--print` → exit 1, + `out == ""`, `err` contains `registry not found or empty`. +12. **The cap.** Team file of 200 lines `zz.k: x` (`i` from 0) → + hook mode: `len(out.encode()) <= 4096`, `out.endswith("\n")`, + `lines[-1] == f"incomplete: run {LOADER} --print"`, `lines[0]` is + the first line, `0 < len(errors) < 200`, and every error line is + one of the 200 the file produces (none cut mid-line). `--print` → + `len(errors) == 200` and no `incomplete:` line. +13. **Usage.** `--nonsense` → exit 2, `out == ""`, `err` one line + starting `usage:`. `--print extra` → exit 2. + +- [ ] **Step 2: Run the tests to see them fail** + +Run: `python3 -m unittest discover -s tests/working-process -p 'test_load_settings.py' -v 2>&1 | tail -n 5` +Expected: every test fails or errors — the script does not exist, so +`/bin/sh` exits 127. + +- [ ] **Step 3: Write the script** + +`plugins/working-process/scripts/load-settings.sh`, executable +(`chmod +x`), opening with `#!/bin/sh` and a comment block that names +the contract's home — the spec-plan-lifecycle rule is not it: the +process-settings rule (Task 6) defines the files, the grammar and the +block; the registry defines the keys; this plan's Tasks 2–5 the modes. +Match the contract above; add nothing it does not name. The functions: +`usage`, `die` (stderr + exit 1, or silent exit 0 in hook mode), +`find_root`, `registry_keys`, `registry_field`, `scan_file`, +`resolve`, `emit_block`, `cap_block`, `main`. The block is built in a +variable and printed once. + +- [ ] **Step 4: Run the tests to see them pass** + +Run: `python3 -m unittest discover -s tests/working-process -v` +Expected: every test in all three files passes. + +Then run the script under dash by hand from this repository, which has +no `.working-process/`: + +```bash +/bin/sh plugins/working-process/scripts/load-settings.sh; echo "rc=$?" +/bin/sh plugins/working-process/scripts/load-settings.sh --print; echo "rc=$?" +``` + +Expected: the first prints only `rc=0`; the second prints one line +ending `no settings directory)` and `rc=0`. + +- [ ] **Step 5: Register the hook** + +Replace the whole of `plugins/working-process/hooks/hooks.json` with: + +```json +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/check-rules-drift.sh\"" + } + ] + }, + { + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/load-settings.sh\"" + } + ] + } + ] + } +} +``` + +No `matcher` on either group, so both run on startup, resume, clear and +compaction. The loader is its own group rather than a second entry in +the first, since Codex budgets context per handler. + +Verify: + +```bash +python3 -c "import json; h=json.load(open('plugins/working-process/hooks/hooks.json'))['hooks']['SessionStart']; print(len(h), [g['hooks'][0]['command'].split('/')[-1] for g in h], all('matcher' not in g for g in h))" +claude plugin validate plugins/working-process +``` + +Expected: `2 ['check-rules-drift.sh"', 'load-settings.sh"'] True`, and +validation passes. + +- [ ] **Step 6: Commit** + +```bash +git add plugins/working-process/scripts/load-settings.sh tests/working-process/test_load_settings.py plugins/working-process/hooks/hooks.json +git commit -m "feat(working-process): settings loader with hook mode and --print" +``` + +--- + +### Task 3: The loader — worktree and bare-repository layouts + +**Files:** +- Modify: `plugins/working-process/scripts/load-settings.sh` — + `find_root` gains the layout, `resolve` the fallback, `emit_block` + the qualifiers. +- Modify: `tests/working-process/test_load_settings.py` — the layout + helpers and cases 14–18. + +**Interfaces:** +- Consumes: Task 2's script and helpers. +- Produces: `LAYOUT` (`main` | `worktree` | `bare-worktree` | `none`) + and `MAIN` (the main checkout's root), which Task 5's destination + logic reads; the `LOCAL_LABEL` qualifiers Task 6 quotes; the helpers + `worktree()` and `bare()` Task 5's tests reuse. + +**Realizes:** D5, D28, D41 + +**The contract — layouts.** + +- `find_root` runs `git worktree list --porcelain` from `$ROOT` when git + resolved the root. The first entry is the main worktree. Where its + second line is `bare`, `LAYOUT=bare-worktree` and `MAIN` is empty. + Where its path, made physical, equals `$ROOT`, `LAYOUT=main`. + Otherwise `LAYOUT=worktree` and `MAIN` is that path. Where git did + not resolve the root, `LAYOUT=none`. Paths are compared after + `(cd "$p" && pwd -P)`, so a symlinked temporary directory compares + equal on macOS. +- The personal file the loader reads (`LOCAL_READ`) and its label: + - `main` or `none`: `$ROOT/.working-process/settings.local.md`, + label `settings.local.md`; + - `worktree`, the worktree's own file present: + the worktree's, label `settings.local.md (worktree, shadows main checkout)`; + - `worktree`, no own file: `$MAIN/.working-process/settings.local.md`, + label `settings.local.md (main checkout)`; + - `bare-worktree`: the worktree's own, label + `settings.local.md (worktree, bare repository)`. + Where the file the label names does not exist, `, absent` is added + inside the parentheses — `settings.local.md (main checkout, absent)`, + `settings.local.md (worktree, bare repository, absent)` — or + ` (absent)` where there were none. The team file is always + `$ROOT/.working-process/settings.md`. +- Error lines from the fallback file still name `settings.local.md`. +- The fallback is read only for the personal file: a worktree without + `.working-process/` at all is silent in hook mode even when the main + checkout has settings, because `$DIR` is the worktree's. + +- [ ] **Step 1: Write the failing tests** + +Add to `LoaderTest`: + +```python + def worktree(self, main: Path, name: str) -> Path: + """A linked worktree of `main` at main.parent/name on a new branch.""" + path = main.parent / name + self.git(main, "worktree", "add", "-q", str(path), "-b", name) + return path + + def bare(self, src: Path, name: str = "bare.git", worktree: str = "bwt") -> Path: + """A bare clone of `src` with one linked worktree; returns the worktree.""" + bare = src.parent / name + self.git(src.parent, "clone", "-q", "--bare", str(src), str(bare)) + path = src.parent / worktree + self.git(bare, "worktree", "add", "-q", str(path), "main") + return path +``` + +Cases: + +14. **Fallback.** `main = git_init()`; `settings(main, team="dir.default: tracked\n", local="review.autonomy: yes\n")`; + `wt = worktree(main, "wt")`; `settings(wt, team="dir.default: ignored\n")`; + run from `wt` → `review.autonomy: yes [local]`, + `dir.default: ignored [team]`, first line + `first_line(wt, local="settings.local.md (main checkout)")`. +15. **Fallback absent.** As 14 without the main's local file → first + line `local="settings.local.md (main checkout, absent)"` and + `review.autonomy: unset [default]`. +16. **Shadow.** As 14 plus `local="review.autonomy: no\n"` in `wt` → + `review.autonomy: no [local]`, first line + `local="settings.local.md (worktree, shadows main checkout)"`, and + `yes` appears on no line. +17. **Bare.** `src = git_init()`; `bwt = bare(src)`; `settings(bwt, team="dir.default: tracked\n")`; + run from `bwt` → first line + `local="settings.local.md (worktree, bare repository, absent)"`; + then with `local="review.autonomy: yes\n"` in `bwt` → + `review.autonomy: yes [local]` and + `local="settings.local.md (worktree, bare repository)"`. +18. **Worktree without a settings directory.** `main` with settings, + `wt` without `.working-process/` → hook mode from `wt` prints + nothing, exit 0; `--print` prints the one `no settings directory` + line naming `wt`. + +- [ ] **Step 2: Run the tests to see them fail** + +Run: `python3 -m unittest discover -s tests/working-process -p 'test_load_settings.py' -v 2>&1 | tail -n 5` +Expected: cases 14–17 fail on the first line or the fallback value; +case 18 and every Task 2 case pass. + +- [ ] **Step 3: Extend the script** + +Add the layout to `find_root`, `LOCAL_READ` and `LOCAL_LABEL` to +`resolve`/`emit_block` as the contract says. `git worktree list +--porcelain` runs once; its first two lines are all `find_root` reads. + +- [ ] **Step 4: Run the tests to see them pass** + +Run: `python3 -m unittest discover -s tests/working-process -v` +Expected: every test passes. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/scripts/load-settings.sh tests/working-process/test_load_settings.py +git commit -m "feat(working-process): settings loader reads the main checkout's personal file from a worktree" +``` + +--- + +### Task 4: The loader — `--validate` + +**Files:** +- Modify: `plugins/working-process/scripts/load-settings.sh` — + `do_validate`. +- Modify: `tests/working-process/test_load_settings.py` — cases 19–21. + +**Interfaces:** +- Consumes: `scan_file` and `registry_field` from Task 2. +- Produces: the command + `load-settings.sh --validate --scope team|personal `, its + `error:`, `notice:` and summary lines, and its exit codes, which + Task 13's skill names for a hand-edited file. + +**Realizes:** D28, D34, D46 + +**The contract — validation.** + +- `--validate --scope `: the two options in that + order, then one path. Anything else, a scope other than the two, or a + file that cannot be read → `usage:` on stderr, exit 2. +- The file is scanned as the deciding file of its scope. Each finding + is one line on stdout, in line order: + - `error: : unknown key `` — ignored`; + - `error: : invalid value for ``: — unset` + for a key of the file's scope; `— ignored` for a personal key in a + file validated as `team`; + - `error: :,[,…] duplicate key `` — unset`, or + `— ignored` for a personal key in a file validated as `team`, once + per key; + - `error: : team key `` in the personal file — ignored` + for every line of a team key in a file validated as `personal`, + whatever its value, and no other line for it; + - `notice: : personal key `` in the team file — a suggestion` + when the scope is `team` and the line is valid. +- The last line is `: error(s), notice(s)`, written + with the literal words `errors` and `notices` (`0 errors, 1 notices` + is accepted ugliness over a plural rule). Exit 1 when `e > 0`, else + 0. A notice never fails. +- `` is the file's basename as given, so a candidate file + named otherwise (`settings.md.new`) reports under its own name. + +- [ ] **Step 1: Write the failing tests** + +19. **A suggestion passes.** File `t.md` = `consult.personas: yes\ndir.default: tracked\n` + with `--scope team` → exit 0, stdout exactly: + ``` + notice: t.md:1 personal key `consult.personas` in the team file — a suggestion + t.md: 0 errors, 1 notices + ``` +20. **Each error kind fails.** One `subTest` per row; every row exits 1 + and its stdout contains the line given: + - `--scope team`, `dir.default: bogus\n` → + `error: t.md:1 invalid value for `dir.default`: bogus — unset`; + - `--scope team`, `dir.default: tracked\ndir.default: ignored\n` → + `error: t.md:1,2 duplicate key `dir.default` — unset`; + - `--scope team`, `review.autonmy: yes\n` → + `error: t.md:1 unknown key `review.autonmy` — ignored`; + - `--scope personal`, `dir.default: tracked\n` → + `error: t.md:1 team key `dir.default` in the personal file — ignored`; + - `--scope team`, `consult.personas: maybe\n` → + `error: t.md:1 invalid value for `consult.personas`: maybe — ignored` + and no `notice:` line. + Each row's last line is `t.md: 1 errors, 0 notices`. +21. **Usage.** `--validate t.md` (no scope), `--validate --scope both t.md`, + `--validate --scope team missing.md` → exit 2, `out == ""`, `err` + starts `usage:`. + +- [ ] **Step 2: Run the tests to see them fail** + +Run: `python3 -m unittest discover -s tests/working-process -p 'test_load_settings.py' -v 2>&1 | tail -n 5` +Expected: cases 19 and 20 fail with exit 2 (the mode is unknown); +case 21 passes by accident and stays. + +- [ ] **Step 3: Extend the script** + +Add `do_validate` and its dispatch in `main`, reusing `scan_file` and +the registry lookups. The mode never consults `$ROOT`. + +- [ ] **Step 4: Run the tests to see them pass** + +Run: `python3 -m unittest discover -s tests/working-process -v` +Expected: every test passes. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/working-process/scripts/load-settings.sh tests/working-process/test_load_settings.py +git commit -m "feat(working-process): settings loader validates a file for its scope" +``` + +--- + +### Task 5: The loader — `--set` and `--set --dry-run` + +**Files:** +- Modify: `plugins/working-process/scripts/load-settings.sh` — + `do_set`, `destination`, `plan_write`, `apply_write`. +- Modify: `tests/working-process/test_load_settings.py` — cases 22–34. + +**Interfaces:** +- Consumes: `LAYOUT`, `MAIN`, `LOCAL_READ` from Task 3; `scan_file`, + `registry_field`, `emit_block` from Task 2. +- Produces: the commands `load-settings.sh --set ` and + `load-settings.sh --set --dry-run `, their output lines + and exit codes, which Task 6's rule, Task 13's skill and the "and + record" answers in Tasks 7–12 call. + +**Realizes:** D24, D28, D37, D41, D43, D49, D50 + +**The contract — writing.** + +- `--set ` and `--set --dry-run `; any other + shape → `usage:`, exit 2. `--set` takes no scope: the registry fixes + it. +- **Validation first.** An unregistered key → + `error: unknown key ``` on stderr, exit 1. A value not among the + key's `values:` → `error: invalid value for ``: (allowed: )` + on stderr, exit 1. Nothing is created before both pass. +- **Destination** (`destination`): a team key → `$ROOT/.working-process/settings.md` + in the checkout the command runs in, label `settings.md`. A personal + key: `LAYOUT` `main` or `none` → `$ROOT/.working-process/settings.local.md`, + label `settings.local.md`; `worktree` → `$MAIN/.working-process/settings.local.md`, + label `settings.local.md (main checkout)`, and where the current + worktree has a personal file of its own, the note + `note: settings.local.md in this worktree shadows the main checkout; this write will not change the current worktree's answer` + is printed first; `bare-worktree` → the worktree's own file, label + `settings.local.md (worktree, bare repository)`. Nothing is ever + written inside a bare repository. +- **The plan** (`plan_write`), computed from the destination's current + content, which may not exist: with no record for the key — *insert*: + append, after a blank line where the file is non-empty and its last + line is not blank, the key's `question:` text as one commentary line, + then `: `; a file that does not exist is created with + the title line `# working-process settings` (team) or + `# working-process settings — personal` (personal), a blank line, + then the question and the key line (Deviation 5). With exactly one + record — *replace* that line in place with `: `, every + other byte untouched; where the line already reads exactly + `: ` — *unchanged*, no write. With several records — + *replace the first* in place and *remove* each later record's line, + together with the line directly above it when that line equals the + key's `question:` text exactly; other lines, blank lines included, + stay. +- **The `.gitignore`** beside a personal destination: absent → created + holding the single line `settings.local.md`; present without a line + equal to `settings.local.md` → that line appended (Deviation 4); + present with it → untouched. Never touched for a team key. +- **Other errors** in the destination file (D49) are reported as the + block would report them — `error: : …` lines, computed + before the write — and never block it. +- **Output** of `--set`, on stdout, in this order: the shadow note if + any; the `error:` lines for other lines; one + `removed