diff --git a/.gitignore b/.gitignore index 92dad75..f4fea79 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,10 @@ _unrelated-seo-work/ # passCount hands every fresh clone a pre-counted Gate-B pass. .context/* !.context/codex-gate.on + +# Local drafts and reading notes — working material, not repo content. `docs/field-reports/` +# is deliberately NOT here: field reports are tracked, because a field-intake round cites +# them as its evidence. These two replace a per-clone `.git/info/exclude`, so the policy +# travels with the repo instead of living in one checkout. +docs/ideas/ +docs/research/ diff --git a/CLAUDE.md b/CLAUDE.md index c78de2c..4458020 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,16 +69,129 @@ yours — a non-blocking hook (shipped by the `dev-workflow` plugin) reminds you each. Opt out per-workspace with `.context/codex-gate.off` (delete to re-enable); the gates still apply. -**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major -only), counted by the hook.** The hook counts passes but can't read findings or -tell the spec run from the plan run (it resets at `writing-plans`), so Gate A — -the spec run especially — is instruction-backed: a satisfied count is not a clean -review. Open a TodoWrite "Codex pass N" per pass; fix Blocker/Major after each. Your -final pass must be clean — if pass 3 still finds Blocker/Major, keep going until +**Both gates are a LOOP with a HARD FLOOR: a minimum number of passes per run +(Blocker/Major only), derived from the cited story's profile.** +The derivation is max(risk, security): a value of 0 gives a floor of 1; every +resolvable profile above that, and an artifact citing no story, gives 3. Two levels, +not three — `high` takes its rigor from lens sets and evidence mode, not from extra +passes. A cited story whose profile is present but unresolvable stops and surfaces +under the existing rule; it does not fall through to 3, because reading it as 3 would +turn a stop condition into a silent default. Across a cited set the floor is 1 if and +only if the set is non-empty and every member is profiled, resolvable and at level 0 +— all four conditions, since "every cited story" is vacuously true of an empty set; +no story cited, or any cited story unprofiled, gives 3. One derived value governs all +three cycle *kinds*: the Gate-A spec loop, the Gate-A plan loop and the Gate-B cycle. Not +because they are one cycle — they are separate cycles, and a change carrying several plans runs +a Gate-A plan cycle per plan — but because they derive from the same +cited-story set. That value is a function of the current confirmed profiles of that set, +read fresh wherever this section already requires them to be read, so wherever a value can be +derived at all there is exactly one, because there is one source. Some states derive **no** value +rather than a second one, and each stops rather than defaulting: governing headers that +disagree; a cited profile that is present but unresolvable; and a `Story:` header that cannot +be read. A change to a profile or to the set +therefore binds every open and future cycle — a raise costs an affected open cycle a +further pass under the current profile, as the profile-change rule below requires — while a +cycle that has already closed +stands, its close having been valid under the profile current when it closed, which is the +cycle-level form of passes already run keeping their count. **The set has one authority: +the artifact's `Story:` header, which carries the path of every cited story.** Nothing else +is a citation. A story path appearing anywhere else in an artifact's body — including a +sentence placing a story *outside* this change's scope — **contributes nothing to the cited +set and nothing to the floor derivation**, which is the only claim made about it; it may still +be a perfectly good cross-reference for any other purpose. And **an agent +deriving the set reads that header and does not grep the body for story paths**, because a +grep finds mentions and cannot tell a citation from a disclaimer. **Each cycle's governing header is the +header of the artifact it reviews**: the spec's for the Gate-A spec loop, the plan's for the +Gate-A plan loop, and — since a Gate-B cycle reviews a diff and has no header of its own — +**the union of the `Story:` headers of every plan contributing to that diff, which the Gate-B +call must carry in full**, as this section already requires of every cited path. **Every expected artifact contributes a set — a spec, and every plan contributing to the +reviewed diff — and an expected artifact whose `Story:` header is absent contributes the empty +set rather than dropping out of the comparison.** **One path per entry**, and "entry" is +decidable against the form these artifacts actually carry: a single line beginning `**Story:**`, +then one or more paths, **each wrapped in backticks**, separated by `, `. Trailing prose after +the last path is allowed and contributes nothing — several headers carry a reminder to read the +profile fresh, and a reminder is not a citation. So a header citing several stories carries several entries, +one path each. Exact duplicate paths are one member; entries naming different stories are +different members; and a header that does not parse as that line is malformed and stops, +reporting that as the cause rather than as a disagreement. **Before each pass the deriving agent +compares every such set, and again before a clean pass is accepted as the cycle's final pass.** +A header or profile that changed during that pass means the pass is not final — the same +answer a change gets at every other read point. Where they name different sets the premise of a single value has +failed: **stop and surface the disagreement** rather than deriving from either, exactly as an +unresolvable profile stops rather than defaulting. + +**The derived floor is the pass count a cycle owes, and the hook's ratio is a reminder +threshold that controls nothing.** The hook still counts passes, and it still can't read +findings or tell the spec run from the plan run (it resets at `writing-plans`), so +Gate A — the spec run especially — is instruction-backed: a satisfied count is not a +clean review, and a below-threshold reminder is noted in the pass report and disregarded +where the cycle's own closure rules are satisfied. This replaces the pass-count number +and nothing else. Every other rule stated here about how a cycle closes stands as +written, and none of them is restated — a summary is where their conditions would get +dropped. Nothing here writes the floor knob: it stays the user's, never written, never +removed, never read for this derivation. Open a TodoWrite "Codex pass N" per pass; fix Blocker/Major after each. Your +final pass must be clean — if the pass at the floor still finds Blocker/Major, keep going until clean or clearly stuck → then STOP and surface to the user. The only early exit -below 3 is a pass with **zero** findings; don't manufacture findings to pad. Codex is +below the floor is a pass with **zero** findings; don't manufacture findings to pad. Codex is advisory — validate before applying; dismissed finding → one-line why. +**Named residual:** the hook's messages state its own threshold as an obligation, so at a +floor of 1 they report a shortfall the cycle does not owe. Hook text is out of scope here +by decision; what makes that tolerable is the precedence rule above plus the hook exiting +0 on every branch, not the reminder being harmless. + +**The gate-off surface — routes known today, not a complete list**, since an enumeration +read as complete guarantees the routes it omits. One route is created here: a stated floor +the cited set does not license, which could not exist before there was a derived floor to +state. Pre-existing and unchanged: omitting a higher-risk cited story; minting or editing a +profile to level 0; presenting an incomplete cited set; falsifying evidence entries; +silencing reminders; or not running a pass and reporting that it ran. A user-set floor is +not the lever — it moves what the hook says, not what the cycle owes. +None of this is a guard: the floor is produced by the agent and nothing checks it against +the cited profiles. + +**When these rules bind.** From the commit that ships them, and a cycle already running +finishes under the rules it started with. Where a cycle's starting rules cannot be +established it takes the stricter reading of every part this change touches — at minimum +floor 3, severity classified without the demotion, the provenance-line duty owed, the curve +duty owed, and the nonce duties at their strictest — the cycle is treated as post-rule, so it +owes a nonce, owes its provenance line and its curve or skip record, and uses that nonce in every +cycle record it does write — which changes what a record is named, never whether one is owed, so +the working record stays optional and a skipped cycle still writes no findings slots. Where it +cannot recover a nonce it starts a new cycle rather than claiming `none (pre-rule)`, that reserved +field being unavailable to a cycle whose start cannot be established. Each further rule this change ships adds its own strict +reading to this list. Not a re-derivation, which could hand a level-0 +cycle a floor of 1 and skip passes on the strength of not knowing when it started. A user +knob set above 3 is not lowered by this fallback. A revert is itself a shipping commit for +the old rules, and the activation rule wins wherever the start is determinable; the +fallback covers only where it is not. + +**Downstream has no shipping commit.** Adoption binds from the `/workflow-init` run that +actually writes the text — which may write nothing, be declined, or be merged in part — +so these rules bind only over the text a project's `CLAUDE.md` actually contains, and a +partial adoption can persist undetected. A project taking the floor rule without the +severity test gets a floor whose docs-only question the severity test is what settles. +**A partial adoption can leave a project's floor undefined or self-contradictory.** The rule +is a coherence requirement, stated semantically rather than as a list of spellings, and it +runs in **both** directions: **exactly one definition of the floor must be present, and every +statement that defines or constrains the floor, or makes closing depend on it, must resolve to +that one definition.** **The unknown-start fallback is not a second definition**: it is +explicitly conditional on a cycle's starting rules being undeterminable and governs only that +state, so it coexists with the predicate rather than competing with it. Everything else +likewise keeps its own footing and is **not** required to derive from the floor: **the other closure and stop predicates** — assigned-fix-set membership, a new +structural question, an accepted Blocker or Major, the tell thresholds; **independent reporting +and diagnostic ordinals**, such as a duty owed from a given pass onward; and **the hook's +reminder threshold together with any descriptive or historical pass number**, which say what a +tool reports or what once happened rather than what a cycle owes. Four states break it, and the list is **not exhaustive**: a fixed-number or +specific-pass obligation surviving beside the derived predicate; a claim or dependency on a +derived floor with no predicate to define it; **no definition at all**; and **two definitions +at once**. A +merge can produce any of them: the Gate-A loop description, the pass-1 closure rule and the +re-review rationale each carry a fixed-three claim and can be taken or left independently of +the predicate itself. In any such state nothing here resolves which rule governs: **stop, and +have a human complete or revert the adoption, before running a gate under it.** What prompt text can do about downstream +adoption is limited, and that limit is what this paragraph states. + **What a loop absorbs, and what stops it — a question of scope, not of action.** A finding that corrects the correction you just made **and stays inside the assigned fix set** is **inside this loop's scope**: keep it here rather than handing it back, then act on it by its @@ -123,13 +236,22 @@ finish, and it is why **a clean completion takes precedence over this exit**: a Blocker/Major-free pass **at or above the floor** has satisfied the clean-final-pass rule — collect the Minors and Nits and close — and reporting "will not converge" on a converged loop is a false report. **Below the floor nothing closes**, and a zero-finding pass remains -the only exception, exactly as above; a Blocker/Major-free pass 1 carrying a Minor keeps +the only exception, exactly as above; a Blocker/Major-free pass below the floor +carrying a Minor keeps looping. **Surfacing does not close the cycle, and that is what makes this reachable.** You surface *with the finding still open* — the resolve rule is not waived, no pass is credited as clean, and the loop resumes on whatever the user decides. Reading it as "stop instead of fixing" would put the exit in competition with the rule that every Blocker and Major resolves, and then nothing could satisfy both. +**Every pass report states three things about the floor**, from pass 1 onward: the +derived floor, the risk and security values read, and the cited stories they were read +from. A report giving the number alone leaves a reader unable to check the derivation +while passes are still being spent — which is the only time checking it is cheap. Where +no story is cited, or a cited story is unprofiled, the report says so in place of axis +values; a multi-story set names each story and its values. This is owed by every pass; +the three lines below are owed from pass 4 and are a different obligation. + **From pass 4 onward every pass report carries three lines.** The carrier is **your own status report to the user** — never the Codex reply, which stays exactly one line per branch, and never the findings file, which admits no line that is not a finding or the terminator. @@ -177,7 +299,26 @@ Append to the gate prompt: > `.context/codex-reviews/.md` (create the directory if needed; the path is > relative to that root — Codex resolves writes against its working directory, so > without this a valid file can land in a different checkout). `` is -> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

`. +> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

` for a +> cycle with no nonce; a cycle that has one writes `gate-a-spec--pass-

`, +> `gate-a-plan--pass-

` or `gate-b---pass-

` instead, and uses +> the nonce in every slot more than one cycle could write. The bare names are reserved for the +> legacy single-cycle case they already serve. **Distinct-nonce paths coexist by construction and +> are never in conflict** — a sibling cycle's slot is simply a different file. +> +> **The rule binds the deletion step, which is where the damage is done.** This section already +> requires every target to be deleted and confirmed gone before a call. A cycle holding a nonce +> **deletes only paths carrying its own nonce**; it never deletes a bare path or one carrying a +> different nonce, and an attempt to do either **stops and names the path** instead of removing +> it. That is reachable and observable: the step operates on a path it computed, and the check is +> whether that path is the cycle's own. **The case it exists for is a nonce-holding cycle +> computing a bare path** — the legacy spelling — **and deleting a file that belongs to somebody +> else**, which is exactly what happened once. **Two cycles that drew the same nonce compute the +> same paths and are indistinguishable to this rule**; what makes that unlikely is the width of +> the draw, not this rule — this section already stops on a +> target that survives deletion, and this extends that to a target that must not be deleted at +> all. That rule exists because a bare slot was in fact overwritten once, destroying a previous +> cycle's findings file. > > One finding per line in the format above; escape a literal pipe inside a field as > `\|`. @@ -223,6 +364,114 @@ terminator remain the only hard requirement, and a zero-finding pass needs no co Whoever runs the cycle writes it when useful, replaces it as the cycle moves, and deletes it once the cycle closes. Nothing depends on it existing. +**The cycle nonce.** Both shipped records below carry a **cycle field**, because a record that +cannot be attributed to a cycle cannot be told apart from another cycle's when several are read +together. That is a limitation rather than a disqualification — a human reading one cycle's +records knows which cycle they came from; what attribution buys is that a *later* reader +**usually** does not have to. Usually, not always: the guarantee is probabilistic, for the two +reasons stated at the end of this block. This section defines three **kinds** of cycle — the Gate-A spec loop, the Gate-A +plan loop and the Gate-B cycle — and **one cycle field is produced per cycle run, not per +kind**: a change carrying several plans runs a Gate-A plan cycle for each, and each of those is +its own cycle with its own nonce. + +Generated once at cycle start, immutable, and collision-resistant operationally: **8 to 16 +characters drawn uniformly from `[a-z0-9]`, from a source of randomness** — 8 being where +collision resistance starts and 16 where the field stops being a usable infix. **Never derived +from a name, a timestamp or a commit**, each of which collides exactly where sibling cycles do, +which is the one thing the nonce exists to prevent. The character set keeps it safe as a slot +infix and a path component. + +**It appears in every record the cycle writes** — which keeps records apart **as far as distinct +nonces allow**, and no further — **and that set is named rather than left open**: +the provenance line, the per-pass curve (including a skip record standing in for one), the +cycle's findings slots, and its advisory working record. **The working record is a cycle record +too**: a cycle holding a nonce names it `gate-a-spec--resume.md`, +`gate-a-plan--resume.md` or `gate-b--resume.md`, and the bare names above stay +reserved for the legacy single-cycle case, exactly as the findings slots do. **Because recovery +scopes candidates by artifact as well as by kind, the record's contents name that artifact**, and +what counts as the artifact depends on the cycle kind: for a Gate-A cycle it is the reviewed +document's path, quoted by the same rule the provenance line uses where quoting is needed; for a +Gate-B cycle, which reviews a diff rather than a file, it is the **base commit's full +40-character hex object name**, the same value the cycle's reviews are run against. The filename +carries kind and nonce; the artifact key lives inside, where neither a path nor a hex name has to +survive a filename. The nonce is not +required in records this change neither introduces nor keys to a cycle — the evidence entry and +a human-exception record among them. + +**A nonce is a candidate for recovery only if** it is keyed to this cycle's kind — Gate-A spec, +Gate-A plan, or Gate B — **and** this cycle's artifact, **and** that cycle is still open. **Those +three are necessary and not sufficient, and the difference matters**: two Gate-A cycles can review +the same document and two Gate-B cycles commonly share a base commit, so a sole match on kind and +artifact is **not** identity. **A candidate is adopted only if it is positively linked to this +run** — the working record this run itself wrote. A match that is merely consistent is treated as +no identity, and the cycle starts fresh; adopting a sibling on a shared key would merge two +cycles under one nonce, which is the failure this rule exists to prevent. +History normally holds many closed cycles' nonces and they are not candidates; a working record +left by a closed cycle is not one either, which is why that record is **retired at closure** +rather than left to be found later. **Recovery has two sources, and they answer different questions.** The **working record** is the +source while the cycle runs, and it is the one the candidate rules above apply to — several files +may be present and the run must decide which, if any, is its own. **History is the source once +the cycle's own commit exists**, and there is no search there: the cycle is reading **its own +commit body**, so kind and artifact are settled by which commit is being read, and the nonce is +taken from the provenance line and the curve, which must agree. A Gate-A cycle mid-run has no +such commit and therefore has only the working record. Recovering a single candidate from +**either** keeps identity **as far as the field can distinguish cycles** — two cycles sharing a +nonce are one cycle to it. **No candidate, +disagreeing sources, or more than one candidate → no identity: start a new cycle**, which costs +passes rather than letting one cycle's records read as another's — again, as far as distinct +nonces allow. **Starting a new cycle does +not close, adopt or retire the cycles those candidates belong to** — they stay open, keep their +own nonces, and are a human's to resolve; the new cycle simply does not claim them. + +**A cycle does not start without a valid nonce, unique among the cycles open when it was +generated.** That is the requirement. **What the check can establish is narrower** — it compares +against the cycles it can observe — and the gap between the two is the residual set out below. +Where generation fails, make **at most three attempts in total**, then stop and +surface, **naming which of the three causes occurred**; each has its own check and its own fix, +and one token would name a symptom rather than a cause: + +The three are distinguished by **where** the attempt stopped, so they cannot both apply: the +source failed to produce bytes; or it produced bytes that are not a well-formed nonce; or it +produced a well-formed nonce that is already in use. An empty result is the first, never the +second. + +- **randomness unavailable** — the source errors or produces no bytes. *Fix:* retry, since the + condition can be transient; if it persists across the attempts, make a source available or run + where one is, which is a change to the environment rather than another draw. +- **an invalid value** — the drawn value is not 8 to 16 characters from `[a-z0-9]`. *Fix:* + redraw. Repeated invalid output points at the generator rather than at luck, and the report + says which. +- **a collision with a known-open cycle** — the value equals a nonce on a cycle still open. + *Fix:* redraw. A second collision at this width is possible but unlikely enough to be worth + reporting as a possible source defect, which the report states as a suspicion rather than a + finding. + +**Report every distinct cause observed across the attempts, in the order they occurred** — the +attempts can fail for different reasons, and naming only the last would describe the tail of the +sequence rather than what happened. + +**No deterministic fallback.** + +**Residuals, disclosed rather than guarded, and this list is not exhaustive.** The check compares +against cycles *known to be open*, so a nonce can repeat one belonging to a cycle nobody can see; +two cycles starting at the same moment can each check before either has published, so neither +observes the other; and the check deliberately ignores **closed** cycles, so a new cycle can +redraw a closed one's value and then write to its surviving findings slots and working record. +**What makes both unlikely is the width of the draw, not the check** — and unlikely is the +honest word. Neither is a guard. + +**What follows from that, said here rather than left to be discovered.** The nonce is +collision-**resistant**, not collision-**proof**, so everything built on it inherits that bound: +two cycles sharing a nonce write to the same slots and are not refused, their records read as +one cycle's, and a later reader cannot separate them. Attribution is therefore a strong default +rather than a guarantee, and any reading of these records that would be wrong if two cycles +shared a field should say so rather than assume they did not. + +**A cycle that began before these rules shipped has no nonce and cannot acquire one.** Its +records carry the reserved `cycle none (pre-rule)` field and are, by construction, not +cycle-attributable. That exception is bounded and self-terminating: it reaches only cycles +already running when the rules land, and no later cycle can enter the state. + (Field practice, infinite-portfolio-canvas: 7 dispositions files and a Gate-A resume note had been invented per-session there before the protocol knew about them.) @@ -233,7 +482,7 @@ the file" would accept a truncated file padded with fragments); and, for a `full pass, both branch files satisfy all of that. Anything else — missing, unreadable or empty file, wrong path, malformed terminator, count mismatch, extra lines, one branch file, an `INCOMPLETE` reply — is an **INCOMPLETE pass**, which is not a review: don't -act on the partial list, don't count it toward the 3-pass floor, and don't read "no +act on the partial list, don't count it toward the floor, and don't read "no Blocker/Major visible" as clean. **Reader:** the severity field is taken by splitting the line on **unescaped** pipes and @@ -297,7 +546,7 @@ number of lines, nor a stale file if you skip the delete. invalidate the review they document. Ignore `/.context/codex-reviews/` specifically — not all of `.context/`, which would strip the committed `codex-gate.on` adoption marker. -- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).** Run on the +- **Gate A — Spec, then plan (TWO runs, each its own loop at the derived floor).** Run on the **spec** right after brainstorming (before `writing-plans`), then on the **plan** before `executing-plans`/`subagent-driven-development` — catching a spec flaw before it's baked into the plan. Tool: `mcp__codex__exec` (raw; @@ -331,8 +580,8 @@ not all of `.context/`, which would strip the committed `codex-gate.on` adoption - **Gate B — Code.** Tests green, before `git commit`. Tool: `mcp__codex__review` (args `instruction`, `whatWasImplemented`, `baseSha`; `reviewType: full` runs spec + quality in parallel). Skip ONLY trivial changes. Check against - @AGENTS.md. Re-review after every fix — a fix changes the diff and the hook - invalidates the prior pass, which is where the 3 come from. + @AGENTS.md. Re-review after every fix — a fix changes the artifact, so the prior + review no longer covers it. The hook merely notices, at commit time. **A fix that changes specified behaviour updates the spec in the same commit.** If a Gate-B fix alters something the approved spec pins down — an ordering, a terminal @@ -400,16 +649,31 @@ the reviewer, the other obliges the author. it; risk's *abuse* and security's *abuse paths* are **one lens carrying both labels**, not two questions. -Lenses are **different questions, not more passes.** The 3-pass floor, the Blocker/Major -filter, the file-first findings protocol and the clean-final-pass rule are unchanged. +Lenses are **different questions, not more passes** — they change what a pass asks, never +how many a cycle owes. The Blocker/Major filter, the file-first findings protocol and the +clean-final-pass rule are unchanged. The floor is not among them: it is no longer a fixed +number but derives from the profile and the cited set. -**Reading the profile — three cases, three answers:** +**Reading the profile — five cases, five answers:** +0. **The ordinary case**: every cited story is readable and its profile resolves → derive the + floor from it and run. Stated first because a partition of failures alone is not a + partition, and an earlier revision of this list omitted it. 1. The artifact **cites no story** → run unprofiled and **say so** in the pass. Artifacts predating this rule are the common case; stopping on them would halt in-flight work. 2. The cited story has **no profile line** → same: today's behaviour. -3. A profile is **present but unresolvable** → **stop and surface the cause**. That covers - the syntactic failures — unparseable line, a value outside the enums, two profile - blocks, a citation resolving to nothing — **and the semantic ones**: a `**Validation:**` +3. The cited path **does not yield a readable story file** → **stop and surface which of + these it was**, because each has a different fix: the path does not exist (a typo, or a + file moved or deleted); it exists but is not a regular file, a directory being the common + case; it is a symlink that does not resolve; or it exists and is a regular file but cannot + be read for permissions. **Report what you observed; no test order is prescribed here**, + because the obvious one is wrong — an ordinary existence or regular-file test follows a + symlink, so a dangling link reads as absent rather than as a broken link. + This case exists because none of the answers above is available to an agent that never + obtained the file: it can establish neither that a profile is absent nor that one is + present but unresolvable. +4. The story **is readable** and a profile is **present but unresolvable** → **stop and + surface the cause**. That covers the syntactic failures — unparseable line, a value + outside the enums, two profile blocks — **and the semantic ones**: a `**Validation:**` value disagreeing with `max(risk, security)`, or `+abuse-path` present without security `high` or absent with it. Only the **latest `mode override`** in the log, moving in a direction compatible with the current value, can explain such a mismatch — and if the @@ -429,8 +693,9 @@ trivial** (the pre-existing judgement, unchanged by profiles), **and** for a pro profile *changes*, and a skip changes no profile value. **A skip removes the review, never the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the battery and lands its evidence entry beside the reason; a skipped **unprofiled** story -records the reason and the battery result and nothing more, because it owes no mode-derived -entry and keeps exactly today's judgement-based skip. +records the reason and the battery result, because it owes no mode-derived entry and keeps +exactly today's judgement-based skip. **Neither is excused the records every cycle owes** — +the provenance line, and a skip record in place of the curve. **A cycle citing several stories** aggregates along separate dimensions, never through one winning mode: the **battery runs once** for the cycle; **each cited _profiled_ story @@ -487,6 +752,29 @@ the current profile. Inside an active Gate-B cycle, fold the edit into the activ snapshot by amend — a non-`WIP` commit reads to the hook as the cycle closing and would discard the accumulated passes. +**While a gate is running, the floor derives from the current profile at each pass.** +Passes already run keep counting; closing requires the floor as currently derived. These +are pass-count rules, so they apply while a gate is running and are silent otherwise — +what governs when a gate runs is unchanged and deliberately not summarised here. + +**Any profile change costs at least one further pass**, in either direction and whether or +not the floor number moves, because the final clean pass must run under the current +profile — so no already-banked pass can be it. That further pass must itself be clean and +every other closure duty must be satisfied; it is one more pass, not a licence to close on +the next one. What a lowering drops is whatever the changed values drop, not a fixed pair: +a mode-only override changes the evidence obligations while leaving the axis-derived lens +sets alone, and security `high` → `standard` keeps the security lens set while changing +what evidence is owed. Every derived obligation is recomputed from the current profile. + +**The cited set is re-read at each pass, and the final clean pass runs against the current +set** — whenever its membership changes, not only when the floor number moves. Adding a +high-risk story to a set already at floor 3 leaves the number alone while adding that +story's lens set, its evidence obligations and its review scope; a pass run before it +joined did not cover them. Removing a story recomputes obligations from the current set +and so does remove that story's lenses and evidence duty — but it never discharges an +accepted in-set Blocker or Major: the acceptance put that finding in the fix set, not the +citation. + **What this does not do:** nothing checks which file a model actually read, whether the header changed mid-call, or whether the lens sets were appended. This is instruction-backed like the rest of §5; the detection is a reader comparing the pass against the story. @@ -494,10 +782,43 @@ like the rest of §5; the detection is a reader comparing the pass against the s ### Mechanics (reference) - **Severity:** Blocker (wrong/unsafe/breaks invariant) · Major (design flaw → rework) → both must resolve. Minor · Nit → collect, never iterate. + + **Deciding severity — one procedure. The subject list is illustration, not a second + rule.** Name what in the system consumes this text — whatever *acts* on it — and the + decision that act takes differently if the text is wrong. Both are required. If you + cannot name both, the finding is Minor or below: collect, never iterate. + + The exclusions are contract, not commentary. The reader must consume the text in the + system's *operation*, not in reviewing it — the review pass raising the finding is not + an in-system reader of the text it reviews; without this the test demotes nothing. + Gates remain legitimate readers of rule text they will later apply. A human reader never + satisfies the test — the prose exemption already prices that cost as non-gating. The + list of reader kinds is illustrative, not closed, because this ships into projects whose + readers we have never seen. The test sets a ceiling, not a floor, and never chooses + between Blocker and Major — the four definitions above still decide that. The instrument + carve-out is symmetric: an instrument finding keeps its severity whenever it shows the + instrument changes what a gate concludes about product behaviour — a false green, and + equally a false red or a check blocking a valid change. Rationale prose is Minor only + when no rule's application depends on it, not categorically: `docs/prompt-standards.md` + requires rules to carry their why, so rationale a reader must consult to apply a rule + passes the test. This removes arbitrariness, not judgement. Coverage-first is unchanged + — the reviewer reports every finding with severity and confidence; the filter is ours. + + This is the finding-level analog of the path-level prose exemption: one principle at two + granularities — text that *describes* the product versus text that *is* the product. + + **How this demotion bears on the loop-health measures — the per-pass counts, the finding + clusters and the stop thresholds — is not settled here, and this change does not settle it. + Until it is, a pass whose outcome would turn on that question reports the question and + stops rather than deciding it** — the same answer any unresolved gate question gets. + That question is owned by the loop-rule consolidation work in + `docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md`. - **Tool routing:** docs (spec/plan, incl. code snippets) → `mcp__codex__exec`; implemented diff → `mcp__codex__review`. Never `review` a doc — it reads the git range, not the text. -- **`baseSha`:** against main = merge-base with main (`headSha` = HEAD); +- **`baseSha`:** against main = merge-base with main (`headSha` = the full 40-character + object name `HEAD` resolves to at that moment, never the symbolic `HEAD` — see the + branch-agreement rule below for why); pre-commit, `baseSha` = HEAD is an empty range (HEAD..HEAD) — make a WIP commit and set `baseSha` to its parent. **Name that commit `WIP: …`** — the hook treats a `wip`-prefixed commit message as cycle-internal, so it neither fires a Gate-B STOP @@ -516,7 +837,142 @@ like the rest of §5; the detection is a reader comparing the pass against the s destroyed exactly when the cycle closes. The final commit body is the durable record; a PR shows commit messages, so there is no second home to keep in sync. - **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** + **Every cycle records one provenance line in its closing commit body** — default floor or + not, so an absent line is never ambiguous between "the default applied" and "someone forgot". + **One line per cycle**, so a change running five cycles records five. There is no informal + variant; anything quoting this form elsewhere quotes an instance of it, because the deferred + metrics work is intended to parse it — that consumer does not exist yet, and the form is pinned + now so that it can. + + ; floor per ; hook reminder threshold + + := "cycle " | "cycle none (pre-rule)" + := [a-z0-9]{8,16} + := [1-9][0-9]* + := "none" | "{" ("," )* "}" + each appears at most once; a repeated path, + with or without conflicting levels, is malformed + := " (level " ("0"|"1"|"2") ")" | " (unprofiled)" + := | + := [A-Za-z0-9._/-]+ contains no delimiter, quote or whitespace + := a double-quoted string, non-empty, whose only escapes are \" and \\ ; + a path containing a newline or other control + character is NOT representable — the cycle stops + and surfaces rather than emitting one + := "absent" | [1-9][0-9]* | "unusable" + + A filled instance, so the form is shown and not only described: + + cycle none (pre-rule); floor 3 per {docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md (level 2)}; hook reminder threshold absent + + It carries that cycle's **cycle field** — the nonce for any cycle started after these rules + ship, `none (pre-rule)` only for one that began before them — the **derived floor**, and **the + cited set that produced it**, each member with its level as a numeral. One floor and one set, + not an entry per story, since unanimity makes the floor a property of the set. It + distinguishes **a cited story with no profile** from **no story cited**. It records the + **workspace knob whenever the file exists**: the value if the observer read one, otherwise + `unusable`. **Nothing here describes what the hook does with that file, and the record does not + say why a value was unusable** — four successive attempts to state either were each wrong in a + different way, the last of them demonstrably so, and the rule for a claim needing a fourth + correction is to delete it. Whoever needs to know why reads the file and the hook. + + **These records are one contract, and a partial adoption breaks it.** The nonce, the slot + naming, the provenance line, the curve, this carry rule **and the unknown-start activation + semantics that say what a cycle owes when its starting rules cannot be established** depend on + one another, and the requirement is that the adopted definitions **agree**, not merely that all + of them are present: a curve + without a cycle field cannot be attributed, a slot rule without a nonce has nothing to key on, + and a carry rule naming records a project does not produce is inert. **A project whose text + carries some of them and not others, or carries all of them in versions that disagree, stops + and has a human complete, revert or reconcile the adoption before running a gate under it** — + disagreement is the harder case and gets the same stop, because a project holding two + definitions of a record has no single answer to what it owes — the same answer, and for the same reason, as a partial + adoption of the floor rule. + + **On squash-merge, copy every evidence entry, every human-exception record, the provenance lines, the curves and any skipped cycle's skip record TOGETHER WITH THE SKIP REASON IT POINTS AT in the squash range into the squash body — a skip record carried without its reason is a pointer into a body the squash has made unreachable — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** + + **Every cycle records its own per-pass curve in its own commit body.** Gate B alone would + leave the dominant cost unrecorded — the loops this rule was built from are Gate-A loops. + + ; (passes , ): Findings . Blockers . Majors . + + := "Gate-A spec" | "Gate-A plan" | "Gate B" + := ("," )* strictly ascending, non-overlapping + :=

|

"-"

+

:= [1-9][0-9]* + := ("," )* exactly as many entries as enumerates + := 0 | [1-9][0-9]* | "?" "?" = the count is unrecoverable for that pass + := | ("; " )* + := "pass "

" " ("+" )* + := | | "undetermined" + "undetermined" means the model could not be determined; + a real model so named is written as + := [!-~]{1,} minus ; : , ( ) + " and space, and not the + literal "undetermined", which is reserved + printable ASCII only; a control character makes the + identifier unrepresentable, handled below + := a non-empty double-quoted string, same two escapes as ; + an identifier that cannot be determined, or cannot be + represented, is written `undetermined`, and the raw + value is NOT reproduced anywhere in the body, since a + commit message cannot safely carry one (NUL cannot + appear at all). The record does not say why a pass + reached `undetermined`, and nothing here describes how + a model identifier fails — same rule, same reason as + the knob above + + Two filled instances, one ordinary and one with a split logical pass: + + cycle none (pre-rule); Gate B (passes 1-3, codex): Findings 16,29,4. Blockers 4,15,0. Majors 5,2,1. + cycle 7b2q9xk4; Gate-A spec (passes 1,2, pass 1 codex+claude; pass 2 codex): Findings 5,0. Blockers 1,0. Majors 2,0. + + A skipped cycle writes `; : skipped (see skip reason)` and no counts. **The skip + reason it points at is the text immediately following it in the same commit body** — + adjacency is the link. The cycle field is not: every pre-rule cycle writes + `cycle none (pre-rule)`, so it identifies nothing when a body carries more than one. + **`` keys must be exactly the passes `` expands to, each once, ascending** — a + list that omits or repeats a pass is malformed, not partially informative — and **every model + contributing to a split logical pass is listed**, joined by `+`, since recording one of two is + the same loss as recording none. + + **Majors are recorded as well as Findings and Blockers**, because the severity rule moves the + Blocker/Major line rather than the total, so totals and Blockers alone could not show even a + change in the mix. **Subject categories are deliberately not recorded** — they are a judgement + per finding rather than a count, and the findings files carry the material. + + **One entry per valid pass**, and since incomplete passes are excluded while still consuming + pass numbers, the record **states which pass numbers it covers**. A valid zero-finding pass is + recorded as zero, never omitted. **A count that cannot be recovered is written `?`, never + guessed and never written as `0`** — a cycle keeps its identity through the nonce rather than + through its pass files, as far as distinct nonces allow, so a resumed cycle may know a pass happened and not what it found, and + zero and unknown are different facts. **`?` is per series**: a pass whose Findings are unknown + may still have usable Blocker and Major counts, and a reader excludes the unknown value from + the comparisons that read that series while keeping the pass's other series. + + A `full` Gate-B pass, separate `spec`/`quality` calls, and a single-branch recovery are + **branches of one logical pass** contributing one summed entry — **the curve counts logical + passes; the hook counts calls**, and where they differ the body says so **as prose beside the + curve**: neither grammar has a field for a call count, deliberately, since the count is a + property of how the pass was invoked rather than of what it found. **Both branches must be + issued against the same commit**, and that — not what they read — is what this rule + establishes. The result reports no reviewed revision, so there is nothing to read back and no + way to confirm from the reply what either branch actually looked at. What is available is the + request: **resolve `HEAD` to its full 40-character object name before each call and pass that + explicit value as `headSha`**, never the symbolic `HEAD`, which two calls can resolve + differently if a `WIP:` amend lands between them. Keep the value you passed **with that + branch's result**, and require the two kept values — **`baseSha` and `headSha` both**, since a + range is selected by both ends and two calls can share a head over different bases — to be + **exactly equal** before summing the branches. Equal values mean the two calls were aimed at one commit; they are not evidence that + either branch reviewed it, and nothing available here would be. Record it as the **full 40-character hex object name**, since abbreviations are + ambiguous across repositories and across time; if it changed between them they are not one + pass, the completed branch is recorded as incomplete and excluded, and the later branch begins + a new one. Ending the pass is the conservative direction; merging two revisions would produce + one entry describing two different artifacts. + + **What the curve is worth, stated rather than implied.** Durable **across** cycles; **not + within** a running one, since the commit does not exist until the cycle closes. And + **author-written and unchecked** — nothing compares it against the validated pass files, so + whatever reads it reads a self-reported curve and must not present it as measurement. **Recording a human exception.** Where a human decides that something **no applicable rule required** was nonetheless worth skipping — an optional check this environment cannot run, a diff --git a/README.md b/README.md index 5e8dc14..f4e3103 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ plugin is installed once per machine; every other repo you open hears nothing fr Per-workspace knobs, all files under `.context/`: -| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. | +| `codex-gate.floor` | a positive integer; moves the hook's reminder threshold. It does not change the floor §5 obliges, which is derived from the cited story's profile. | | `codex-gate.off` | silences the reminders; classification and state tracking keep running, so re-enabling lands on counters carrying the same semantics as gate-on — which is not the same as evidence that a review happened. | | `codex-gate.tools` | `execTool=` and/or `reviewTool=` — counts a Codex server whose tools aren't named `exec`/`review`, and only worth it if that server really does separate text-review from diff-review; aiming both gates at one general-purpose tool moves the counters while neither gate means what it says. Each mapped name must itself lie in `mcp__codex__*`: the hook's `hooks.json` matcher is `^(Bash\|Skill\|mcp__codex__.*)$`, so an out-of-namespace name is either never delivered (the mapping looks applied and does nothing) or, for the reserved names `Bash`/`Skill`, hijacks a lifecycle event; the hook refuses both — register the server as `codex` to place its tools there. Unparseable, out-of-namespace and reserved (`Bash`/`Skill`) lines are ignored, and the gate keeps its default `exec`/`review` name. A typo **inside** the namespace — `mcp__codex__exce` — is still honoured: the hook does not check that a mapped tool exists, so the gate now counts that name and nothing else. Whether it ever counts depends on whether a tool by that name is actually invoked; for a typo, normally never. | diff --git a/docs/coding-workflow.md b/docs/coding-workflow.md index c3b520c..ee3d018 100644 --- a/docs/coding-workflow.md +++ b/docs/coding-workflow.md @@ -76,8 +76,9 @@ story that captures *what* and defers *how*: the problem, the desired outcome, t acceptance criteria, which core invariants the change touches, the open questions, a rough size, and a **profile** — risk and security relevance, confirmed by the human, with a validation mode derived from the two. The two axes **add** review lenses at the -gates for a risky or security-relevant change (they never subtract any: Gate A's floor and -the baseline questions are the same at every level), while the derived mode calibrates +gates for a risky or security-relevant change (they never subtract a baseline question; the +floor itself derives from the profile, so it is not the same at every level), while the derived +mode calibrates what evidence the author owes before Gate B. The design ("how") is deliberately left out — it belongs to the next stage. The value here is a shared, reviewable definition of done before anyone argues about approach. @@ -127,8 +128,9 @@ prior review. Trivial changes may skip it, on terms that depend on the story: an unprofiled one keeps the judgement call, while a profiled one qualifies only at effective level 0 — trivial risk *and* no security relevance — so a trivial-looking change on security-relevant surface is not eligible. A skip removes the review, never -the evidence: the battery still runs, the reason is recorded in the commit body, and -so is one evidence entry per cited profiled story. **Explanatory** +the evidence: the battery still runs, and the commit body carries the reason, the battery +result, the cycle's provenance line, a skip record in place of the curve, and one evidence +entry per cited profiled story. **Explanatory** documentation carries no gate at all — a wrong sentence there costs a confused reader rather than broken behaviour. Prompt artifacts are not explanatory prose: in a project whose product is prompts, the text *is* the behaviour, so the review policy requires Gate @@ -276,8 +278,9 @@ top-level field alone is the wrong answer precisely where the override documente use, since `CODEX_DEV_REVIEW_MODEL` is stored at `tools.review.model`. If neither level names a model the probe establishes nothing — the CLI then picks its own default, and the only honest record is to set an explicit model or record the model as undetermined. Record the result beside -the finding count in the pass record: the commit body's evidence entry, or the slot's -dispositions file. This is +the finding count in **the cycle's per-pass curve**, which pins a field for it — not the +evidence entry and not the dispositions file, neither of which is keyed to a pass. The health +probe above is how the value is established; the curve is where it goes. This is bookkeeping, not enforcement: nothing checks it, and a wrong entry looks exactly like a right one. diff --git a/docs/field-reports/2026-08-16-canvas-a1-a5-dispositions.md b/docs/field-reports/2026-08-16-canvas-a1-a5-dispositions.md index 7a4a1c1..96c129f 100644 --- a/docs/field-reports/2026-08-16-canvas-a1-a5-dispositions.md +++ b/docs/field-reports/2026-08-16-canvas-a1-a5-dispositions.md @@ -310,3 +310,69 @@ established neither whether the hash was computable at any given pass nor whethe failed to persist, and it did not read the hook at the site that computes it. Its one new lead — that the second shape's STOP arrived at a `git reset --soft`, which reaches the reset path only via `is_commit` — ties it to the item-2 row and is a lead, not a finding. + +--- + +## Field note added 2026-08-28 — criteria that restate the design are a Blocker generator + +Observed across Gate-A passes 1–4 of the review-loop-economics cycle +(`docs/superpowers/specs/2026-08-28-review-loop-economics-design.md`): **five Blocker-severity +occurrences of one defect**, where the spec was revised and the story's acceptance criteria +still described the superseded mechanism — criterion 8 demanding a floor a risk-`high` story +cannot license; criterion 5 describing artifact-kind severity after a consequence-keyed test was +settled; desired outcome 2 keeping a false-green-only carve-out; criterion 3 reversing the +settled meanings of derived floor and hook knob; and criterion 4 requiring both shipped copies to +describe a mechanism the design had deleted — where implementing the criterion would have +recreated the rejected design in order to satisfy a criterion about it. + +The cause is structural rather than carelessness: those criteria embedded **mechanism detail**, +so each had to track a design still in motion. A criterion that restates the design is a second +copy of it, and this repo's ledger already records what a second copy does — "a restatement is a +second copy that can drift". + +Remedy applied in that cycle: the criteria were rewritten to state **what must be observably +true** rather than **how**, with the bound that a criterion which cannot be made observable +without naming mechanism is one where the mechanism *is* the contract, and there it stays named. + +**Captured, not acted on beyond that cycle.** This is field evidence for whoever next touches +`dev-workflow:intake`, whose story template says acceptance criteria "describe observable +outcomes or constraints, never implementation steps" — the rule exists; what this record adds is +five measured occurrences of the failure it is meant to prevent, and the observation that the +drift shows up as *Blockers in a later gate* rather than as a bad-looking criterion at intake +time. + +**Also observed, 2026-08-28, same cycle:** three occurrences of an agent ending a turn on an +announcement — "writing the spec now", "running pass 6" — with the named tool call never issued. +A fourth followed on 2026-08-29 ("running pass 10"), spotted by Daniel watching the terminal. Each cost a round-trip and one cost ~90 minutes of wall clock before a peer session noticed. No +error, no timeout, nothing in flight: the announcement simply replaced the act. Remedy adopted for +the remainder of that cycle: during an active gate cycle a turn ends with the tool call actually +issued, a message sent, or an explicit statement that something is blocking — and noticing the +turn ending with the call not in flight *is* that statement. + +**Prediction recorded 2026-08-28, before the pass that tests it.** Gate-A pass 7 of the +review-loop-economics cycle found that the spec's own nineteen-row condition-inventory table (§6.2) +had become the loop's largest finding source: **7 of 29 Blocker/Major, including 4 of 9 Blockers**, +every one a row contradicting the design it existed to account for. The table was split out — +method and passage list stay in the spec, the row-by-row dispositions move to an artifact produced +once against frozen text and gated before implementation. + +Verbatim prediction, so it can be scored rather than remembered: **pass 8 should lose roughly 7 of +29 Blocker/Major and 4 of 9 Blockers to the §6.2 removal. If pass 8 does not fall materially, the +generator is elsewhere and the whole-artifact split becomes the live candidate.** + +Worth keeping either way: this is the third site of one defect class in a single cycle — a +restatement that must track a moving original. It appeared in the story's acceptance criteria +(five Blocker occurrences), then inside the spec's own accounting table (four more). The lesson is +not "write the table more carefully"; it is that a second copy of a moving thing drifts, and the +remedy is to produce it once against something that has stopped moving. + + +**The loop's first wrong finding, 2026-08-29.** Gate-A pass 13 of the review-loop-economics cycle +returned a BLOCKER claiming a spec sentence was "syntactically incomplete at *every and the curve +duty treated as owed*". `grep 'every and'` on the reviewed file returns nothing — the break existed +in revision 13 and revision 14 had replaced the sentence. **Dismissed with that evidence.** + +Worth recording because it is the **first outright wrong finding in thirteen passes** of that +loop — roughly 380 findings. That ratio is the argument for validating before applying rather than +against it: the discipline cost thirteen passes' worth of checking and caught one, and the one it +caught would otherwise have driven an edit to text that was already correct. diff --git a/docs/field-reports/2026-08-26-fic2-cycle-evidence.md b/docs/field-reports/2026-08-26-fic2-cycle-evidence.md new file mode 100644 index 0000000..be7be2e --- /dev/null +++ b/docs/field-reports/2026-08-26-fic2-cycle-evidence.md @@ -0,0 +1,238 @@ + +> **Why this is committed.** Written during the cycle at +> `.context/codex-reviews/gate-b-fic2-parked-review-economics.md`, where `.gitignore` +> excludes all of `.context/` and each gate cycle overwrites the previous cycle's slots. +> It is the opening evidence for the review-economics story, so it is committed here +> rather than left to a `.context/` clear. Verbatim copy: no machine-local absolute +> paths were present, so the field-report path-neutrality convention required no +> substitution. The 14 `fic2` pass files it cites stay local — their per-pass figures +> are carried in the tables below. + +# Parked: opening-evidence package for the review-economics story + +Written 2026-08-26 during Gate-B cycle `fic2` on branch `field-intake-canvas-a1-a5`. +Not a findings file — it participates in no pass validation. It exists because the +material below was produced by a live loop and would otherwise die with the session. + +**Disposition (Daniel, 2026-08-26): revert and park.** The cycle does not absorb the §5 +surgery these findings imply. The assigned fix set contracts back to the eleven +dispositions plus the two loop rules that survived the earlier 12-pass cycle, plus this +cycle's verified error fixes. Everything below moves to a story, unanswered. + +## What was reverted, and why it is not a retreat + +Two clauses were written into CLAUDE.md §5 and the `/workflow-init` template mirror +during this cycle and then taken back out: + +- **Q1 — clean completion outranks the two-tell stop.** A Blocker/Major-free pass at or + above the floor would close even with two or more tells present, the tells going into + the closing status report rather than blocking the close. +- **Q2 — a declined expansion has a defined exit.** A finding the user declines to bring + in-set leaves the cycle as a recorded out-of-scope item, with the Blocker/Major-resolve + duty scoped to in-set findings. + +Both were correct answers to real defects. Pass 1 found the defects; Daniel confirmed both +answers. Pass 2 then found that shipping them requires qualifying **three §5 rules nobody +proposed changing** — the universal Blocker/Major-resolve duty, the rule that a surfaced +finding stays open with resolution unwaived, and the rule that no pass carrying it counts +as clean. That is the scope expansion the cycle declined. + +## The three questions that go to the story unanswered + +- **Q3.** Does a *scope stop* outrank clean completion, or the reverse? "Clean completion + outranks both exits" was written, but §5 has three exits — the scope stop, the + clearly-stuck exit, and the two-tell stop. If scope is included, the sentence + contradicts the rule that even a Minor opening a contract question must stop. +- **Q4.** Does a decline bind *later passes in the same cycle*? As drafted it governed one + stop only, so the same out-of-set Blocker could stop every subsequent pass and re-ask the + same question indefinitely. +- **Q5.** Is qualifying the three universal rules with an in-set boundary acceptable at + all? Yes makes Q2 shippable and is §5 surgery; no means Q2 cannot ship in that form. + +## The instrument is broken, and that is the most reusable finding here + +The `battery+check` evidence for a prose-only change was a decision matrix: N review +states, complete inputs, one expected output each, scored against the old text and the new +text and against both copies independently. Two defects were found in it by Gate B, and +both are properties of the *technique*, not of this instance: + +1. **A state's inputs must include every input the rule reads.** Rows 3 and 10 carried + identical recorded inputs and different expected outputs, because the user's expansion + answer was never an input column. A matrix that omits an input cannot distinguish the + states that input separates, and it will still look complete. +2. **A counterfactual must distinguish ABSENT from CONTRADICTORY.** The entry claimed the + parent commit was "contradictory" on one state. `git show 17d5ad3:CLAUDE.md` has no + two-tell rule at all — only the undefined phrase "clearly stuck" at line 78. The + contradiction existed solely in an intermediate draft produced *inside this cycle*, so + the check reported a failure mode the prior state could not produce. + +Both survived a full Gate-B pass before being caught on the next one. + +## Loop economics, measured on this cycle + +| Pass | Findings | Blocker | Major | +|---|---|---|---| +| 1 | 14 | 3 | 5 | +| 2 | 24 | 4 | 13 | + +Four of the five tells present at pass 2: finding count rising; Blocker count failing to +fall; findings clustering on the **instrument** (6 of 24, on the matrix rather than on the +rules it scores); findings clustering on **prose about** the rules (9 of 24 — CHANGELOG, +closure record, ledger entry, a parked story). No require↔withdraw pair: pass 2 narrowed +what pass 1 required, which is a qualification and not a withdrawal. + +The reporting duty formally begins at pass 4. These were readable at pass 2, which is the +argument for the duty starting earlier — or for the trend being computed rather than +narrated. + +## Cross-references + +- Findings: `.context/codex-reviews/gate-b-{spec,quality}-fic2-pass-{1,2}.md` +- The matrix as it now stands (nine states, both clauses removed): + `docs/field-reports/2026-08-16-canvas-a1-a5-dispositions.md`, section "The named + verification behind the `battery+check` entry" +- Slot-name variance for this cycle: `fic2` discriminates it from the 2026-08-17 cycle + whose records occupy the bare `gate-b-{spec,quality}-pass-N` slots. + +--- + +## Parked at pass 5 (Daniel, 2026-08-26: bounded fix, then close) + +Four findings from Gate-B pass 5 were parked rather than repaired. All four are true. All four +are about the *instrument* or about the entry conditions of unstarted work, and none is about +what §5 tells an agent to do. Pass 5 returned **zero product-behaviour findings**. + +### The one that is this story's subject matter, verbatim + +**A prose rule's check demands a fixture per predicate.** Pass 4 found that the nine-state matrix +could not see the reporting duty at all — no input it read changed when that duty was deleted. A +five-state table was added. Pass 5 then found that the new table takes the *tell count* as a +precomputed input rather than deriving it from raw observations, so a draft could delete or invert +one of the five tell definitions and every row would keep its expected result. The repair asked for +is raw input columns exercising each of the five tell predicates, against both prompt copies at both +revisions: roughly 5 × 2 × 2 hand-scored fixtures, for a rule whose entire product surface is one +paragraph. + +The finding is correct and the repair is disproportionate. That gap — **what differential evidence +a prose-only rule can actually carry, and where the cost of the fixture exceeds the value of the +coverage** — is the question. `battery+check` says "a check that fails without the change" and says +nothing about how much instrument a one-paragraph rule is worth. + +Sequence worth keeping: pass 4 asked for the table, pass 5 asked for it to be rebuilt, and the thing +being measured did not change between them. + +### Three on a parked story's acceptance criterion + +`docs/superpowers/stories/2026-08-17-arms-race-remedy-as-procedure-story.md`. Its kept/moved/dropped +criterion should enumerate individually, per pass 5: the three line contents; all five tell +definitions; and the threshold's consequence (report the triggering tells, hand the decision to the +user). Its problem statement also says §5 carries "one recognition heuristic and a terminal action" +while the same story later inventories two stop paths — a contradiction in the story's own opening. + +Each is true. Each refines the entry conditions of work nobody has begun. One condition from this +group *was* applied rather than parked — the pass-4 activation boundary — because a future rewrite +could otherwise move the duty to pass 1 while checking off every other listed condition. + +### Cycle shape at the point of closing + +| Pass | Findings | Blocker | Major | +|---|---|---|---| +| 1 | 14 | 3 | 5 | +| 2 | 24 | 4 | 13 | +| 3 | 12 | 0 | 6 | +| 4 | 3 | 0 | 2 | +| 5 | 6 | 0 | 5 | + +Two stop-and-surfaces, at pass 2 (four tells) and pass 5 (three tells). Only the pass-5 stop was +required by the shipped duty, which begins at pass 4; the pass-2 stop was an early surface chosen +because the tells were already readable, which is the argument the duty's activation boundary +invites rather than a rule it enforces. The pass-2 stop produced a revert; the pass-5 stop produced +this bounded close. Both were decided by the maintainer, neither by the loop. + +--- + +## Observation recorded 2026-08-26: pass counter disagreed with the pass record + +Raw facts only. **The cause is UNDIAGNOSED**, and no attribution to any existing ledger row +is made here — attribution without diagnosis is the class the ledger polices. + +- Cycle: `fic2`, branch `field-intake-canvas-a1-a5`, closed 2026-08-26 at commit `3cdd075`. +- Passes actually run and validated: **7**. Each wrote both branch files, each file carried a + well-formed terminator and a count matching its finding lines: + `.context/codex-reviews/gate-b-{spec,quality}-fic2-pass-{1..7}.md` — 14 files on disk. +- What the gate hook reported at the closing commit: **"only 1/3 mcp__codex__review pass(es) + since the last commit"**. +- What it reported at each intermediate amend: **"1 recorded pass(es) this cycle"**, from the + amend following pass 1 onward. The value did not advance across passes 2 through 7. +- One intermediate amend instead reported **"no fingerprint is recorded for this cycle"**. +- `.context/codex-gate.passCount` read `7` at the start of the session, before this cycle began; + that value belongs to the previous cycle and was not re-read afterwards. +- Every one of the 7 calls returned `success: true` with a normal result envelope. None timed + out, none was aborted, none returned an `INCOMPLETE` reply. +- The cycle used a slot-name discriminator (`fic2`) rather than the bare + `gate-b-{spec,quality}-pass-N` names. Whether that is related is **not established** — the + hook is documented as never reading the findings file at all. +- Each pass was followed by `git commit --amend` on a single `WIP:`-prefixed commit. + +Not diagnosed, and deliberately not guessed at: whether the counter was reset, never +incremented, incremented and overwritten, or read from a different key than it was written to. +Nobody inspected the hook's state files during the cycle, so there is no evidence either way. + +Consequence for this cycle: none. `CLAUDE.md` §5 says the counter is not evidence and that every +incomplete pass is discounted regardless of what it says; the close rested on the 14 validated +findings files, not on the counter. The observation matters for the instrument, not for this +artifact. + +--- + +## Parked from PR #25 review (Daniel, 2026-08-26): the unavailable-history gap + +Greptile raised one P1 on PR #25 against `CLAUDE.md:143`, the pass-4-onward reporting duty. +Thread: https://github.com/dsnger/dev-workflow-kit/pull/25#discussion_r3864986788 + +**Split verdict.** + +*The stated mechanism is FALSE.* The claim was that "the mandatory findings format cannot +retain all of that history". It can. §5 mandates one findings file per pass per branch at a +**pass-numbered** slot, one finding per line, severity as a leading closed-set field, and a +terminator carrying the count. Both historical inputs the three-line report needs are +therefore derivable from the mandated artifacts alone — trend by counting finding lines and +`^BLOCKER` lines per pass, require↔withdraw by comparing across those same files. Run over +this cycle it reproduced the reported figures exactly (findings 14, 24, 12, 3, 6, 6, 2; +Blockers 3, 4, 0, 0, 0, 0, 0) with no optional artifact consulted. The deletion rule does not +erase history either: §5 deletes only the slot about to be written, and slots are numbered. + +*The gap is REAL, and it is availability rather than format.* Where prior-pass files are +genuinely absent — a fresh checkout, a cleared `.context/`, another machine, a cycle resumed +elsewhere — §5 defines **no behaviour** for the report from pass 4 onward. The cycle-stable +resume note is not the fallback: it is explicitly optional, and `CLAUDE.md:224` says "Nothing +depends on it existing." An agent in that position must invent the trend, omit the line, or +decide for itself, and the two-tell threshold is mandatory on top of whatever it decides. + +**Why parked rather than fixed:** closing it needs new normative §5 content, which is the +surgery this cycle declined twice. It joins Q3-Q5 above as a fourth open question of the same +shape — a gap in the shipped rule whose repair is a contract decision. + +**Q6.** What does the pass-4-onward report do when the prior-pass record is unavailable? The +candidate answers are not obviously equal: report the lines that *are* computable and say +which are not; treat unavailable history as a stop condition of its own; make the resume note +mandatory for cycles that cross a session boundary (which changes an artifact §5 currently +calls advisory); or start the duty's clock at the first pass of the *current* record rather +than of the cycle. + +## Raw observation, undiagnosed: CodeRabbit plan metadata disagrees with the routing file + +Recorded 2026-08-26, not attributed and not acted on. + +- CodeRabbit's run configuration on PR #25 reports **`Plan: Pro Plus`** (Run ID + `cdbb25fa-8c3d-45fb-a259-6b973b2ea965`, review profile CHILL). +- `docs/pr-review-bots.md`'s CodeRabbit row records **Plan: Free** "(per Daniel)", and states + that the earlier Pro Plus reading "was observed on PR #1 only and no longer describes the + account". +- These disagree. **The cause is UNDIAGNOSED.** Two candidates, not distinguished: the plan + actually changed since that row was written, or the run-configuration metadata is + unreliable. +- Why it matters: that row's review-limit reasoning — and part of the argument for routing + CodeRabbit opportunistically rather than blocking on it — rests on the Free reading. +- Not this PR's business; `docs/pr-review-bots.md` is untouched by PR #25. A docs-only + follow-up can correct it **after** diagnosis, not before. diff --git a/docs/field-reports/2026-08-29-gate-a-rle-cycle-evidence.md b/docs/field-reports/2026-08-29-gate-a-rle-cycle-evidence.md new file mode 100644 index 0000000..db51332 --- /dev/null +++ b/docs/field-reports/2026-08-29-gate-a-rle-cycle-evidence.md @@ -0,0 +1,81 @@ +# Gate-A spec cycle `rle` — the record, preserved + +The Gate-A spec cycle for +`docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` ran **34 passes** and closed +clean. Its 34 validated findings files live under `.context/codex-reviews/`, which is gitignored, +so **this file is the durable record** — written before any slot reuse or `.context/` clear, the +same reason `2026-08-26-fic2-cycle-evidence.md` exists. + +Every number below was extracted mechanically from those files +(`grep -cE '^(BLOCKER|MAJOR|MINOR|NIT) \|'` and `grep -c '^BLOCKER'` per pass), not recalled. + +## The curve + +``` +Findings 27, 30, 54, 40, 33, 34, 32, 33, 28, 27, 38, 24, 32, 18, 13, 4, 6, 2, 7, 6, 9, 4, 2, 7, 5, 4, 6, 6, 3, 3, 1, 2, 1, 1 +Blockers 5, 2, 0, 4, 0, 3, 9, 4, 2, 2, 2, 5, 9, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 0, 0 +Majors 19, 20, 43, 27, 30, 23, 20, 24, 20, 21, 28, 12, 19, 12, 9, 3, 3, 1, 4, 4, 3, 3, 2, 2, 3, 3, 5, 4, 2, 1, 0, 1, 1, 0 +``` + +Blocker/Major never fell below 22 for the first eleven passes. **Two structural decisions moved +it, and nothing else did.** + +| Pass | Event | Blocker/Major after | +|---|---|---| +| 1–11 | ordinary repair rounds | 22–43, never below 22 | +| 12 | **part 3 split to a successor story** | 17 | +| 13 | (split accounting errors) | 28 | +| 14 | **spec slimmed 604 → 332 lines, rules only** | 12 | +| 15–34 | ordinary repair rounds | 9, then 1–5 throughout | + +**Three mandatory two-tell stops** fired, at passes 6, 11 and 13, each surfaced to the human, and +the second and third produced the two structural decisions above. A fourth stop at pass 7 was +discretionary — the tells were readable before the duty activates at pass 4. + +## What the passes actually cost, by cause + +Of roughly 380 findings, the recurring generators were: + +1. **A restatement that must track a moving original.** Three sites in one cycle: the story's + acceptance criteria (five Blocker occurrences), the spec's own condition-inventory table (four + more), and — three revisions running — a summary of §5's triviality skip, where each attempt + corrected the summary instead of deleting it and each new summary dropped a different condition. + **The remedy is never a better summary.** +2. **A correction landing in one place and not the others.** The demotion-versus-comparison + correction took four revisions to propagate across four artifacts; twice I wrote in a commit + body that I had enumerated every site and had not. +3. **A repair generating the next defect.** Most Blockers from pass 10 onward traced to the + immediately preceding revision's own fix. + +## Findings worth keeping + +- **A restructuring guard that asks "did a decision move?" misses the case where a rule survives + in outline and loses its force.** Pass 9 found eight of those in one revision — including a + severity test compressed to "if you can name neither", which inverts a rule requiring both. +- **Running a check is not reading its output.** Twice I ran a grep, wrote a sentence its own + output contradicted, and cited the verification. A grep for the phrasing you expect is not a + check either — my search for compression losses returned zero because the reviewer had phrased + them differently. +- **An instrument that cannot measure its own subject.** The per-pass curve recorded Findings and + Blockers while existing to show whether severity moves the Blocker/Major line. +- **Comparable is not measured.** Even with Majors recorded, no finding is ever classified under + both rules, so no demotion figure is derivable — only a comparison of recorded mixes across + cycles that reviewed different artifacts. +- **The loop's first wrong finding arrived at pass 13**, in roughly 380. It claimed a syntax break + a prior revision had already replaced; dismissed with grep evidence. One bad finding in ~380 is + the argument *for* validating before applying, not against it. + +## Closing state + +Pass 34 returned **zero Blocker/Major** and one MINOR, collected per §5's clean-final-pass rule: +§4's rationale claims the lost series differs across cycles, which nothing establishes. Recorded +here as the cycle's one outstanding Minor. + +**Coverage statement.** The `/workflow-init` mirror's actual text was checked by hand against every +claim the spec makes about it, re-run against the final revision: the template block is fenced at +192–778 with §5 at 257–777; the describe-versus-be principle the severity kinship rests on **is +present** in the template (an earlier revision's claim that it was absent was withdrawn); the +hook's §5-heading regex still matches `CLAUDE.md`; and `workflow-init.md` retains exactly one +`Target model:` declaration, which the item-1 n/a decision preserves. **Known limit:** the two +copies diverge on ~192 lines overall, and only the rules this change touches were compared — +general reconciliation was out of scope throughout. diff --git a/docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md b/docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md new file mode 100644 index 0000000..f272c5a --- /dev/null +++ b/docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md @@ -0,0 +1,257 @@ +# Review cycles `rle` — the four Gate-A plan cycles and the Gate-B cycle, preserved + +Companion to `2026-08-29-gate-a-rle-cycle-evidence.md`, which holds the **spec** cycle. This file +holds the **four plan** cycles for the same change, and — since 2026-09-02 — the single +**Gate-B** cycle that reviewed their combined diff. Their validated findings files live under +`.context/codex-reviews/`, which is gitignored and whose slots are reused, so this is the durable +record — the same reason the spec file and `2026-08-26-fic2-cycle-evidence.md` exist. + +Every number below was extracted mechanically from those files +(`grep -cE '^(BLOCKER|MAJOR|MINOR|NIT) \|'`, `grep -c '^BLOCKER'`, `grep -c '^MAJOR'` per pass), +not recalled. + +**Why the records are here and not in a commit body.** The four Gate-A cycles for this change — +one spec, three plans — all closed, or are closing, **before the record rules they helped write +are active**. An earlier revision of Plan C tried to reconstruct them into the implementation +commit's body with a marker saying they were reconstructed. Three Gate-A passes contested that in +a row, on three separate grounds: the destination is wrong (the settled rule puts each cycle's +record in *that cycle's* closing body), the marker could not live inside a record without breaking +the pinned grammar, and moved beside the record it was not covered by the squash-carry rule, which +enumerates records and not prose. **Daniel's resolution, 2026-08-30: the reconstruction leaves the +commit body entirely.** Pre-rule history goes to this channel, which is committed prose that git +carries on its own. The implementation commit carries only the native records of +its own Gate-B cycle. **That cycle is also pre-rule** — it began before these rules shipped, so +its records carry `cycle none (pre-rule)` like the rest. It writes them natively not because the +new operational rules bind it, but because this branch's own acceptance and evidence criteria +require them. + +## The three curves + +**Plan A — the floor predicate and the severity test.** 12 passes, closed clean. + +``` +Findings 21, 16, 16, 7, 5, 2, 3, 4, 6, 6, 8, 0 +Blockers 7, 6, 6, 0, 0, 0, 0, 0, 0, 1, 1, 0 +Majors 10, 8, 7, 3, 4, 1, 2, 2, 5, 5, 4, 0 +``` + +**Plan B — the provenance line, the per-pass curve, the cycle nonce, slot naming.** 7 passes, +closed clean. + +``` +Findings 11, 3, 6, 10, 9, 7, 0 +Blockers 2, 0, 0, 0, 0, 0, 0 +Majors 7, 3, 4, 7, 5, 6, 0 +``` + +**Plan C — rollout, packaging, evidence, the close.** 7 passes, **closed as not converged** +(Daniel, 2026-08-30). No clean pass was reached and none is claimed anywhere. The record is +`.context/codex-reviews/gate-a-plan-planc-CLOSURE.md`. + +``` +Findings 18, 20, 20, 23, 22, 19, 29 +Blockers 5, 4, 2, 4, 5, 2, 2 +Majors 11, 13, 13, 16, 13, 16, 19 +``` + +Blocker/Major never left the 15–21 band, and both the highest finding total and the highest +Blocker+Major of the cycle are **pass 7 — the last one**. + +**Plan C1 — the user-facing floor description.** The first carve-out of the split. 3 passes, +**stopped on the two-tell rule**, never closed. + +``` +Findings 14, 12, 17 +Blockers 0, 1, 0 +Majors 8, 7, 10 +``` + +The finding count rose and Blocker+Major rose (8, 8, 10) — two of §5's five tells, which makes +stop-and-surface mandatory rather than discretionary. **Daniel's decision, 2026-09-01: do not +resume the loop. C1 is dissolved and its payload is reviewed at Gate B, on the artifact.** + +One operational note the cycle paid for: the slots are +`gate-a-plan-planc1-pass-{1,2,3}.md`, with **no revision infix**. Four plan revisions were +committed (bb358c2, 476236b, 7e42947, 52192d1) and three passes recorded, and which pass ran +against which revision is not recoverable from the artifacts. A dispositions file must carry the +revision it reviewed; this record cannot reconstruct it. + +## What the three cost, and why they differ + +**Plan A's shape is the ordinary one**: Blockers exhausted in three passes, then a long tail of +Majors, then clean. Its late Blockers at passes 10 and 11 were its own fixes regenerating — +the pattern the spec cycle recorded from pass 10 onward. + +**Plan B is the cheap cycle**, and it is worth saying why, because it is the only counterexample +in the set. It ships four record *forms* — pinned grammars with named fields. A grammar is +checkable by reading it against itself: a reviewer can ask whether every field has a production +and whether every production is reachable, and get a decidable answer. It never needed a second +structural revision. + +**Plan C never got its Blocker line to zero**, and both of its mandatory stops trace to the same +cause: **the plan restating rules that live somewhere else.** + +| Revision | What changed | Next pass | +|---|---|---| +| 1–2 | ordinary repair rounds | 16 → 17 Blocker/Major | +| 3 | six unfailable checks stripped | 15 B+M — and the strip took two real actions with it | +| 4 | those two restored | 20 B+M, 17 of 23 findings on the plan's own copy of `CLAUDE.md` §5 | +| 5 | that copy deleted; the plan defers to §5 | 18 B+M, 16 of 22 still on what the deferral left unspecified | + +Two mandatory two-tell stops fired, at passes 4 and 5. The first produced revision 5's +de-restatement; the second produced the resolution recorded at the top of this file. + +**The finding that generalizes:** a plan that restates a protocol its own repo already governs +creates a second copy that drifts, and Gate A will review the copy instead of the work. Plan A and +Plan B state rules; Plan C had to *use* rules, and using them tempted it into repeating them. The +remedy was the same one the spec cycle recorded for restatements generally — **not a better +restatement, a deletion** — but deleting a restatement leaves a gap where the plan-specific facts +were tangled up in it, and pass 5 is a list of those. + +**C1 turns that into a second data point, and it generalizes further than the first.** C1 *was* +the remedy for Plan C's non-convergence: take one statement site out, make the plan small, give it +its own cycle. C1 is nine sentences in eight replacements across two files — about as small as a +plan of this kind gets — and its curve rose anyway. So the cost is not carried by the plan's +**size**. + +What both artifacts share is that they are prose describing replacements of prose, and a reviewer +reading one has no decidable question to answer. Plan B is the contrast that makes this visible: +its grammars can be checked against themselves, so a pass either finds an unreachable production +or it does not. A sentence-replacement list can only be checked against a fresh reading of two +other documents, and every pass brings a fresh reading. **A plan made of prose about prose has +now failed to converge under Gate A twice, at two very different sizes.** + +Where that evidence points is Gate B: the same eight replacements, read as a diff against the +files they changed, are a question with an answer. + +## The Gate-B cycle — five passes, closed as not converged + +The four cycles above are Gate-A. This one is the single Gate-B cycle over the combined +A+B+C diff, and it closed the same way Plan C's did: **on the clearly-stuck exit, with no +clean pass and none claimed.** Numbers extracted mechanically from +`.context/codex-reviews/gate-b-{spec,quality}-rle-pass-{1..5}.md`, the same way the rest of +this file was taken. + +``` +pass 1 2 3 4 5 +Findings 16 29 25 25 23 +Blockers 4 15 6 5 10 +Majors 5 2 9 10 6 +B+M 9 17 15 15 16 +``` + +**Pass 2 is discounted and not counted toward the floor.** Both branch files were +structurally valid — correct terminators, exact counts, six fields, no stray lines — but the +reply contradicted itself: each of the two parallel reviewers reported *the other* branch +`INCOMPLETE`, having mistaken its counterpart's legitimate file for a foreign write. The +findings were acted on, because a file that passes every structural check is provably not the +partial list the rule guards against; the pass was not credited, because an `INCOMPLETE` reply +is an incomplete pass by rule. **A protocol note in the next call's `additionalContext` — +"finding the other branch's file present is EXPECTED and is not a collision" — fixed it, and +it did not recur in passes 3, 4 or 5.** This failure shape is worth naming because nothing in +the file protocol anticipates it: `reviewType: full` runs two writers, and the rule that +protects them from racing on one path does not tell either that the other exists. + +### What made it stick: one mechanism, four wrong descriptions + +Every round's Blocker/Major cluster traced to prose describing **what the hook does with the +`.context/codex-gate.floor` knob**. The corrections, in order: + +| round | what was written | why it was wrong | +|---|---|---| +| 1 | "trims trailing newlines"; "exceeds the hook's accepted maximum" | the hook runs `tr -d '[:space:]'`, and it defines no maximum | +| 2 | rewritten against the hook source, cause by cause | it gates on `-f` before reading, so a broken symlink never reaches the read, and a failed `cat` is indistinguishable from an empty file | +| 3 | walkthrough deleted, one summary sentence kept: "an unusable value leaves the hook's default standing" | false. Tested: `printf '1\0002' > knob.bin` is **accepted as twelve** in `sh`, `dash` and `bash` — command substitution drops the NUL, and `1` `2` becomes `12` | +| 4 | the claim deleted entirely, `` dropped from the grammar | the note explaining *why* the description was deleted is itself a description of the hook | + +That last row is the one to remember. **There is no version of that paragraph that survives +its own rule** — the remedy consumes any explanation of why the remedy was applied. +`docs/prompt-standards.md` item 11 already prescribes deletion after a fourth correction; what +this cycle adds is that the deletion has to include its own rationale, and the rationale then +lives here, in a field report, where describing the hook is the point rather than a claim the +product makes. + +The capability cost was accepted knowingly on 2026-09-02: the provenance record now says +**that** a knob was unusable and no longer **why**. Whoever needs why reads the file and the +hook. + +### Why the exit was taken rather than a sixth round + +All three §5 conditions were affirmed, not assumed: + +- **A plateau across passes.** Blocker+Major never returned to its pass-1 level of 9 and rose + on the last pass. +- **Coverage affirmatively sufficient.** Across five passes the reviewers covered both prompt + copies, the spec, the story, all three plans, the hook source and the user docs. No + materially unreviewed area is known — and this file states that as a judgement, which the + exit requires, rather than inferring it from a low count. +- **Blocker/Major regenerating across genuine repair attempts.** Each round's fix produced the + next round's findings on the same mechanism, four times. + +One further signal, and it is the one that settled it: **pass 5 returned as Blockers the very +requirement the human had withdrawn the day before** — that `unusable` and `undetermined` +distinguish their causes. The reviewer is not wrong that a collapsed record is less useful. +But a gate cannot clear a finding whose resolution the human has already declined, and a loop +that re-raises a decided question is no longer measuring the artifact. That is a +require↔withdraw pair in the §5 sense, and it is what made the stop mandatory rather than +discretionary. + +Open findings and their dispositions, including the two marked as a chosen cost rather than a +missed defect, are in `.context/codex-reviews/gate-b-rle-pass-5-dispositions.md` — which is +git-ignored, so what survives a clone is this section. + +## The decisions behind these cycles + +All Daniel's, in order. Recorded here because commit bodies are carried by the squash and +this file is carried by git on its own, and because two of them are choices a later reader +would otherwise read as oversights. + +1. **2026-09-01 — C1 dissolved** into the rollout after its own Gate A stopped on two tells; + no third prose plan; **plan-level Gate A skipped** for the sentence replacements after two + non-convergences, with verification moved to the artifact (asserts plus the combined Gate + B); execution ordered **A → B → C** on the sequencing finding. +2. **2026-09-02 — Plan C Tasks 19 and 20 dropped.** The deterministic slot discriminator is + not shipped; a general production goes to the loop-rule consolidation story. +3. **2026-09-02 — the `rle` slot infix is a recorded plan-local naming exception** under the + old rules that govern this cycle. No shipped rule admits the form. The cycle is pre-rule + and cannot mint a nonce, and the bare family already held 30 files that delete-before-call + would have destroyed. +4. **2026-09-02 — the knob-cause vocabulary and the model-cause obligation withdrawn** from + the grammar and both prompt copies. Accepted capability cost, stated: the record says + **that** a knob was unusable, no longer **why**. Whoever needs why reads the file and the + hook. +5. **2026-09-02 — close on the clearly-stuck exit** rather than a sixth round. +6. **2026-09-02 — citation completeness accepted as a documented residual risk.** A + contributor who omits a high-risk story and cites only a level-0 one gets a floor of 1, and + nothing verifies the header is complete. That is the settled header-is-sole-authority + design rather than an oversight: a completeness check would need an independent source of + truth for what *should* have been cited, and none exists. **The countermeasure is the + sampled human audit, not a parser.** No mechanism was built. +7. **2026-09-02 — no sixth Gate-B pass** after the closing repairs. What the post-close + commits carry instead: the honesty disclosure naming exactly which changes no pass + reviewed, a green battery over them, and two independent bot reviews across the whole PR. + None of that is a Gate-B pass, and the commit bodies say so. + +Decisions 6 and 7 both answer findings a reviewer raised and the human declined. They are +recorded as chosen costs so that a later reader meets them as decisions rather than as +defects nobody noticed. + +## The superseded single-plan artifact + +`docs/superpowers/plans/2026-08-29-review-loop-economics.md` is the single-plan version of this +change. It **opened at 31 Blocker/Major on its first Gate-A pass** and was replaced by the three +plans above. It is still in the tree, unmarked. + +This is recorded as history, not as a task. Plan C's revision 4 shipped a task to mark the file +in place; the approved spec puts any remedy to the supersession convention out of scope, Gate-A +pass 4 said so, and revision 5's replacement — a line in the closing commit body — was itself the +same remedy relocated, which pass 5 also said. The convention is a real gap and it belongs to a +story of its own; it is not this cycle's to fix, and a note here is not a mechanism. + +## Known limits of this record + +The per-pass files behind these numbers stay in `.context/` and will be overwritten by later +cycles; what survives is the counts above and the dispositions files committed beside them. No +finding is classified under both the old and the new severity rules in any of the three cycles, +so — exactly as the spec cycle recorded — **no demotion figure is derivable from this data**, only +a comparison of recorded mixes across cycles that reviewed different artifacts. diff --git a/docs/getting-started.md b/docs/getting-started.md index 0ec38c9..e5456e1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -31,17 +31,17 @@ settled decisions with rationale, not a wish list. **3. Gate A on the spec.** Claude sends the spec text to Codex (`mcp__codex__exec`) — a different model family, so it doesn't share Claude's blind spots. Blocker/Major findings get fixed, the review reruns on the revised spec: -three passes minimum, final pass clean — the one early exit is a pass that comes -back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` are +the floor its profile derives, final pass clean — the one early exit is a pass that +comes back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` are the counter, not an error. Your job: arbitrate disputed findings — Codex is advisory, and a dismissed finding needs a one-line reason. **4. Plan, and Gate A again.** `superpowers:writing-plans` turns the spec into a -task-by-task plan (each task starts with a failing test); the same 3-pass loop runs +task-by-task plan (each task starts with a failing test); the same loop runs at the derived floor on the plan. A flaw caught here never reaches code. **5. Implement.** `superpowers:executing-plans` works through the plan, test-first, -progress claims backed by test runs. If the Gate-A floor wasn't met, the hook says +progress claims backed by test runs. If the hook's own threshold wasn't met, it says so right when execution starts. **6. Quality battery.** The one command you wired at init (typecheck + lint + dead @@ -50,12 +50,14 @@ skipping locally only postpones the red. **7. Gate B on the diff.** Claude makes a `WIP:`-prefixed commit (gives Codex a range to read; the hook knows WIP doesn't end the cycle), then loops -`mcp__codex__review` the same way: three passes, final clean. Verification is by +`mcp__codex__review` the same way: the derived floor, final clean. Invalidation is by **content** — any change to included content present when the hook runs, even from a -formatter, flips it back to unsatisfied; `.context/` and untracked ignored paths are +formatter, flips it back to unsatisfied. What that proves is bounded, and the hook's own +source says so: the current fingerprint matches the one recorded on a counted call, which +is not evidence that Codex read those bytes; `.context/` and untracked ignored paths are excluded, and staging counts, because the fingerprint covers the index and that is what a commit carries. On -`✓ Codex Gate B satisfied (3/3 cycle, 3 on current fingerprint)`, the real commit replaces +`✓ Codex Gate B satisfied (/ cycle, on current fingerprint)` — three different numbers: the calls the hook counted this cycle, the hook's own reminder threshold, and the **consecutive** counted calls on the current fingerprint since it last changed. The first is not the calls you made: the hook withholds the count for a recognized failure envelope, the backgrounding notice, and a result it can get no text from. The third is a streak, not a tally — the hook keeps the last fingerprint and that streak, so a pass on a changed fingerprint restarts it and an earlier matching pass separated by a different fingerprint is not counted. None of the three is the floor §5 obliges — the real commit replaces the WIP via `git commit --amend`. **8. PR and bots.** Open the PR as usual; once the bots have commented, run @@ -81,9 +83,9 @@ close-out (8–9). Trivial changes travel lighter, within limits: **Gate B** may only when the change is behaviourally trivial **and** the story is eligible — a profiled one at effective level 0 (risk `trivial` *and* security `none`), an unprofiled one by the prior judgement call. The profile supplies eligibility, never the skip itself; the battery -is still owed and Gate A's floor is unchanged at every level. The caution bias is +is still owed; Gate A's floor derives from the profile exactly as Gate B's does, and what the axes never subtract is the baseline questions. The caution bias is for non-trivial work, judgment is allowed. Two knobs: `.context/codex-gate.floor` (any -positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off` +positive integer) moves the hook's reminder threshold, and `touch .context/codex-gate.off` silences the reminders in a scratch workspace (delete to re-enable; state keeps tracking while off, so nothing goes stale). @@ -102,8 +104,9 @@ smallest that matches your intent: 2. **One trivial change:** the hook warns, it never blocks. What §5 permits depends on the story: an **unprofiled** one keeps the old judgement call, while a **profiled** one may skip Gate B only at effective level 0 (risk `trivial` *and* security `none`), - still owes the battery, and records both the skip reason and its evidence entry in the - commit body. Gate A is not + still owes the battery, and records the skip reason, the battery result, the cycle's + provenance line, a skip record in place of the curve, and one evidence entry per cited + profiled story, in the commit body. Gate A is not skippable at any level. 3. **Pause a project:** `touch .context/codex-gate.off` (delete to re-enable; state keeps tracking, so nothing goes stale). diff --git a/docs/hardening-log.md b/docs/hardening-log.md index fbf874a..45b182c 100644 --- a/docs/hardening-log.md +++ b/docs/hardening-log.md @@ -123,3 +123,5 @@ escape `\|`, one line), `source` (gate-a|gate-b|bot|manual), | 2026-08-16 | docs-drift | sixth occurrence: PR #24 (CodeRabbit) — `docs/coding-workflow.md` and `docs/sparring-briefing.md` both told the reader to record the reviewer model by reading "the configured value at that moment", while the same section's own timing facts said `mcp-codex-dev` caches its model chain per project root. Two sentences in one document disagreeing, and the wrong one was the actionable instruction: after any model edit the config names a model the running server is not using, so a pass record built from it misstates who reviewed — which is the only thing that makes reviewer-family independence checkable | bot | major | 1 prose | Both documents now name the model the pass *ran under* as the thing recorded, state the cache boundary exactly (launch root at startup, any other root on its first call, cached until restart, and an unseen root is the exception where a post-startup edit does take effect), and name a DETERMINISTIC probe rather than "check the config": `mcp__codex__health` with the same `workingDirectory` as the gate call, reading `checks.config.effective.model`, which is the cached per-root resolution the gate call itself uses. NOT ESCALATED past the 2026-07-26 `2 lint` row: that check guards prose count claims and cannot reach a stale mechanism description, the over-escalation those rows warn about. NO DETERMINISTIC RUNG EXISTS for this: nothing can tell that a sentence about a cache is stale, and the probe is a recipe a human runs, not a check — it raises the floor by making the right value obtainable, and does not close the class. Sibling row this same date under `unverified-enforcement-claim` covers the empty-commit half of the same PR review | | 2026-08-17 | prompt-missing-stop-condition | first row of this base class here: §5's loop told the reader to keep looping and never said what a loop may swallow, so absorbing a finding that opened a new contract question and handing back a three-line repair-of-a-repair were equally defensible readings — field-minted by the kit's heaviest consumer across ~150 gate passes, and its own reason for the rule is that absorbing a contract question spends a decision the loop was never given | manual | major | P std | CLAUDE.md §5 loop paragraph + the same block in the workflow-init inline template. Guard: the absorb-vs-stop rule and the sentence bounding it — a finding correcting the correction just made is absorbed, one opening a new structural or contract question stops the loop, novelty and not size decides, and the stop is NOT an exit from the gate (floor, Blocker/Major filter and clean-final-pass all stand). Does NOT guard which findings count as structural, and says nothing about pass counts. | | 2026-08-17 | prompt-vague-criteria | first row of this base class here: §5 named "clearly stuck" as the loop's terminal state and never said how to recognize one, so a plateauing artifact could burn passes with nobody entitled to call it — in the field one 2848-line spec ran 34 Gate-A spec passes, and over the 19 that were measured the count fell from 43 to an oscillation between 2 and 7 while Blockers fell from 8 to 0-1, i.e. the substance converged and the number never did | manual | major | P std | CLAUDE.md §5 loop paragraph + the same block in the workflow-init inline template. Guard: the recognition rule — after about six passes read the Blocker curve rather than the total, and Blockers at 0-1 with every remaining finding individually fixable IS the stuck state — plus the smaller-specs guidance that follows from the same measurement. Does NOT guard a size threshold (deliberately unmeasured and shipped as guidance), and does not say where a plateau begins. | +| 2026-09-02 | docs-drift | seventh occurrence: PR #26 (CodeRabbit) — the dark-factory vision document, sitting on the same branch, still said the kit "has a fixed 3-pass floor", listed the review-economics story as "in flight" twice, and made both claims about the very change the branch ships. Every sentence was true when written and false the moment the branch merged. NEW SUB-SHAPE, and it is why this row exists rather than a note: the standing lens was carried on this cycle and the falsified file is one the diff never touches — it is not in the changed-path set, so nothing scoped to the diff could reach it, and the lens's own recipe (grep for where each changed value is described elsewhere) was run against `CLAUDE.md`'s vocabulary and not against the branch's other documents | bot | major | 1 prose | The lens now names the branch, not the diff, as its search surface: a document added or edited **anywhere on the same branch** can be falsified by a change it does not contain, and a co-shipped design document describing the current state of the thing being changed is the likeliest instance. PRIOR ROW: 2026-08-16 docs-drift (1 prose), and 2026-08-04 (P std) whose guard asks what the diff changes the size, value or position of — this finding is INSIDE that guard and outside its reach at once: the value did change and was described elsewhere, but "elsewhere" was scoped to the changed paths by everyone who ran it, including me. Repaired at the same rung, not escalated. NO DETERMINISTIC RUNG EXISTS: nothing can tell that a design document's description of current state went stale, and widening the grep to the whole branch is a recipe a human runs. It raises the floor and does not close the class | +| 2026-09-02 | verification-masks-failure | fifth occurrence: PR #26 (CodeRabbit) — Plan C task 25's completion assert tested only that the provisional wording was ABSENT (`grep -c … -eq 0`). A replacement that deleted the provisional passage and wrote no closed record at all would have passed it, which is precisely the outcome the task exists to prevent. The task did run correctly this cycle, so the mask never fired; it was found by reading, not by failing | bot | major | 4 test | The assert gained a positive arm beside the negative one: the closed curves must be PRESENT, with a cardinality floor (`grep -cE '^Findings( +[0-9]+,?)+' … -ge 3`). PRIOR ROWS under this fingerprint all share one shape — a check whose only assertion is that something is gone. WHAT GENERALIZES: an absence assert is half a check whenever the edit it guards is a replacement rather than a deletion, and the missing half is always the same one. This row's remedy is specific to task 25; the general form belongs to the loop-rule consolidation story, which owns the plan-assert conventions | diff --git a/docs/superpowers/plans/2026-08-29-review-loop-economics-plan-a-rules.md b/docs/superpowers/plans/2026-08-29-review-loop-economics-plan-a-rules.md new file mode 100644 index 0000000..3b28f5b --- /dev/null +++ b/docs/superpowers/plans/2026-08-29-review-loop-economics-plan-a-rules.md @@ -0,0 +1,949 @@ +# Plan A — the rules edits (floor predicate + severity semantics) + +> **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 parts 1 and 2 of the review-loop-economics change into both prompt copies — the +pass floor becomes a function of the cited story's profile, and finding severity is decided by +whether something in the system takes a different decision. + +**Spec:** `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` (revision 36). +Plan A implements §2, §2.1, §2.2, §2.4, §3, and §10 in part. + +**Story:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` + +> **Read the profile from that header at execution time.** This plan states no risk value, no +> security value, no validation mode and no pass count derived from any of them. + +--- + +## Plan A of three + +| Plan | Ships | Spec sections | +|---|---|---| +| **A — this one** | the floor predicate and the severity test | §2, §2.1, §2.2, §2.4, §3, §10 (partial) | +| **B** | the provenance line, the per-pass curve, the cycle nonce, slot naming | §2.3, §4, §5, §6 | +| **C** | rollout: falsified sentences, packaging, the evidence pack, the review loop | §7, §8 | + +**One Gate-B cycle** over the combined A+B+C diff, run and closed by Plan C. The Gate-A +cycles behind it are one per artifact: the spec's, and one per plan — five in the end, since +Plan C was split and C1 got its own before being dissolved. Their record is +`docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md`. +**Execution order A → B → C; Plan B may not open before Plan A's Gate-A loop closes.** + +**Plan C inherits these obligations**, which lived in a Gate-B section Plan A no longer has. A +finding whose section moved is relocated, not repaired: + +| Finding | What Plan C owes | +|---|---| +| pass-1 M8 | single-branch Gate-B recovery: delete only the failed branch, never both | +| pass-1 M9 | record the floor knob's existence and bytes before the cycle, compare after | +| pass-1 M10 | never `git add -u`; stage an explicitly inspected path set | +| pass-1 MINOR 12 | build the closing body with `mktemp`, not a fixed `/tmp` path | +| pass-1 B6 | evidence revalidated after every fix and again before the closing amend | + +--- + +## Global Constraints + +- **This Gate-B cycle runs under the rules in force at its start — the OLD ones.** The new + severity semantics, floor rule and record forms bind only **after** the closing commit ships + them. **Carry this sentence in the `additionalContext` of every Gate-B call**: a reviewer + applying the new Minor-or-below ceiling to the change that introduces it would under-iterate + on exactly the diff needing most iteration. +- **Prompt-only.** No file under `plugins/dev-workflow/hooks/` changes, and the floor knob + `.context/codex-gate.floor` is never written, never removed, never read for the derivation. + This is not a claim that nothing under `.context/` is written — Gate-B calls and commit events + write counters, fingerprints and markers there as always. +- **The §5 heading must keep matching `^#{1,6}[[:space:]]+([0-9]+\.)?[[:space:]]*Cross-Model Review`.** + `codex-gate.sh:94` greps `CLAUDE.md` for it to build every reminder's citation. +- **Every edit lands in both copies except Task 14**, which is `CLAUDE.md`-only and says why. + Where the two copies already differ, the difference is pre-existing, named in the accounting, + and left as it is. +- **§5's other closure rules are never restated, only referred to.** +- **Line numbers are provenance, never instructions.** Each task pastes its `grep -n` anchor as + it stood in the untouched tree; the edit is located by its OLD text. + +### The commit protocol + +**One WIP commit, opened at Task 1, amended by every later task and by Plans B and C, reviewed +once after Plan C, closed once.** + +> **Never `git commit --amend --no-edit` inside the cycle.** +> `plugins/dev-workflow/hooks/codex-gate.sh:763` recognizes a WIP commit by grepping the **Bash +> command string** for `-m ... wip`; an amend without `-m` is not recognized, and the hook resets, +> discarding the cycle's passes. Every amend restates `-m "WIP: review-loop economics"`. + +**The cycle records its base SHA once, at cycle open, and uses it for every diff and every +Gate-B call** — not `HEAD~1`, which moves if a snapshot is ever stacked. Task 1 prints it. + +--- + +## Old-conditions accounting + +Derived from the edits this plan makes, against §5 at HEAD. **Eleven passages, thirteen rows.** +Two passages diverge between the copies and get a row each; both divergences were found by +`diff` over the passage's full extent. + +| # | Passage | Copy | What the existing prose requires | Disposition | +|---|---|---|---|---| +| 1 | floor paragraph | both | a. a hard floor of 3 passes per run · b. Blocker/Major only · c. the count is the hook's · d. the hook cannot read findings · e. the hook cannot tell the spec run from the plan run · f. it resets at `writing-plans` · g. therefore Gate A is instruction-backed · h. a satisfied count is not a clean review · i. a TodoWrite per pass · j. fix Blocker/Major after each · k. Codex is advisory · l. validate before applying · m. dismissed finding → one-line why | a. **replaced** by the derived predicate (Task 1) · c. **moved** — the hook still counts, as its reminder threshold, not the obligation · **d–h kept in substance and rewritten in wording** in the second paragraph, since the replacement re-emits them around the new precedence — not kept verbatim, and the earlier claim that they were is corrected here · b **kept verbatim inside the replacement** · i–m **kept verbatim outside the replaced range** | +| 2 | `if pass 3 still` | both | the final pass must be clean; if the pass at 3 still finds Blocker/Major, keep going until clean or clearly stuck, then STOP and surface | **kept**, `pass 3` → `the pass at the floor` (Task 3) | +| 3 | `below 3` | both | the only early exit below the floor is a zero-finding pass; don't pad | **kept**, `below 3` → `below the floor` (Task 4) | +| 4 | pass-1 Minor sentence | both | below the floor nothing closes; a zero-finding pass is the only exception; a Blocker/Major-free pass 1 carrying a Minor keeps looping | **kept**, `pass 1` → `pass below the floor` (Task 9). The sentence that inverts at floor 1 | +| 5a | pass-report paragraph | `CLAUDE.md` | a. from pass 4 onward, three lines · b. carrier is your own status report · c. never the Codex reply · d. never the findings file · e. trend · f. cluster · g. require↔withdraw · h. the five tells · i. any-two makes stop-and-surface mandatory · j. "clearly stuck" is not a precondition · **k. "you report the tells" — second person** | **untouched.** Task 10 inserts a new paragraph before it and modifies nothing in it | +| 5b | pass-report paragraph | template | a–j as above · **k′. "report the tells" — imperative** · plus different wrapping | **untouched**, same reason. The divergence is pre-existing | +| 6 | incomplete-pass | both | an incomplete pass is not a review: don't act on the partial list, don't count it toward the floor, don't read "no Blocker/Major visible" as clean | **kept**, `the 3-pass floor` → `the floor` (Task 5) | +| 7a | Gate A loop | `CLAUDE.md` | a. two runs, each its own 3-pass loop · b. one broad prompt, re-run each pass · c. don't narrow per-dimension · d. the required opening phrase · e. coverage floor not a cage · f. every finding with severity and confidence · **g. the citation `` (`docs/prompt-standards.md`, "coverage first, filter later") `` ** · h. one line per finding · i. literal `NO FINDINGS` · j. settle mechanically before each read pass | **a kept**, `3-pass loop` → `loop at the derived floor` (Task 6); **b–j untouched, g included** | +| 7b | Gate A loop | template | a–f, h–j as above · **g′. the citation is ABSENT** · plus different wrapping | same edit to `a`; the missing citation is pre-existing and left alone | +| 8 | `where the 3 come from` | both | a. re-review after every fix · b. because a fix changes the diff · **c. and the hook invalidates the prior pass — which is where the 3 come from** | **a kept.** **b and c are DELIBERATELY DROPPED and replaced** (Task 7): the new design makes c false — the floor comes from the profile, not from invalidation — and b's framing put the hook in the causal position. What replaces them: a fix changes the artifact, so the prior review no longer covers it; the hook merely notices, at commit time. Recorded as a drop rather than as a keep, which the earlier row got wrong | +| 9 | Lenses | both | a. lenses are different questions, not more passes · b. the 3-pass floor is unchanged · c. the Blocker/Major filter is unchanged · d. the file-first protocol is unchanged · e. the clean-final-pass rule is unchanged | a **kept and sharpened** · **b deliberately dropped and replaced by its negation** — the floor is what this change makes variable · c, d, e **kept verbatim** (Task 8) | +| 10 | `Changing a profile:` | both | a. proposes the complete resulting header · b. human confirms, both directions · c. an agent never moves it alone · d. correct the header, append one log line · e. any axis change voids every prior override · f. `+abuse-path` follows current security · g. passes under the lower profile keep counting · h. only the final clean pass must run under the current profile · i. fold mid-cycle edits into the WIP by amend | **all nine kept verbatim**; §2.4's rules appended after them, never merged in (Task 11) | +| 11 | Severity | both | Blocker = wrong/unsafe/breaks invariant · Major = design flaw → rework · both must resolve · Minor and Nit → collect, never iterate | **all four kept verbatim**; the reachability test appended, and nothing else. Task 12 ships spec §3 alone — no consequence for the per-pass counts, the clusters or the stop thresholds, and no precedence against any loop rule, all of which §9 excludes. One sentence says the interaction is not settled here and gives the unresolved-state action; **`CLAUDE.md` alone adds the owner's path** (Task 14) | + +**Nothing in §5 outside these eleven passages is edited.** Gate B, reviewing the combined diff, +is what confirms that against this table. + +**Two rows added during the Gate-B cycle**, because two decision procedures were rewritten by +its findings and this table is where old conditions are accounted for: + +| # | Passage | Copy | What the pre-fix prose required | Disposition | +|---|---|---|---|---| +| 12 | profile-reading cases | both | a. cites no story → unprofiled · b. no profile line → unprofiled · c. present but unresolvable → stop, covering unparseable lines, values outside the enums, two profile blocks, a citation resolving to nothing, and the semantic mismatches | a and b **kept verbatim**. c **split**: the failure-to-obtain-the-file half — including "a citation resolving to nothing" — becomes its own case 3 with four named sub-causes and a decidable order of tests, because an agent that never read the file can answer neither a nor b; the remaining syntactic and semantic failures stay as case 4, **unchanged in substance**. Nothing dropped | +| 13 | `headSha` / branch agreement | both | a. both branches must have reviewed the same commit · b. take it from the head commit each call reports · c. capture it with the branch's result, not later · d. require exact equality before summing · e. record the full 40-character name | **b deliberately dropped**: the reviewer tool reports no head commit, so the condition was unsatisfiable as written. **a narrowed to what the mechanism supports** — both branches are *issued against* the same commit, stated with the explicit note that this is not evidence either reviewed it. c, d, e **kept**, now applied to the value passed rather than a value read back | + +--- +## Task 1: The floor predicate + +**Spec:** §2, §2.1 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:72:**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major +plugins/dev-workflow/commands/workflow-init.md:272:**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major +``` + +> The text on disk ends **mid-line**: ` Open a TodoWrite "Codex pass N" per pass;` continues the same line after `review.` Match exactly this and no more; what follows stays. +> +> **The between-cycle rule implements spec §2's one-value sentence, verified against revision 36 before it was written.** §2 says the value is one *"because they derive from **the same cited-story set**"* — a claim about the **source**, not about freezing a number in time. So the value is a function of that set's **current** confirmed profiles, read fresh wherever §5 already requires reading them: one source, therefore exactly one value at any moment. +> +> **A snapshot taken once at the first cycle's open was considered and rejected.** It would be "a remembered or copied value", which §5's Profiles section forbids in those words — *"the story header is the single writable copy … read the values fresh at each pass, never a remembered or copied value"* — and it points the wrong way on invariant 2: a human-confirmed **raise** between cycles would then leave work still in flight reviewed under the weaker profile, which is the under-review direction. +> +> **§2.4 does not merely permit this; it routes the question here.** Its pass-count rules *"apply while §5 says a gate is running and are silent otherwise"*, and it states that *"what §5 says about when a gate runs — including how a moving profile or cited set bears on that — is §5's, unchanged and deliberately not summarised here."* The between-cycle case was never a spec gap. §2.4 does not *supply* the answer — it is silent outside a running gate and says so — it **delegates** the question, and §5's read-fresh rule is what answers it. + +- [ ] **Replace, in both copies.** OLD: + +``` +**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major +only), counted by the hook.** The hook counts passes but can't read findings or +tell the spec run from the plan run (it resets at `writing-plans`), so Gate A — +the spec run especially — is instruction-backed: a satisfied count is not a clean +review. +``` + +NEW: + +``` +**Both gates are a LOOP with a HARD FLOOR: a minimum number of passes per run +(Blocker/Major only), derived from the cited story's profile.** +The derivation is max(risk, security): a value of 0 gives a floor of 1; every +resolvable profile above that, and an artifact citing no story, gives 3. Two levels, +not three — `high` takes its rigor from lens sets and evidence mode, not from extra +passes. A cited story whose profile is present but unresolvable stops and surfaces +under the existing rule; it does not fall through to 3, because reading it as 3 would +turn a stop condition into a silent default. Across a cited set the floor is 1 if and +only if the set is non-empty and every member is profiled, resolvable and at level 0 +— all four conditions, since "every cited story" is vacuously true of an empty set; +no story cited, or any cited story unprofiled, gives 3. One derived value governs all +three cycles: the Gate-A spec loop, the Gate-A plan loop and the Gate-B cycle. Not +because they are one cycle — they are three — but because they derive from the same +cited-story set. That value is a function of the current confirmed profiles of that set, +read fresh wherever this section already requires them to be read, so wherever a value can be +derived at all there is exactly one, because there is one source. Some states derive **no** value +rather than a second one, and each stops rather than defaulting: governing headers that +disagree; a cited profile that is present but unresolvable; and a `Story:` header that cannot +be read. A change to a profile or to the set +therefore binds every open and future cycle — a raise costs an affected open cycle a +further pass under the current profile, as the profile-change rule below requires — while a +cycle that has already closed +stands, its close having been valid under the profile current when it closed, which is the +cycle-level form of passes already run keeping their count. **The set has one authority: +the artifact's `Story:` header, which carries the path of every cited story.** Nothing else +is a citation. A story path appearing anywhere else in an artifact's body — including a +sentence placing a story *outside* this change's scope — **contributes nothing to the cited +set and nothing to the floor derivation**, which is the only claim made about it; it may still +be a perfectly good cross-reference for any other purpose. And **an agent +deriving the set reads that header and does not grep the body for story paths**, because a +grep finds mentions and cannot tell a citation from a disclaimer. **Each cycle's governing header is the +header of the artifact it reviews**: the spec's for the Gate-A spec loop, the plan's for the +Gate-A plan loop, and — since a Gate-B cycle reviews a diff and has no header of its own — +**the union of the `Story:` headers of every plan contributing to that diff, which the Gate-B +call must carry in full**, as this section already requires of every cited path. **Every expected artifact contributes a set — a spec, and every plan contributing to the +reviewed diff — and an expected artifact whose `Story:` header is absent contributes the empty +set rather than dropping out of the comparison.** **One path per entry**: a header citing several +stories carries several entries, one path each. Exact duplicate paths are one member; entries +naming different stories are different members; and a header that cannot be read as a list of +paths that way is malformed and stops, reporting that as the cause rather than as a +disagreement. **Before each pass the deriving agent +compares every such set, and again before a clean pass is accepted as the cycle's final pass.** +A header or profile that changed during that pass means the pass is not final — the same +answer a change gets at every other read point. Where they name different sets the premise of a single value has +failed: **stop and surface the disagreement** rather than deriving from either, exactly as an +unresolvable profile stops rather than defaulting. + +**The derived floor is the pass count a cycle owes, and the hook's ratio is a reminder +threshold that controls nothing.** The hook still counts passes, and it still can't read +findings or tell the spec run from the plan run (it resets at `writing-plans`), so +Gate A — the spec run especially — is instruction-backed: a satisfied count is not a +clean review, and a below-threshold reminder is noted in the pass report and disregarded +where the cycle's own closure rules are satisfied. This replaces the pass-count number +and nothing else. Every other rule stated here about how a cycle closes stands as +written, and none of them is restated — a summary is where their conditions would get +dropped. Nothing here writes the floor knob: it stays the user's, never written, never +removed, never read for this derivation. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "derived from the cited story's profile" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Open the cycle.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit -m "WIP: review-loop economics" +git rev-parse HEAD~1 # the cycle's base — Plan C uses this for every diff and Gate-B call +``` + +--- + +## Task 2: The residual and the gate-off surface + +**Spec:** §2.1, §10 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:80:advisory — validate before applying; dismissed finding → one-line why. +plugins/dev-workflow/commands/workflow-init.md:280:advisory — validate before applying; dismissed finding → one-line why. +``` + +> Match the floor paragraph's final line including its trailing newline, and re-emit it followed by the two new blocks. The gate-off list is explicitly **not** exhaustive. + +- [ ] **Replace, in both copies.** OLD: + +``` +advisory — validate before applying; dismissed finding → one-line why. +``` + +NEW: + +``` +advisory — validate before applying; dismissed finding → one-line why. + +**Named residual:** the hook's messages state its own threshold as an obligation, so at a +floor of 1 they report a shortfall the cycle does not owe. Hook text is out of scope here +by decision; what makes that tolerable is the precedence rule above plus the hook exiting +0 on every branch, not the reminder being harmless. + +**The gate-off surface — routes known today, not a complete list**, since an enumeration +read as complete guarantees the routes it omits. One route is created here: a stated floor +the cited set does not license, which could not exist before there was a derived floor to +state. Pre-existing and unchanged: omitting a higher-risk cited story; minting or editing a +profile to level 0; presenting an incomplete cited set; falsifying evidence entries; +silencing reminders; or not running a pass and reporting that it ran. A user-set floor is +not the lever — it moves what the hook says, not what the cycle owes. +None of this is a guard: the floor is produced by the agent and nothing checks it against +the cited profiles. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "routes known today, not a complete list" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 3: `pass 3` at the clean-final-pass rule + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:77:final pass must be clean — if pass 3 still finds Blocker/Major, keep going until +plugins/dev-workflow/commands/workflow-init.md:277:final pass must be clean — if pass 3 still finds Blocker/Major, keep going until +``` + +- [ ] **Replace, in both copies.** OLD: + +``` +final pass must be clean — if pass 3 still finds Blocker/Major, keep going until +``` + +NEW: + +``` +final pass must be clean — if the pass at the floor still finds Blocker/Major, keep going until +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "if the pass at the floor still finds" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 4: `below 3` at the early-exit rule + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:79:below 3 is a pass with **zero** findings; don't manufacture findings to pad. Codex is +plugins/dev-workflow/commands/workflow-init.md:279:below 3 is a pass with **zero** findings; don't manufacture findings to pad. Codex is +``` + +- [ ] **Replace, in both copies.** OLD: + +``` +below 3 is a pass with **zero** findings; don't manufacture findings to pad. Codex is +``` + +NEW: + +``` +below the floor is a pass with **zero** findings; don't manufacture findings to pad. Codex is +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "below the floor is a pass with" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 5: `3-pass floor` in the incomplete-pass rule + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:236:act on the partial list, don't count it toward the 3-pass floor, and don't read "no +plugins/dev-workflow/commands/workflow-init.md:421:act on the partial list, don't count it toward the 3-pass floor, and don't read "no +``` + +- [ ] **Replace, in both copies.** OLD: + +``` +act on the partial list, don't count it toward the 3-pass floor, and don't read "no +``` + +NEW: + +``` +act on the partial list, don't count it toward the floor, and don't read "no +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "count it toward the floor" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 6: `3-pass loop` in the Gate-A description + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:300:- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).** Run on the +plugins/dev-workflow/commands/workflow-init.md:485:- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).** Run on the +``` + +- [ ] **Replace, in both copies.** OLD: + +``` +- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).** Run on the +``` + +NEW: + +``` +- **Gate A — Spec, then plan (TWO runs, each its own loop at the derived floor).** Run on the +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "each its own loop at the derived floor" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 7: The re-review rationale + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:334: @AGENTS.md. Re-review after every fix — a fix changes the diff and the hook +plugins/dev-workflow/commands/workflow-init.md:518: @AGENTS.md. Re-review after every fix — a fix changes the diff and the hook +``` + +> **Third attempt at one sentence, and both lines are replaced.** The original, `which is where the 3 come from`, was a causal claim the new design makes false. Revision 1 made the hook the *cause* of the obligation. Revision 2 replaced only the second line, leaving `a fix changes the diff and the hook` in front of it, so the sentence read "the hook no longer covers the artifact". +> +> The claim eliminated is *the hook causes the re-review obligation*. The load-bearing half — `a fix changes the artifact, so the prior review no longer covers it` — stands without the hook; the trailing clause describes what the hook does and is true only while it exists, which is a description, not the cause. + +- [ ] **Replace, in both copies.** OLD: + +``` + @AGENTS.md. Re-review after every fix — a fix changes the diff and the hook + invalidates the prior pass, which is where the 3 come from. +``` + +NEW: + +``` + @AGENTS.md. Re-review after every fix — a fix changes the artifact, so the prior + review no longer covers it. The hook merely notices, at commit time. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "review no longer covers it. The hook merely notices" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 8: The Lenses rule + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:403:Lenses are **different questions, not more passes.** The 3-pass floor, the Blocker/Major +plugins/dev-workflow/commands/workflow-init.md:582:Lenses are **different questions, not more passes.** The 3-pass floor, the Blocker/Major +``` + +> **Both lines, for the same reason as the previous task.** The old tail says the floor "is unchanged". Revision 3 replaced the first half and left that tail, so the shipped sentence still told a reader the floor was unchanged in the change that makes it profile-dependent. The claim eliminated is *the floor is unchanged*. + +- [ ] **Replace, in both copies.** OLD: + +``` +Lenses are **different questions, not more passes.** The 3-pass floor, the Blocker/Major +filter, the file-first findings protocol and the clean-final-pass rule are unchanged. +``` + +NEW: + +``` +Lenses are **different questions, not more passes** — they change what a pass asks, never +how many a cycle owes. The Blocker/Major filter, the file-first findings protocol and the +clean-final-pass rule are unchanged. The floor is not among them: it is no longer a fixed +number but derives from the profile and the cited set. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "derives from the profile and the cited set" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 9: The pass-1 Minor sentence + +**Spec:** §2 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:126:the only exception, exactly as above; a Blocker/Major-free pass 1 carrying a Minor keeps +plugins/dev-workflow/commands/workflow-init.md:322:the only exception, exactly as above; a Blocker/Major-free pass 1 carrying a Minor keeps +``` + +> **The sentence that inverts at a floor of 1**, where pass 1 *is* the floor. It contains no digit `3`, so no regex over the other sites reaches it. + +- [ ] **Replace, in both copies.** OLD: + +``` +the only exception, exactly as above; a Blocker/Major-free pass 1 carrying a Minor keeps +``` + +NEW: + +``` +the only exception, exactly as above; a Blocker/Major-free pass below the floor +carrying a Minor keeps +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "a Blocker/Major-free pass below the floor" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 10: The pass report + +**Spec:** §2.2 + +**Site** — no `grep -n` line, deliberately: this task's target does not exist in the untouched tree. It is the text an earlier task inserts, so it is located by its OLD text below and by nothing else. + +> Inserted **before** the existing paragraph, which is not modified — including the `you report the tells` / `report the tells` divergence between the copies, which is pre-existing (accounting rows 5a/5b). Do not harmonize it. + +- [ ] **Replace, in both copies.** OLD: + +``` +**From pass 4 onward every pass report carries three lines.** +``` + +NEW: + +``` +**Every pass report states three things about the floor**, from pass 1 onward: the +derived floor, the risk and security values read, and the cited stories they were read +from. A report giving the number alone leaves a reader unable to check the derivation +while passes are still being spent — which is the only time checking it is cheap. Where +no story is cited, or a cited story is unprofiled, the report says so in place of axis +values; a multi-story set names each story and its values. This is owed by every pass; +the three lines below are owed from pass 4 and are a different obligation. + +**From pass 4 onward every pass report carries three lines.** +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "the cited stories they were read" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 11: A profile or cited set that moves mid-cycle + +**Spec:** §2.4 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:488:discard the accumulated passes. +plugins/dev-workflow/commands/workflow-init.md:667:discard the accumulated passes. +``` + +> The nine conditions of the `Changing a profile:` paragraph are kept verbatim; §2.4's rules are **appended after them**, never merged in. + +- [ ] **Replace, in both copies.** OLD: + +``` +discard the accumulated passes. + +**What this does not do:** +``` + +NEW: + +``` +discard the accumulated passes. + +**While a gate is running, the floor derives from the current profile at each pass.** +Passes already run keep counting; closing requires the floor as currently derived. These +are pass-count rules, so they apply while a gate is running and are silent otherwise — +what governs when a gate runs is unchanged and deliberately not summarised here. + +**Any profile change costs at least one further pass**, in either direction and whether or +not the floor number moves, because the final clean pass must run under the current +profile — so no already-banked pass can be it. That further pass must itself be clean and +every other closure duty must be satisfied; it is one more pass, not a licence to close on +the next one. What a lowering drops is whatever the changed values drop, not a fixed pair: +a mode-only override changes the evidence obligations while leaving the axis-derived lens +sets alone, and security `high` → `standard` keeps the security lens set while changing +what evidence is owed. Every derived obligation is recomputed from the current profile. + +**The cited set is re-read at each pass, and the final clean pass runs against the current +set** — whenever its membership changes, not only when the floor number moves. Adding a +high-risk story to a set already at floor 3 leaves the number alone while adding that +story's lens set, its evidence obligations and its review scope; a pass run before it +joined did not cover them. Removing a story recomputes obligations from the current set +and so does remove that story's lenses and evidence duty — but it never discharges an +accepted in-set Blocker or Major: the acceptance put that finding in the fix set, not the +citation. + +**What this does not do:** +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "Any profile change costs at least one further pass" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 12: Severity semantics + +**Spec:** §3 + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:496: rework) → both must resolve. Minor · Nit → collect, never iterate. +plugins/dev-workflow/commands/workflow-init.md:675: rework) → both must resolve. Minor · Nit → collect, never iterate. +``` + +> The four severity definitions stay verbatim; the reachability test is appended as the procedure that sets a **ceiling** on them. +> +> **Task 12 ships spec §3 and nothing else** — the procedure, the exclusions, the symmetric instrument carve-out, the rationale rule, coverage-first and the kinship sentence. It chooses no precedence against any loop rule and states no consequence for the per-pass counts, the clusters or the stop thresholds. Earlier revisions did, three times, each time reaching past spec §9's exclusion of "the §5 loop-rule consolidation and everything its successor story owns", and each removal found another layer underneath. What remains is one sentence saying the interaction is **not settled here** — a scope disclaimer in §9's own pattern, not a rule. +> +> **This is the plan's one deliberate divergence between the copies, and it is stated because the constraint above requires that.** `CLAUDE.md` adds a second sentence naming the successor story's path; the scaffolded template does **not**. The template writes a `CLAUDE.md` into somebody else's repository, where `docs/superpowers/stories/…` does not exist and is never scaffolded. What forbids it is the **architecture dependency boundary** — nothing may depend on this repo's internal layout — together with **prompt-standards item 11**, which the scaffolded template must satisfy on its own. (Not invariant 7, which governs `examples/` as read-only reference and says nothing about story paths in templates.) A shipped scaffold citing a path only this checkout has would make every initialized project carry an unresolvable authority for part of its own gate semantics. Both copies carry the disclaimer; only this repo's copy carries the pointer, and the successor story carries the reciprocal so the handoff is named on both sides regardless. + +- [ ] **Replace, in both copies.** OLD: + +``` + rework) → both must resolve. Minor · Nit → collect, never iterate. +``` + +NEW: + +``` + rework) → both must resolve. Minor · Nit → collect, never iterate. + + **Deciding severity — one procedure. The subject list is illustration, not a second + rule.** Name what in the system consumes this text — whatever *acts* on it — and the + decision that act takes differently if the text is wrong. Both are required. If you + cannot name both, the finding is Minor or below: collect, never iterate. + + The exclusions are contract, not commentary. The reader must consume the text in the + system's *operation*, not in reviewing it — the review pass raising the finding is not + an in-system reader of the text it reviews; without this the test demotes nothing. + Gates remain legitimate readers of rule text they will later apply. A human reader never + satisfies the test — the prose exemption already prices that cost as non-gating. The + list of reader kinds is illustrative, not closed, because this ships into projects whose + readers we have never seen. The test sets a ceiling, not a floor, and never chooses + between Blocker and Major — the four definitions above still decide that. The instrument + carve-out is symmetric: an instrument finding keeps its severity whenever it shows the + instrument changes what a gate concludes about product behaviour — a false green, and + equally a false red or a check blocking a valid change. Rationale prose is Minor only + when no rule's application depends on it, not categorically: `docs/prompt-standards.md` + requires rules to carry their why, so rationale a reader must consult to apply a rule + passes the test. This removes arbitrariness, not judgement. Coverage-first is unchanged + — the reviewer reports every finding with severity and confidence; the filter is ours. + + This is the finding-level analog of the path-level prose exemption: one principle at two + granularities — text that *describes* the product versus text that *is* the product. + + **How this demotion bears on the loop-health measures — the per-pass counts, the finding + clusters and the stop thresholds — is not settled here, and this change does not settle it. + Until it is, a pass whose outcome would turn on that question reports the question and + stops rather than deciding it** — the same answer any unresolved gate question gets. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "what in the system consumes this text" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 13: Activation + +**Spec:** §10 + +**Site** — no `grep -n` line, deliberately: this task's target does not exist in the untouched tree. It is the text an earlier task inserts, so it is located by its OLD text below and by nothing else. + +> **Plan B extends this list rather than rewriting it** — §10 says extending is safe and replacing is not. +> +> The partial-adoption trigger is **semantic, not a list of spellings**. Naming `pass 3`, `below 3` and `3-pass floor` missed the ones a partial merge happens to leave: the Gate-A loop description, the pass-1 closure rule and the re-review rationale each carry a fixed-three claim, and a merge can take some tasks and not others. + +- [ ] **Replace, in both copies.** OLD: + +``` +None of this is a guard: the floor is produced by the agent and nothing checks it against +the cited profiles. +``` + +NEW: + +``` +None of this is a guard: the floor is produced by the agent and nothing checks it against +the cited profiles. + +**When these rules bind.** From the commit that ships them, and a cycle already running +finishes under the rules it started with. Where a cycle's starting rules cannot be +established it takes the stricter reading of every part this change touches — at minimum +floor 3, and severity classified without the demotion; each further rule this change ships +adds its own strict reading to this list. Not a re-derivation, which could hand a level-0 +cycle a floor of 1 and skip passes on the strength of not knowing when it started. A user +knob set above 3 is not lowered by this fallback. A revert is itself a shipping commit for +the old rules, and the activation rule wins wherever the start is determinable; the +fallback covers only where it is not. + +**Downstream has no shipping commit.** Adoption binds from the `/workflow-init` run that +actually writes the text — which may write nothing, be declined, or be merged in part — +so these rules bind only over the text a project's `CLAUDE.md` actually contains, and a +partial adoption can persist undetected. A project taking the floor rule without the +severity test gets a floor whose docs-only question the severity test is what settles. +**A partial adoption can leave a project's floor undefined or self-contradictory.** The rule +is a coherence requirement, stated semantically rather than as a list of spellings, and it +runs in **both** directions: **exactly one definition of the floor must be present, and every +statement that defines or constrains the floor, or makes closing depend on it, must resolve to +that one definition.** **The unknown-start fallback is not a second definition**: it is +explicitly conditional on a cycle's starting rules being undeterminable and governs only that +state, so it coexists with the predicate rather than competing with it. Everything else +likewise keeps its own footing and is **not** required to derive from the floor: **the other closure and stop predicates** — assigned-fix-set membership, a new +structural question, an accepted Blocker or Major, the tell thresholds; **independent reporting +and diagnostic ordinals**, such as a duty owed from a given pass onward; and **the hook's +reminder threshold together with any descriptive or historical pass number**, which say what a +tool reports or what once happened rather than what a cycle owes. Four states break it, and the list is **not exhaustive**: a fixed-number or +specific-pass obligation surviving beside the derived predicate; a claim or dependency on a +derived floor with no predicate to define it; **no definition at all**; and **two definitions +at once**. A +merge can produce any of them: the Gate-A loop description, the pass-1 closure rule and the +re-review rationale each carry a fixed-three claim and can be taken or left independently of +the predicate itself. In any such state nothing here resolves which rule governs: **stop, and +have a human complete or revert the adoption, before running a gate under it.** What prompt text can do about downstream +adoption is limited, and that limit is what this paragraph states. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "a cycle already running" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated post-edit tree — the pattern lies on one line, contains no `**`, and is passed after `--` so a leading `-` cannot be read as an option. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 14: The successor pointer — `CLAUDE.md` only + +**Spec:** §9 deferral + +**Site** — no `grep -n` line, deliberately: this task's target does not exist in the untouched tree. It is the text an earlier task inserts, so it is located by its OLD text below and by nothing else. + +> **This task edits one copy and not the other, and that is the whole point.** The previous task's disclaimer ships to both. This pointer names a path that exists only in this repository, so it must **not** reach the scaffolded template — that template writes a `CLAUDE.md` into somebody else's project, and a shipped rule citing a path only this checkout has would leave every initialized project holding an unresolvable authority for part of its own gate semantics. +> +> The handoff is still named on both sides regardless of which copy a reader holds: the successor story carries the reciprocal obligation in its own §4. + +- [ ] **Replace, in `CLAUDE.md` only.** OLD: + +``` + stops rather than deciding it** — the same answer any unresolved gate question gets. +``` + +NEW: + +``` + stops rather than deciding it** — the same answer any unresolved gate question gets. + That question is owned by the loop-rule consolidation work in + `docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md`. +``` + +- [ ] **Assert the new text is present in `CLAUDE.md`, and absent from the template.** + +```bash +grep -cF -- "owned by the loop-rule consolidation work" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: **`1` and `0`** — the divergence is the point, and the second number is as load-bearing as the first. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 15: The battery, and the hand-off to Plan B + +Plan A runs no Gate-B cycle. The single cycle covering all three plans opened at Task 1 and is +reviewed and closed by Plan C. + +- [ ] **Run the battery, minus one step, for a stated reason** + +```bash +shellcheck --shell=sh plugins/dev-workflow/hooks/codex-gate.sh && \ +shellcheck --shell=sh --exclude=SC2015 plugins/dev-workflow/hooks/codex-gate.test.sh && \ +shellcheck --shell=sh scripts/check-invariants.sh && \ +shellcheck --shell=sh --exclude=SC2015 scripts/check-invariants.test.sh && \ +shellcheck --shell=sh scripts/check-version-bump.sh && \ +shellcheck --shell=sh scripts/check-version-bump.test.sh && \ +HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh && \ +HOOK_SH=dash dash plugins/dev-workflow/hooks/codex-gate.test.sh && \ +sh scripts/check-invariants.test.sh && sh scripts/check-invariants.sh && \ +sh scripts/check-version-bump.test.sh && \ +claude plugin validate . --strict && echo "BATTERY-GREEN (version-bump deferred)" +``` + +> **`sh scripts/check-version-bump.sh main` is deliberately absent, and only that one step.** Plan +> A changes a path under `plugins/dev-workflow/` while the manifest bump is Plan C's, so the +> checker **would correctly fail here**. `AGENTS.md` states its precondition: it compares +> *commits*, and run mid-work it reports clean and uselessly. **This is a deferral of one step +> with a named reason, not licence to skip the rest** — `scripts/check-version-bump.test.sh`, the +> suite, still runs, because it does not depend on the working tree's bump state. + +- [ ] **Hand off to Plan B** + +Plan B amends the same WIP commit with the same message and uses the base SHA Task 1 printed. +Plan A's Gate-A loop must have closed first. + +--- + +## Self-Review + +**Spec coverage.** §2 → Tasks 1–9. §2.1 → Tasks 1–2. §2.2 → Task 10. §2.4 → Task 11. §3 → Task +12. §10 activation, revert, downstream adoption, gate-off surface → Tasks 2 and 13, partial by +design and written to be extended by Plan B. The §9 deferral → Tasks 12 and 14. §2.3, §4, §5, +§6 → Plan B. §7, §8 → Plan C, with the five relocated obligations named above. + +**Placeholders.** None. + +**Type consistency.** `max(risk, security)`, "derived floor", "reminder threshold", "cited set" +and "level 0" are used identically throughout and match the spec's spellings. + +**Known limit.** The replacement wordings are proposals, not transcriptions — the spec pins the +rules, not the sentences. Gate B, reading the real edits, is what checks what they do to the +shipped files; this plan's own check per task establishes only that each edit landed at its +site. diff --git a/docs/superpowers/plans/2026-08-29-review-loop-economics.md b/docs/superpowers/plans/2026-08-29-review-loop-economics.md new file mode 100644 index 0000000..fb2d402 --- /dev/null +++ b/docs/superpowers/plans/2026-08-29-review-loop-economics.md @@ -0,0 +1,748 @@ +# Review-loop economics (pass floor + severity semantics) — 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:** Make the mandatory pass floor a function of the story profile, and decide finding +severity by whether something in the system takes a different decision. + +**Architecture:** Two mirrored prose edits — `CLAUDE.md` §5 and the inline template in +`/workflow-init` — plus the user-facing sentences they falsify, plus packaging. **No code. Nothing +under `plugins/dev-workflow/hooks/` is touched and no hook state file is written.** + +**Tech Stack:** Markdown prompts; POSIX shell for every check; `git` for the evidence records. + +**Spec:** `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` (revision 36, Gate A +closed clean at pass 34). **Read it alongside this plan.** + +**Story:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` — +`risk high · security none · battery+check+verification`. **Read the profile from that header.** + +**This is a rewrite.** The first version drew 20 Blocker/Major at Gate-A pass 1 from four habits: +typed anchors that did not exist, references to spec contents that no longer existed, checks that +could not fail, and — worst — ordinary commits inside a Gate-B cycle, in a plan whose subject is +§5. Every one is structurally prevented below: **anchors are pasted from `grep -n`, the passage +list lives here because the spec no longer holds it, every check's failing output is recorded, and +the whole prompt change is one Gate-B cycle with one `WIP:` snapshot.** + +--- + +## Global Constraints + +Verbatim from the spec. Every task's requirements implicitly include these. + +- **Prompt-only.** No file under `plugins/dev-workflow/hooks/` changes; no hook state file is + written, in particular not `.context/codex-gate.floor`. +- **The §5 heading must keep matching `^#{1,6}[[:space:]]+([0-9]+\.)?[[:space:]]*Cross-Model Review`.** + `codex-gate.sh:94` greps `CLAUDE.md` for it to build the citation every reminder prints. +- **`plugins/dev-workflow/commands/workflow-init.md` must keep exactly one `^Target model:` line.** + `scripts/check-invariants.sh` fails on any other count. Verified now: **1**. +- **Every rule lands in BOTH copies**, except where a difference is deliberate and stated. +- **§5's other closure rules are never restated, only referred to.** Three spec revisions were + spent on this: each summary of the triviality skip dropped a different condition. + +### The commit protocol — read this before Task 1 + +**Tasks 1–5, 8 and 9 are ONE Gate-B cycle, not seven.** They edit the same two prompt files plus +packaging, and §5 gates the *cycle*, not each edit. So: + +1. **Task 1 opens the cycle** with a commit whose message begins `WIP:`. Every later task in the + cycle **amends that commit**, keeping the `WIP:` prefix and carrying the body forward. +2. **`mcp__codex__review` runs against that WIP commit**, `baseSha` = its parent. +3. **Re-review after every fix.** A fix changes the diff and invalidates the prior pass. +4. **The cycle closes with `git commit --amend -m ""`** — the first message without + `WIP:` — carrying the evidence entry, the provenance line and the curve. + +**An ordinary `git commit` inside the cycle reads to the hook as the cycle closing and resets the +counters.** The first version of this plan instructed exactly that, seven times. + +**Task 7 is outside the cycle** and commits normally: every path it stages is `docs/**.md` or +`README.md`, so §5's prose exemption applies and Gate B is N/A — **but only if nothing else is +staged**, since a mixed commit forfeits it. **Task 0 is also outside** and is likewise N/A +(`docs/superpowers/**.md` only). + +--- + +## File Structure + +| File | Responsibility | Gate B | +|---|---|---| +| `CLAUDE.md` §5 | the live rules | full (in the cycle) | +| `plugins/dev-workflow/commands/workflow-init.md` §5 | the scaffolded mirror | full (in the cycle) | +| same file, outside the fence | the item-1 n/a note | full (in the cycle) | +| `plugins/dev-workflow/.claude-plugin/plugin.json`, `CHANGELOG.md` | packaging | full (in the cycle) | +| `README.md`, `docs/getting-started.md`, `docs/coding-workflow.md` | falsified sentences | **N/A**, committed alone | +| `docs/superpowers/specs/…-conditions.md` | old-conditions dispositions | **N/A** | + +--- + +## Task 0: The conditions artifact + +**Not a gate that needs its own output to start.** This artifact is written as part of this plan +revision and is **reviewed by the plan's own Gate-A passes alongside the plan**; its acceptance is +that cycle's clean pass. Nothing waits on a separate approval. + +**Files:** Create `docs/superpowers/specs/2026-08-28-review-loop-economics-conditions.md` + +### The nineteen passages — provenance stated + +The spec no longer carries this list: revision 15 slimmed it out and put it here. It was recovered +mechanically, not from memory: + +```bash +git show 46072fe:docs/superpowers/specs/2026-08-28-review-loop-economics-design.md \ + | grep -E '^\| [0-9]+ \| ' +``` + +That revision claimed "Eighteen" and its table holds 18 rows — **claim and content agree**, so the +recovery is sound. **Reconciled against revision 36, it is nineteen**: revision 36's §2.2 requires +the pass report to state the derived floor, the axes read and their source stories, which rewrites +the `From pass 4 onward every pass report carries three lines` paragraph — present once in each +copy, and absent from the recovered list because it went to the successor story during the split +and came back when §2.2 was added. + +| # | Passage (quoted by its bold lead-in) | +|---|---| +| 1 | `**Both gates are a LOOP with a HARD FLOOR` | +| 2 | the early-exit sentence — `below 3 is a pass with **zero** findings` | +| 3 | the incomplete-pass rule — `don't count it toward the 3-pass floor` | +| 4 | `Lenses are **different questions, not more passes.**` | +| 5 | `**The Gate-B triviality skip needs two independent conditions**` | +| 6 | `**A cycle citing several stories**` | +| 7 | `- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).**` | +| 8 | `- **Gate B — Code.**` | +| 9 | `- **Severity:** Blocker (wrong/unsafe/breaks invariant)` | +| 10 | `**Changing a profile:**` | +| 11 | the findings-slot naming paragraph (`` is `gate-a-spec-pass-

`, …) | +| 12 | `**Before each call, delete every target file and confirm it is gone.**` | +| 13 | `**Recognizing "clearly stuck"` | +| 14 | `**Recording a human exception.**` | +| 15 | `**The evidence entry lives in the commit body**` | +| 16 | `**Optional companions, from field practice.**` | +| 17 | `**On squash-merge, copy every evidence entry` | +| 18 | the `baseSha` / WIP / closing-amend block | +| 19 | `**From pass 4 onward every pass report carries three lines.**` | + +- [ ] **Step 1: Confirm all nineteen exist exactly once in both copies** + +```bash +while IFS= read -r p; do + a=$(grep -cF "$p" CLAUDE.md) + b=$(grep -cF "$p" plugins/dev-workflow/commands/workflow-init.md) + [ "$a" = 1 ] && [ "$b" = 1 ] || printf 'MISMATCH %s/%s: %s\n' "$a" "$b" "$p" +done <<'PATS' +**Both gates are a LOOP with a HARD FLOOR +below 3 is a pass with +don't count it toward the 3-pass floor +Lenses are **different questions, not more passes.** +The Gate-B triviality skip needs two independent conditions +A cycle citing several stories +Gate A — Spec, then plan (TWO runs, each its own 3-pass loop) +Gate B — Code. +**Severity:** Blocker (wrong/unsafe/breaks invariant) +Changing a profile: +gate-a-spec-pass-

+Before each call, delete every target file and confirm it is gone. +Recognizing "clearly stuck" +Recording a human exception. +The evidence entry lives in the commit body +Optional companions, from field practice. +On squash-merge, copy every evidence entry +**`baseSha`:** against main = merge-base with main +From pass 4 onward every pass report carries three lines +PATS +``` + +Expected: **no output.** Any MISMATCH means the list is stale and that is a finding, not something +to route around. + +- [ ] **Step 2: Regenerate the floor-site inventory** + +```bash +grep -nE "min 3 passes|below 3|3-pass|where the 3 come from|if pass 3 still" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md | wc -l +``` + +**Demonstrated now: 14** — seven per copy. That is the inventory, and it already includes the +digit-free `pass 1` site because the pattern `if pass 3 still` and the `carrying a Minor` sentence +both fall inside it. Confirm the digit-free one separately: + +```bash +grep -n 'carrying a Minor keeps' CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +**Demonstrated now:** `CLAUDE.md:126` and `workflow-init.md:322`. + +- [ ] **Step 3: Write one dispositioned entry per passage** + +Columns: *passage*, *what its existing prose requires* (one requirement per line, read from the +file, not recalled), *disposition* — **kept** / **moved** (say where) / **deliberately dropped** +(say why). A requirement neither kept nor explicitly dropped is a dropped condition and fails this +task. + +- [ ] **Step 4: Verify entry count against the list** + +```bash +grep -c '^| [0-9]* |' docs/superpowers/specs/2026-08-28-review-loop-economics-conditions.md +``` + +Expected: **19**. + +- [ ] **Step 5: Commit — ordinary commit, Gate B N/A** + +```bash +git add docs/superpowers/specs/2026-08-28-review-loop-economics-conditions.md +git diff --cached --name-only # confirm ONLY this path +git commit -m "docs(spec): old-conditions dispositions for the review-loop economics change" +``` + +--- + +## Task 1: Open the Gate-B cycle, and derive the floor + +**Files:** `CLAUDE.md:72`, `plugins/dev-workflow/commands/workflow-init.md:272` + +**Interfaces:** +- Produces: `max(risk, security)` inside the floor paragraph, which Task 2's sites refer back to. + +- [ ] **Step 1: Run the check and see it fail** + +```bash +for f in CLAUDE.md plugins/dev-workflow/commands/workflow-init.md; do + printf '%s: ' "$f" + sed -n '/Both gates are a LOOP with a HARD FLOOR/,/^$/p' "$f" | grep -c 'max(risk, security)' +done +``` + +**Demonstrated failing output, 2026-08-29:** +``` +CLAUDE.md: 0 +plugins/dev-workflow/commands/workflow-init.md: 0 +``` + +**The `sed` scoping is load-bearing.** An unscoped `grep -c 'max(risk, security)'` returns **1** +for both files today, because the Profiles section already contains the phrase — the check would +pass before the edit and prove nothing. That was a real finding against the first version of this +plan. + +- [ ] **Step 2: Replace the floor clause in both copies** + +Anchor, pasted from `grep -n` (identical in both files): +``` +72:**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major +272:**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major +``` + +Replace `min 3 passes per run` with the derived floor, **keeping every other requirement in that +paragraph verbatim**: + +```markdown +**Both gates are a LOOP with a HARD FLOOR: a minimum number of passes per run (Blocker/Major +only), derived from the story profile — `max(risk, security) == 0` gives **1**; every resolvable +profile above that, and an artifact citing no story, gives **3**. A cited story whose profile is +present but unresolvable **stops and surfaces** under the existing rule rather than falling +through to 3. A cycle citing several stories reaches 1 **only if the cited set is non-empty and +every member is profiled, resolvable and at level 0**; any other set gives 3. **One derived value +governs all three cycles** — the Gate-A spec cycle, the Gate-A plan cycle and the Gate-B cycle — +because they derive from the same cited-story set, not because they are one cycle. +``` + +- [ ] **Step 3: Add the pass-report and residual rules to both copies** + +Closing the coverage gap the first version left. Append to the same paragraph: + +```markdown +**Every pass report states the derived floor, the risk and security values read, and the cited +stories they were read from** — the number alone leaves a reader unable to check the derivation +while passes are still being spent. **The derived floor is the count a cycle owes; the hook's +ratio is a reminder threshold and controls nothing**, so where the cycle's own closure rules are +satisfied a below-threshold reminder is noted in the pass report and disregarded. This replaces +the pass-count number and nothing else; every other rule here about how a cycle closes stands as +written. **The floor is produced by the agent and nothing checks it** — not against the cited +profiles, not anywhere — so a stated floor the cited set does not license, an omitted higher-risk +story, a minted level-0 profile or an incomplete cited set are all routes to fewer passes, and +naming them is a disclosure rather than a guard. **The workspace knob is never written, never +removed and never read for this derivation**; it remains the hook's reminder threshold. +``` + +- [ ] **Step 4: Re-run the check** + +Expected: `1` and `1`. + +- [ ] **Step 5: Verify the paragraph's tail survived** + +```bash +for f in CLAUDE.md plugins/dev-workflow/commands/workflow-init.md; do + printf '%s: ' "$f" + tr '\n' ' ' < "$f" | tr -s ' ' | grep -c "don't manufacture findings to pad" +done +``` + +Expected: `1` and `1`. **Demonstrated present now**, so this check catches a replacement that +swallowed its neighbours. + +- [ ] **Step 6: OPEN THE CYCLE with a WIP commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit -m "WIP: review-loop economics — floor, severity, records" +``` + +**This message must begin `WIP:`.** Every later task in this cycle amends it. Do not run an +ordinary commit again until Task 9's close. + +--- + +## Task 2: The fourteen floor-wording sites + +**Interfaces:** consumes Task 1's predicate; produces nothing. + +- [ ] **Step 1: Run the check and see it fail** + +```bash +grep -cE "min 3 passes|below 3|3-pass|where the 3 come from|if pass 3 still" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +**Demonstrated output, 2026-08-29:** `CLAUDE.md:7` and `workflow-init.md:7`. After this task both +must be **0**. + +- [ ] **Step 2: Apply seven replacements per copy** + +Anchors pasted from `grep -n`; **CLAUDE.md line : template line**. + +| Anchor (verbatim, as the file holds it) | Lines | Replacement | +|---|---|---| +| `final pass must be clean — if pass 3 still finds Blocker/Major, keep going until` | 77 : 277 | `…— if the pass at the floor still finds Blocker/Major, keep going until` | +| `below 3 is a pass with **zero** findings; don't manufacture findings to pad. Codex is` | 79 : 279 | `below the floor is a pass with **zero** findings; …` | +| `the only exception, exactly as above; a Blocker/Major-free pass 1 carrying a Minor keeps` | 126 : 322 | `…; a Blocker/Major-free pass **below the floor** carrying a Minor keeps` | +| `act on the partial list, don't count it toward the 3-pass floor, and don't read "no` | 236 : 421 | `…don't count it toward the floor, and don't read "no` | +| `- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).** Run on the` | 300 : 485 | `- **Gate A — Spec, then plan (TWO runs, each its own loop at the derived floor).** Run on the` | +| ` invalidates the prior pass, which is where the 3 come from.` | 335 : 519 | ` invalidates the prior pass, which is where the floor's lower bound comes from.` | +| `Lenses are **different questions, not more passes.** The 3-pass floor, the Blocker/Major` | 403 : 582 | `Lenses are **different questions, not more passes.** The floor, the Blocker/Major` | + +**The `pass 1` sentence continues onto the next line** — `looping.` sits at 127/323. Match the +fragment above, not a reconstructed whole sentence. That mismatch broke the first version. + +- [ ] **Step 3: Re-run the check** + +Expected: `0` and `0`. + +- [ ] **Step 4: Confirm the historical citation was NOT touched** + +```bash +grep -c "PR #23's Gate-B pass 3 returned all four findings" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Expected `1` and `1`. **Demonstrated present now.** It cites an actual pass, not a rule, and must +keep saying 3 — changing it would falsify a record. + +- [ ] **Step 5: Amend the WIP commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend --no-edit +``` + +The message keeps its `WIP:` prefix. **No ordinary commit.** + +--- + +## Task 3: Severity semantics + +**Files:** `CLAUDE.md:495`, `plugins/dev-workflow/commands/workflow-init.md:674` + +- [ ] **Step 1: Run the check and see it fail** + +```bash +for f in CLAUDE.md plugins/dev-workflow/commands/workflow-init.md; do + printf '%s: ' "$f"; tr '\n' ' ' < "$f" | tr -s ' ' | grep -c 'consumes this text' +done +``` + +**Demonstrated failing output, 2026-08-29:** `0` and `0`. + +- [ ] **Step 2: Extend the Severity entry in both copies** + +Anchor, pasted from `grep -nF`: +``` +CLAUDE.md:495:- **Severity:** Blocker (wrong/unsafe/breaks invariant) · Major (design flaw → +plugins/dev-workflow/commands/workflow-init.md:674:- **Severity:** Blocker (wrong/unsafe/breaks invariant) · Major (design flaw → +``` + +Keep the four definitions verbatim; append the classifier from spec §3 — the reachability test +with both halves required, the reviewing-pass exclusion, the human-reader exclusion, the +illustrative reader list, the ceiling-not-floor rule, the symmetric instrument carve-out, the +qualified rationale case, the kinship sentence, and coverage-first. + +- [ ] **Step 3: Re-run the check** + +Expected: `1` and `1`. + +- [ ] **Step 4: Verify the four definitions survived** + +```bash +grep -cF '**Severity:** Blocker (wrong/unsafe/breaks invariant)' \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Expected `1` and `1`. + +- [ ] **Step 5: Amend the WIP commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend --no-edit +``` + +--- + +## Task 4: The two pinned records and the cycle nonce + +- [ ] **Step 1: Run the check and see it fail** + +```bash +grep -c 'cycle none (pre-rule)' CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +**Demonstrated failing output, 2026-08-29:** `0` and `0`. + +- [ ] **Step 2: Copy both grammars from the spec** + +**From spec §2.3 (provenance) and §4 (curve) only** — §5 of the spec is the nonce prose and holds +no grammar. Copy the fenced blocks **verbatim**; retyping is how a production drifts. + +- [ ] **Step 3: Add the surrounding rules** + +Every cycle records a provenance line. The curve records Findings, Blockers and Majors per valid +pass with the pass numbers covered; an unrecoverable count is `?`, excluded **per series** and not +per pass; a skipped cycle records a skip line in place of a curve. + +**The nonce**, with the generation policy the spec delegates here: 8–16 chars of `[a-z0-9]` from a +source of randomness, never derived from a name, timestamp or commit. **On failure — randomness +unavailable, an invalid value, or a collision with an open cycle — retry at most three times, then +stop and surface. No deterministic fallback.** It appears in the provenance line, the curve, the +cycle's findings slots and its advisory working record. A cycle that cannot recover exactly one +candidate starts fresh. + +- [ ] **Step 4: Widen the findings-slot grammar in both copies** + +Anchor: the paragraph containing `gate-a-spec-pass-

`. The three names gain an optional per-cycle +infix, **required whenever more than one cycle could write that slot**, with a target owned by +another cycle **refused rather than overwritten**. + +- [ ] **Step 5: Re-run the check, then parse the grammars** + +```bash +grep -c 'cycle none (pre-rule)' CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Expected `1` and `1`. + +Then **construct** instances and parse them against the productions **as they now read in the +shipped copies** — the spec carries no worked examples to reuse. Cover: a quoted path; each +`unusable()` value; a gapped `` like `1,2,4`; a split-model pass; a skipped cycle; a +`?` count; and one instance that must be **rejected** (`` shorter than ``). Record +which feature each exercised. **A grammar nothing ever parsed is a format claim, not a format.** + +- [ ] **Step 6: Amend the WIP commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend --no-edit +``` + +--- + +## Task 5: The squash carry + +**Files:** `CLAUDE.md:519`, `plugins/dev-workflow/commands/workflow-init.md:698` + +- [ ] **Step 1: Run the check and see it fail** + +```bash +for f in CLAUDE.md plugins/dev-workflow/commands/workflow-init.md; do + printf '%s: ' "$f"; tr '\n' ' ' < "$f" | tr -s ' ' | grep -c 'the per-pass curves' +done +``` + +**Demonstrated failing output, 2026-08-29:** `0` and `0`. + +- [ ] **Step 2: Extend the list — do not rewrite the sentence** + +Anchor, pasted from `grep -nF` (the whole rule is one long line): +``` +CLAUDE.md:519: **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — …** +plugins/dev-workflow/commands/workflow-init.md:698: (identical) +``` + +Add **the provenance line, the per-pass curves, and a skipped cycle's skip record**. The successor +story adds the decline record to this same passage later; a rewrite there would drop what this +adds, which is why this is an extension. + +- [ ] **Step 3: Re-run the check, and confirm the original two survived** + +```bash +for f in CLAUDE.md plugins/dev-workflow/commands/workflow-init.md; do + printf '%s: ' "$f" + tr '\n' ' ' < "$f" | tr -s ' ' | grep -c 'every evidence entry and every human-exception record' +done +``` + +Expected `1` and `1` for both checks. + +- [ ] **Step 4: Amend the WIP commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend --no-edit +``` + +--- + +## Task 6: The item-1 note + +**Runs before Task 7's conformance pass**, because that pass certifies this file and must see its +final state. The first version had them the other way round. + +**Files:** `plugins/dev-workflow/commands/workflow-init.md`, **outside** the fenced block. + +- [ ] **Step 1: Record the count that must not change** + +```bash +grep -c '^Target model:' plugins/dev-workflow/commands/workflow-init.md +``` + +**Demonstrated now: 1.** It must still be 1 afterwards — `scripts/check-invariants.sh` fails on +any other count, and adding a second declaration is exactly what a naive fix would do. + +- [ ] **Step 2: Add the note immediately before the fence** + +The fence opens at `192:````markdown`. Place the note **before** that line: + +```markdown +> **Prompt-standards item 1 for the scaffolded `CLAUDE.md`: n/a, and why.** The file this template +> writes is model-agnostic by design — its executing model is whatever the reader of that project +> runs — so a `Target model:` line inside it would be false in every repo it lands in. Recorded as +> a reasoned n/a rather than skipped: the item is answered. This note sits **outside the fence** so +> it never scaffolds, and is deliberately **not** a `Target model:` line, which would make this +> file's declaration count 2 and fail `scripts/check-invariants.sh`. +``` + +- [ ] **Step 3: Verify the count and the placement** + +```bash +grep -c '^Target model:' plugins/dev-workflow/commands/workflow-init.md +awk 'NR>=185 && NR<=200' plugins/dev-workflow/commands/workflow-init.md +sh scripts/check-invariants.sh && echo INVARIANTS-OK +``` + +Expected: `1`; the note visible **before** the ```` ````markdown ```` line; checker exits 0. + +- [ ] **Step 4: Amend the WIP commit** + +```bash +git add plugins/dev-workflow/commands/workflow-init.md +git commit --amend --no-edit +``` + +--- + +## Task 7: Parity and the twelve-item conformance pass + +No new text. This is the verification story criterion 7 and invariant 11 require. + +- [ ] **Step 1: Walk every changed rule across both copies** + +For each rule Tasks 1–5 added, extract it from both files and diff. Record each difference as +**deliberate-and-stated** or **defect**. The copies already diverge on ~192 lines overall, so a +whole-section diff proves nothing — walk the named rules. + +- [ ] **Step 2: Run the twelve items against each changed prompt artifact** + +The resulting scaffolded template, and `plugins/dev-workflow/commands/workflow-init.md` as the +outer command prompt. **Item 7 is read against the whole resulting artifact**, not the diff. +**Root `CLAUDE.md` is outside invariant 11's list and is not part of this pass.** + +- [ ] **Step 3: Record item 1's n/a for the scaffolded template**, with the reason from Task 6. + +- [ ] **Step 4: Append the results to the conditions artifact and commit — Gate B N/A** + +```bash +git add docs/superpowers/specs/2026-08-28-review-loop-economics-conditions.md +git diff --cached --name-only # confirm ONLY this path +git commit -m "docs(spec): parity walk and twelve-item conformance results" +``` + +**This is an ordinary commit and it is correct here** — the staged path is `docs/superpowers/**.md` +and the WIP cycle's counters are unaffected by a commit that touches none of its files. Amend the +WIP commit again in Task 8 before reviewing. + +--- + +## Task 8: The falsified user-facing sentences — committed alone + +- [ ] **Step 1: Run the check and see it fail** + +```bash +grep -cE "3-passes-per-gate|three passes minimum|same 3-pass loop|Gate-A floor wasn't met|three passes, final clean|3/3 cycle|floor is unchanged at every level|moves the 3-pass floor|Gate A's floor and" \ + README.md docs/getting-started.md docs/coding-workflow.md +``` + +**Demonstrated output, 2026-08-29:** `README.md:1`, `docs/coding-workflow.md:1`, +`docs/getting-started.md:7` — **nine hits, matching the nine sites below.** The first version's +grep found only five and its site list said nine. + +- [ ] **Step 2: Correct each site** + +Anchors pasted from `grep -nF`: + +| Anchor | Correction | +|---|---| +| `README.md:130:` `\| `codex-gate.floor` \| a positive integer; moves the 3-passes-per-gate floor. \|` | `… moves the **hook's reminder threshold**. It does not change the floor §5 obliges, which is derived from the story profile.` | +| `getting-started.md:34:three passes minimum, final pass clean — the one early exit is a pass that comes` | `the floor its profile derives, final pass clean — the one early exit is a pass that comes` | +| `:40:task-by-task plan (each task starts with a failing test); the same 3-pass loop runs` | `…; the same loop runs at the derived floor` | +| `:44:progress claims backed by test runs. If the Gate-A floor wasn't met, the hook says` | `… If the hook's own threshold wasn't met, it says` | +| `:53:` `` `mcp__codex__review` the same way: three passes, final clean. Verification is by`` | `… the same way: the derived floor, final clean. Verification is by` | +| `:58:` `` `✓ Codex Gate B satisfied (3/3 cycle, 3 on current fingerprint)`, the real commit replaces`` | `` `✓ Codex Gate B satisfied (N/N cycle, N on current fingerprint)` — N being the derived floor — the real commit replaces `` | +| `:84:is still owed and Gate A's floor is unchanged at every level. The caution bias is` | `is still owed; Gate A's floor is derived from the profile like Gate B's, and what the axes never subtract is the baseline questions. The caution bias is` | +| `:86:positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off`` | `positive integer) moves the hook's reminder threshold, and …` | +| `coding-workflow.md:79:gates for a risky or security-relevant change (they never subtract any: Gate A's floor and` | `gates for a risky or security-relevant change (they never subtract a baseline question; the floor itself is derived from the profile, and` | + +- [ ] **Step 3: Re-run the check** + +Expected: zero hits in all three files. + +- [ ] **Step 4: Commit alone — Gate B N/A** + +```bash +git add README.md docs/getting-started.md docs/coding-workflow.md +git diff --cached --name-only # MUST list exactly these three +git commit -m "docs: correct every sentence the derived floor falsifies" +``` + +**Verify the staged set before committing.** That verification is what earns the exemption; a +mixed commit forfeits it. + +--- + +## Task 9: Packaging, then close the cycle + +- [ ] **Step 1: Bump the manifest** + +`plugins/dev-workflow/.claude-plugin/plugin.json`: `0.10.0` → `0.11.0`. Minor — shipped rules +change, no documented interface breaks. + +- [ ] **Step 2: Add the CHANGELOG entry**, newest first. **Nothing enforces this** — neither + invariant 12 nor the checker mentions the changelog — so the story's criterion carries it. + +- [ ] **Step 3: Amend the WIP commit, THEN run the version check** + +```bash +git add plugins/dev-workflow/.claude-plugin/plugin.json plugins/dev-workflow/CHANGELOG.md +git commit --amend --no-edit +sh scripts/check-version-bump.sh main && echo BUMP-OK +``` + +**Order matters and this is why:** `check-version-bump.sh` compares *committed* state against the +merge-base and ignores the worktree and index. Run before the amend it reports clean — correctly +and uselessly, as `AGENTS.md` says in as many words. The first version of this plan ran it first +and called that a failing check. + +- [ ] **Step 4: Run the full battery** + +The whole `AGENTS.md` § Commands chain, exit 0, with assertion counts recorded. + +- [ ] **Step 5: Gate B — the review loop** + +Against the WIP commit, `baseSha` = its parent. Floor 3 (this story is risk `high`). Re-review +after every fix; the final pass must be clean. Findings to +`.context/codex-reviews/gate-b--rle-pass-

.md`, targets deleted and confirmed gone +before each call. + +- [ ] **Step 6: Close the cycle** + +```bash +git commit --amend -m "$(cat <<'MSG' +feat(gates): derive the pass floor from the profile; decide severity by consequence + + +MSG +)" +``` + +**This is the first message without `WIP:`**, and the hook reads it as the cycle closing. + +--- + +## Task 10: The evidence pack + +Mode `battery+check+verification`. Produced during Task 9's cycle and **revalidated before every +re-review and before the closing amend**, against the content the close will carry. + +- [ ] **Step 1: The battery** — the § Commands chain, green, counts recorded. + +- [ ] **Step 2: The differential named verification — both revisions read** + +One question about behaviour at a derived floor of 1, answered against **both** revisions: + +> *At floor 1, does a Blocker/Major-free pass 1 carrying a Minor close, or keep looping?* + +- **Pre-change** (`git show :CLAUDE.md`, line 126): says it **keeps looping** — wrong, since + pass 1 is at the floor. +- **Post-change**: says a pass **below the floor** keeps looping — correct. + +Ask the same of `below 3` versus `below the floor` at line 79. **Record which revision was read for +each answer.** A verification consulting only the post-change text cannot fail and would report +success because of how it was wired. + +- [ ] **Step 3: The risk-path verification** + +Recompute each provenance line's floor from the cited stories' profile headers. The observation +that would exist if the claim were false is **a provenance line whose number the profiles do not +license**. Also confirm **no floor file is present at close where none was at start** — which +detects a persisting write and **cannot** detect a transient one. + +- [ ] **Step 4: The knob verification, conditionally** + +If `.context/codex-gate.floor` exists before the cycle, it is byte-identical after. **If none +exists, record not-applicable with that reason.** Do not create one — a fixture supplying its own +input proves nothing. + +- [ ] **Step 5: The three closing bodies** + +Each cycle's closing commit carries a provenance line and a curve in the pinned forms. **All three +use `cycle none (pre-rule)`** — every cycle on this branch began before these rules ship, so none +has a nonce and minting one would be late-created provenance. The branch demonstrates **every field +of both forms except two**: the cycle identifier, and the knob clause's non-absent form. Both are +recorded as **undemonstrable-here with their reasons**, not as gaps and not as satisfied. The +implementation commit records that **any cycle that both starts under these rules and closes** +discharges the nonce demonstration; duplicate discharge is harmless. + +--- + +## Self-Review + +**Spec coverage.** §2 predicate → Task 1. §2.1 knob, precedence, residual → Task 1 Step 3. §2.2 +pass report → Task 1 Step 3. §2.3 provenance → Task 4. §2.4 mid-cycle → Task 1. §3 severity → +Task 3. §4 curve → Task 4. §5 nonce incl. retry policy → Task 4 Step 3. §6 accounting → Task 0. +§7 rollout, falsified sentences, packaging → Tasks 6, 8, 9. §8 evidence → Task 10. §9 scope → +Global Constraints. §10 risks → shipped by Task 1 Step 3. + +**Placeholders.** None. Every anchor is pasted `grep -n` output; every replacement is written out; +every check has its demonstrated output recorded. + +**Type consistency.** ``, ``, ``, ``, ``, ``, +`` are defined in the spec, which travels with this plan, and are used identically in +Tasks 4 and 10. + +**Gate-B classification, per path.** Tasks 1–6 and 9 stage prompts or plugin files → **one full +Gate-B cycle**, opened at Task 1 and closed at Task 9. Tasks 0, 7 and 8 stage only `docs/**.md` or +`README.md` → **N/A**, committed alone. The first version claimed Tasks 0 and 6 fired full Gate B; +they do not. + +**Known limit.** Task 2's replacement wordings are proposals, not transcriptions — the spec pins +the rules, not the sentences — and this plan's Gate A is what reviews them. diff --git a/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-b-records.md b/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-b-records.md new file mode 100644 index 0000000..030f014 --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-b-records.md @@ -0,0 +1,686 @@ +# Plan B — the records (provenance line, per-pass curve, cycle nonce, slot naming) + +> **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 two pinned commit-body records — the provenance line and the per-pass curve — +together with the cycle nonce that attributes them and the slot naming that keeps cycles holding +**distinct** nonces from overwriting each other. Two cycles that drew the same nonce are +indistinguishable to the naming, which is why the nonce is collision-resistant rather than +collision-proof and why the shipped text says so at each claim. + +**Spec:** `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` (revision 36). +Plan B implements §2.3, §4, §5, and §6's method applied to its own passages, plus the §10 +extension Plan A's wording was written to permit. + +**Story:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` + +> **Read the profile from that header at execution time.** This plan states no risk value, no +> security value, no validation mode and no pass count derived from any of them. + +--- + +## Plan B of three + +| Plan | Ships | Spec sections | +|---|---|---| +| A | the floor predicate and the severity test | §2, §2.1, §2.2, §2.4, §3, §10 (partial) | +| **B — this one** | the provenance line, the per-pass curve, the cycle nonce, slot naming | §2.3, §4, §5, §6 | +| C | rollout: falsified sentences, packaging, the evidence pack, the review loop | §7, §8 | + +**Three Gate-A cycles, one Gate-B cycle** over the combined A+B+C diff, run and closed by Plan C. +**Plan B may not open before Plan A's Gate-A loop closes** — it did, clean at pass 12. + +**Plan B edits the POST-PLAN-A tree.** Every `grep -n` anchor below was taken against the files +as Plan A leaves them, not against the untouched tree, and Task 6 edits a sentence Plan A +creates. Executing Plan B against an un-edited tree will fail at Task 6, correctly. + +--- + +## Global Constraints + +- **This Gate-B cycle runs under the rules in force at its start — the OLD ones.** The record + forms this plan ships bind only **after** the closing commit ships them. **Carry this sentence + in the `additionalContext` of every Gate-B call.** +- **Prompt-only.** No file under `plugins/dev-workflow/hooks/` changes, and the floor knob + `.context/codex-gate.floor` is never written, never removed, never read. +- **The §5 heading must keep matching `^#{1,6}[[:space:]]+([0-9]+\.)?[[:space:]]*Cross-Model Review`.** +- **Every edit lands in both copies.** All five passages Plan B touches were verified identical + across the two copies over their full extent, so no row below is split. +- **The two pinned grammars are copied from the spec verbatim, never paraphrased.** The spec pins + them because a program parses them; a paraphrase would be a second form, which is the one thing + a pinned form cannot survive. +- **§5's other closure rules are never restated, only referred to.** +- **Line numbers are provenance, never instructions.** +- **One cycle field per cycle RUN, not per cycle kind — a confirmed reading, not a new rule.** + The story's criterion says "one per cycle, so a run of all three holds three". **"One per + cycle" is the rule**; the clause after "so" is a worked example written before this change was + split into three plans, and it does not outrank the rule it illustrates. This change runs + **five** cycles — one Gate-A spec, three Gate-A plan, one Gate B — and therefore holds five + fields, five provenance lines and five curves. The five-cycle shape is a consequence of two + decisions already taken: the plan split, and the single Gate-B cycle. +- **For Tasks 2, 3 and 4 the assert-new check is also the preflight, and the OLD text is not.** + Each of those tasks re-emits its OLD text verbatim inside its NEW text, so **the OLD still + matches after the task has run** and tells you nothing about whether it did. Read the assert's + two counts as a closed state space, and stop on anything that is not one of the first two: + + | counts | state | action | + |---|---|---| + | `0` and `0` | not started | apply the replacement to both copies | + | `1` and `1` | done | skip the task | + | `1` and `0`, or `0` and `1` | **interrupted between the copies** | **STOP and surface. Do not replace either copy** — applying to both would duplicate the block in the copy that already has it while repairing the other | + | any count above `1` | **already duplicated** | **STOP and surface.** | + + **The two stop rows have one exit, and it is a human's**: restore both copies by hand to a + state the first two rows recognise — either both without the block, or both carrying exactly + one — and retry the task from there. **Removing a surplus copy is not by itself an exit**: a + `2/0` that becomes `1/0` has moved from one stop row to the other, which is why the exit is + defined as reaching `0/0` or `1/1` rather than as an edit. +- **The two pinned grammars are inserted from the spec's own text, not retyped.** The escape + specification inside the provenance grammar (`\"` and `\\`) is itself made of backslashes, + and a transcription step ate them once — the grammar that pins the escape rules had its own + escapes collapsed. Copy the block; do not re-key it. + +### The commit protocol + +**Plan B amends the WIP commit Plan A opened**, with the same message, and uses the base SHA +Task 1 of Plan A printed. Plan C adds the manifest bump, runs the single Gate-B loop, and closes. + +> **Never `git commit --amend --no-edit` inside the cycle.** +> `plugins/dev-workflow/hooks/codex-gate.sh:763` recognizes a WIP commit by grepping the **Bash +> command string** for `-m ... wip`; an amend without `-m` is not recognized, and the hook resets, +> discarding the cycle's passes. + +--- + +## Old-conditions accounting + +Per spec §6's method, against the post-Plan-A text. **Three passages are rewritten and get rows.** +Three further sites are **insertion points whose existing text is re-emitted unchanged** — they +rewrite nothing, and are listed so a reader can confirm that rather than assume it. + +| # | Passage | What the existing prose requires | Disposition | +|---|---|---|---| +| 1 | the findings-slot grammar | a. write the full list to `.context/codex-reviews/.md` · b. create the directory if needed · c. the path is relative to the reviewed repo root, because Codex resolves writes against its working directory · d. `` is one of three named forms — four concrete names, since `gate-b--pass-

` expands to two | a–c **kept verbatim** · d **kept and extended** (Task 1): the three bare names remain, reserved for the legacy single-cycle case, and a cycle holding a nonce uses the infixed form in every slot more than one cycle could write. A refusal rule is added, which is new and not a change to d | +| 2 | the squash-merge carry | a. copy every evidence entry in the squash range · b. copy every human-exception record · c. into the squash body · d. because the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable | **a–d all kept verbatim**; three members added — the provenance lines, the curves, and a skipped cycle's skip record (Task 5). Extended rather than rewritten, because the successor story adds a decline record to this same sentence later | +| 3 | the activation strict-fallback list *(text Plan A creates; this is the second accounting that passage owes, per §6)* | a. from the shipping commit · b. a running cycle finishes under its starting rules · c. where the start cannot be established, take the stricter reading of every part · d. at minimum floor 3 and severity without the demotion · e. each further rule this change ships adds its own strict reading · f. not a re-derivation · g. a user knob above 3 is not lowered · h. a revert is itself a shipping commit | **a–h all kept verbatim**; the list gains the provenance-line duty, the curve duty and the nonce duties at their strictest (Task 6). This is the append that **e** exists to license | + +**Insertion points, rewriting nothing:** the optional-companions block (Task 2 appends after it), +the closing-message paragraph (Task 3 inserts before the squash sentence), and the human-exception +block (Task 4 inserts before it). Each task re-emits the existing text verbatim as part of its +replacement, which is what makes the insert auditable as an insert. + +--- +## Task 1: The findings-slot grammar gains a per-cycle infix + +**Spec:** §5 slot rules + +**Site** — pasted `grep -n` **against the post-Plan-A tree**, which is what Plan B edits: + +``` +CLAUDE.md:292:> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

`. +plugins/dev-workflow/commands/workflow-init.md:479:> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

`. +``` + +> **This is the rule the cycle that wrote it paid for.** A bare slot was overwritten during this very change, destroying a previous cycle's findings file — an `ls` and an `rm` in one command, so the evidence that the slot was occupied arrived after it was gone. The bare names stay valid for the legacy single-cycle case; anything more than one cycle could write takes the nonce. + +- [ ] **Replace, in both copies.** OLD: + +``` +> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

`. +``` + +NEW: + +``` +> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

` for a +> cycle with no nonce; a cycle that has one writes `gate-a-spec--pass-

`, +> `gate-a-plan--pass-

` or `gate-b---pass-

` instead, and uses +> the nonce in every slot more than one cycle could write. The bare names are reserved for the +> legacy single-cycle case they already serve. **Distinct-nonce paths coexist by construction and +> are never in conflict** — a sibling cycle's slot is simply a different file. +> +> **The rule binds the deletion step, which is where the damage is done.** This section already +> requires every target to be deleted and confirmed gone before a call. A cycle holding a nonce +> **deletes only paths carrying its own nonce**; it never deletes a bare path or one carrying a +> different nonce, and an attempt to do either **stops and names the path** instead of removing +> it. That is reachable and observable: the step operates on a path it computed, and the check is +> whether that path is the cycle's own. **The case it exists for is a nonce-holding cycle +> computing a bare path** — the legacy spelling — **and deleting a file that belongs to somebody +> else**, which is exactly what happened once. **Two cycles that drew the same nonce compute the +> same paths and are indistinguishable to this rule**; what makes that unlikely is the width of +> the draw, not this rule — this section already stops on a +> target that survives deletion, and this extends that to a target that must not be deleted at +> all. That rule exists because a bare slot was in fact overwritten once, destroying a previous +> cycle's findings file. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "The rule binds the deletion step, which is where" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated tree carrying Plan A's edits — the pattern lies on one line, contains no `**`, and is passed after `--`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 2: The cycle nonce + +**Spec:** §5 + +**Site** — pasted `grep -n` **against the post-Plan-A tree**, which is what Plan B edits: + +``` +CLAUDE.md:335: Whoever runs the cycle writes it when useful, replaces it as the cycle moves, and +plugins/dev-workflow/commands/workflow-init.md:522: Whoever runs the cycle writes it when useful, replaces it as the cycle moves, and +``` + +> Inserted after the optional-companions block, whose text is re-emitted unchanged — this task rewrites nothing, it appends. The block is the right neighbour because the working record it describes is itself one of the records the nonce keys. +> +> The bounded retry policy is this plan's to fix and it is fixed here: **at most three attempts in total** — not three retries after a first try — **then stop and surface, naming which of the three causes occurred.** The spec delegates the number; what it requires is that the causes stay distinguishable, since randomness-unavailable, invalid-value and collision need different fixes. +> +> **Two residuals are disclosed in the shipped text rather than guarded**, because neither can be closed by prompt rules: the uniqueness check compares against cycles *known to be open*, and two cycles starting simultaneously can each check before either publishes. What makes both unlikely is the width of the draw, not the check. +> +> **This task inserts; it rewrites nothing.** The companions block is re-emitted verbatim, so its OLD text still matches afterwards — the assert-new check is this task's preflight, not the OLD. + +- [ ] **Replace, in both copies.** OLD: + +``` + Whoever runs the cycle writes it when useful, replaces it as the cycle moves, and + deletes it once the cycle closes. Nothing depends on it existing. +``` + +NEW: + +``` + Whoever runs the cycle writes it when useful, replaces it as the cycle moves, and + deletes it once the cycle closes. Nothing depends on it existing. + +**The cycle nonce.** Both shipped records below carry a **cycle field**, because a record that +cannot be attributed to a cycle cannot be told apart from another cycle's when several are read +together. That is a limitation rather than a disqualification — a human reading one cycle's +records knows which cycle they came from; what attribution buys is that a *later* reader +**usually** does not have to. Usually, not always: the guarantee is probabilistic, for the two +reasons stated at the end of this block. This section defines three **kinds** of cycle — the Gate-A spec loop, the Gate-A +plan loop and the Gate-B cycle — and **one cycle field is produced per cycle run, not per +kind**: a change carrying several plans runs a Gate-A plan cycle for each, and each of those is +its own cycle with its own nonce. + +Generated once at cycle start, immutable, and collision-resistant operationally: **8 to 16 +characters drawn uniformly from `[a-z0-9]`, from a source of randomness** — 8 being where +collision resistance starts and 16 where the field stops being a usable infix. **Never derived +from a name, a timestamp or a commit**, each of which collides exactly where sibling cycles do, +which is the one thing the nonce exists to prevent. The character set keeps it safe as a slot +infix and a path component. + +**It appears in every record the cycle writes** — which keeps records apart **as far as distinct +nonces allow**, and no further — **and that set is named rather than left open**: +the provenance line, the per-pass curve (including a skip record standing in for one), the +cycle's findings slots, and its advisory working record. **The working record is a cycle record +too**: a cycle holding a nonce names it `gate-a-spec--resume.md`, +`gate-a-plan--resume.md` or `gate-b--resume.md`, and the bare names above stay +reserved for the legacy single-cycle case, exactly as the findings slots do. **Because recovery +scopes candidates by artifact as well as by kind, the record's contents name that artifact**, and +what counts as the artifact depends on the cycle kind: for a Gate-A cycle it is the reviewed +document's path, quoted by the same rule the provenance line uses where quoting is needed; for a +Gate-B cycle, which reviews a diff rather than a file, it is the **base commit's full +40-character hex object name**, the same value the cycle's reviews are run against. The filename +carries kind and nonce; the artifact key lives inside, where neither a path nor a hex name has to +survive a filename. The nonce is not +required in records this change neither introduces nor keys to a cycle — the evidence entry and +a human-exception record among them. + +**A nonce is a candidate for recovery only if** it is keyed to this cycle's kind — Gate-A spec, +Gate-A plan, or Gate B — **and** this cycle's artifact, **and** that cycle is still open. **Those +three are necessary and not sufficient, and the difference matters**: two Gate-A cycles can review +the same document and two Gate-B cycles commonly share a base commit, so a sole match on kind and +artifact is **not** identity. **A candidate is adopted only if it is positively linked to this +run** — the working record this run itself wrote. A match that is merely consistent is treated as +no identity, and the cycle starts fresh; adopting a sibling on a shared key would merge two +cycles under one nonce, which is the failure this rule exists to prevent. +History normally holds many closed cycles' nonces and they are not candidates; a working record +left by a closed cycle is not one either, which is why that record is **retired at closure** +rather than left to be found later. **Recovery has two sources, and they answer different questions.** The **working record** is the +source while the cycle runs, and it is the one the candidate rules above apply to — several files +may be present and the run must decide which, if any, is its own. **History is the source once +the cycle's own commit exists**, and there is no search there: the cycle is reading **its own +commit body**, so kind and artifact are settled by which commit is being read, and the nonce is +taken from the provenance line and the curve, which must agree. A Gate-A cycle mid-run has no +such commit and therefore has only the working record. Recovering a single candidate from +**either** keeps identity **as far as the field can distinguish cycles** — two cycles sharing a +nonce are one cycle to it. **No candidate, +disagreeing sources, or more than one candidate → no identity: start a new cycle**, which costs +passes rather than letting one cycle's records read as another's — again, as far as distinct +nonces allow. **Starting a new cycle does +not close, adopt or retire the cycles those candidates belong to** — they stay open, keep their +own nonces, and are a human's to resolve; the new cycle simply does not claim them. + +**A cycle does not start without a valid nonce, unique among the cycles open when it was +generated.** That is the requirement. **What the check can establish is narrower** — it compares +against the cycles it can observe — and the gap between the two is the residual set out below. +Where generation fails, make **at most three attempts in total**, then stop and +surface, **naming which of the three causes occurred**; each has its own check and its own fix, +and one token would name a symptom rather than a cause: + +The three are distinguished by **where** the attempt stopped, so they cannot both apply: the +source failed to produce bytes; or it produced bytes that are not a well-formed nonce; or it +produced a well-formed nonce that is already in use. An empty result is the first, never the +second. + +- **randomness unavailable** — the source errors or produces no bytes. *Fix:* retry, since the + condition can be transient; if it persists across the attempts, make a source available or run + where one is, which is a change to the environment rather than another draw. +- **an invalid value** — the drawn value is not 8 to 16 characters from `[a-z0-9]`. *Fix:* + redraw. Repeated invalid output points at the generator rather than at luck, and the report + says which. +- **a collision with a known-open cycle** — the value equals a nonce on a cycle still open. + *Fix:* redraw. A second collision at this width is possible but unlikely enough to be worth + reporting as a possible source defect, which the report states as a suspicion rather than a + finding. + +**Report every distinct cause observed across the attempts, in the order they occurred** — the +attempts can fail for different reasons, and naming only the last would describe the tail of the +sequence rather than what happened. + +**No deterministic fallback.** + +**Residuals, disclosed rather than guarded, and this list is not exhaustive.** The check compares +against cycles *known to be open*, so a nonce can repeat one belonging to a cycle nobody can see; +two cycles starting at the same moment can each check before either has published, so neither +observes the other; and the check deliberately ignores **closed** cycles, so a new cycle can +redraw a closed one's value and then write to its surviving findings slots and working record. +**What makes both unlikely is the width of the draw, not the check** — and unlikely is the +honest word. Neither is a guard. + +**What follows from that, said here rather than left to be discovered.** The nonce is +collision-**resistant**, not collision-**proof**, so everything built on it inherits that bound: +two cycles sharing a nonce write to the same slots and are not refused, their records read as +one cycle's, and a later reader cannot separate them. Attribution is therefore a strong default +rather than a guarantee, and any reading of these records that would be wrong if two cycles +shared a field should say so rather than assume they did not. + +**A cycle that began before these rules shipped has no nonce and cannot acquire one.** Its +records carry the reserved `cycle none (pre-rule)` field and are, by construction, not +cycle-attributable. That exception is bounded and self-terminating: it reaches only cycles +already running when the rules land, and no later cycle can enter the state. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "Generated once at cycle start, immutable" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated tree carrying Plan A's edits — the pattern lies on one line, contains no `**`, and is passed after `--`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 3: The provenance line + +**Spec:** §2.3 + +**Site** — pasted `grep -n` **against the post-Plan-A tree**, which is what Plan B edits: + +``` +CLAUDE.md:687: **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +plugins/dev-workflow/commands/workflow-init.md:864: **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +``` + +> Inserted before the squash-merge sentence, which is re-emitted unchanged here and then edited by Task 5 — so the OLD still matches after this task, and the assert-new check is its preflight. +> +> **The grammar is the spec's block, copied — two spaces of indentation added so it stays inside its bullet, and nothing else changed.** Verified by de-indenting the shipped block and comparing it byte-for-byte with spec §2.3. That check earns its keep: an earlier attempt embedded the block through a transcription step that interpreted `\\"` as `"` and `\\\\` as `\\`, silently rewriting the very production that specifies which escapes are legal. + +- [ ] **Replace, in both copies.** OLD: + +``` + **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +``` + +NEW: + +``` + **Every cycle records one provenance line in its closing commit body** — default floor or + not, so an absent line is never ambiguous between "the default applied" and "someone forgot". + **One line per cycle**, so a change running five cycles records five. There is no informal + variant; anything quoting this form elsewhere quotes an instance of it, because the deferred + metrics work parses it. + + ; floor per ; hook reminder threshold + + := "cycle " | "cycle none (pre-rule)" + := [a-z0-9]{8,16} + := [1-9][0-9]* + := "none" | "{" ("," )* "}" + each appears at most once; a repeated path, + with or without conflicting levels, is malformed + := " (level " ("0"|"1"|"2") ")" | " (unprofiled)" + := | + := [A-Za-z0-9._/-]+ contains no delimiter, quote or whitespace + := a double-quoted string, non-empty, whose only escapes are \" and \\ ; + a path containing a newline or other control + character is NOT representable — the cycle stops + and surfaces rather than emitting one + := "absent" | [1-9][0-9]* | "unusable" + (narrowed 2026-09-02: the cause token this plan + originally shipped was withdrawn — see the spec's + §2.3 note and the field report) + + It carries that cycle's **cycle field** — the nonce for any cycle started after these rules + ship, `none (pre-rule)` only for one that began before them — the **derived floor**, and **the + cited set that produced it**, each member with its level as a numeral. One floor and one set, + not an entry per story, since unanimity makes the floor a property of the set. It + distinguishes **a cited story with no profile** from **no story cited**. It records the + **workspace knob whenever the file exists**, including when present but unusable, **naming the + cause**, because those need different fixes. + + **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "floor per ; hook reminder threshold " \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated tree carrying Plan A's edits — the pattern lies on one line, contains no `**`, and is passed after `--`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 4: The per-pass curve + +**Spec:** §4 + +**Site** — pasted `grep -n` **against the post-Plan-A tree**, which is what Plan B edits: + +``` +CLAUDE.md:689: **Recording a human exception.** Where a human decides that something **no applicable rule +plugins/dev-workflow/commands/workflow-init.md:866: **Recording a human exception.** Where a human decides that something **no applicable rule +``` + +> Inserted before the human-exception block, which is re-emitted unchanged — so again the OLD still matches afterwards and the assert-new check is the preflight. The grammar is spec §4's block with two spaces of indentation added and nothing else, verified the same way. +> +> Note what the shipped text says about its own worth, because it is the part most likely to be dropped as hedging: the curve is durable **across** cycles and not **within** one, and it is **author-written and unchecked** — nothing compares it against the validated pass files. Whatever later reads it reads a self-report. + +- [ ] **Replace, in both copies.** OLD: + +``` + **Recording a human exception.** Where a human decides that something **no applicable rule + required** was nonetheless worth skipping +``` + +NEW: + +``` + **Every cycle records its own per-pass curve in its own commit body.** Gate B alone would + leave the dominant cost unrecorded — the loops this rule was built from are Gate-A loops. + + ; (passes , ): Findings . Blockers . Majors . + + := "Gate-A spec" | "Gate-A plan" | "Gate B" + := ("," )* strictly ascending, non-overlapping + :=

|

"-"

+

:= [1-9][0-9]* + := ("," )* exactly as many entries as enumerates + := 0 | [1-9][0-9]* | "?" "?" = the count is unrecoverable for that pass + := | ("; " )* + := "pass "

" " ("+" )* + := | | "undetermined" + "undetermined" means the model could not be determined; + a real model so named is written as + := [!-~]{1,} minus ; : , ( ) + " and space, and not the + literal "undetermined", which is reserved + printable ASCII only; a control character makes the + identifier unrepresentable, handled below + := a non-empty double-quoted string, same two escapes as ; + a reported identifier containing a control character is + written `undetermined` — and the raw value is NOT + reproduced anywhere in the body, since a commit message + cannot safely carry one (NUL cannot appear at all). + (the source-and-rejected-bytes obligation this line + carried was withdrawn 2026-09-02 with the cause + vocabulary; nothing replaces it) + + A skipped cycle writes `; : skipped (see skip reason)` and no counts. + **`` keys must be exactly the passes `` expands to, each once, ascending** — a + list that omits or repeats a pass is malformed, not partially informative — and **every model + contributing to a split logical pass is listed**, joined by `+`, since recording one of two is + the same loss as recording none. + + **Majors are recorded as well as Findings and Blockers**, because the severity rule moves the + Blocker/Major line rather than the total, so totals and Blockers alone could not show even a + change in the mix. **Subject categories are deliberately not recorded** — they are a judgement + per finding rather than a count, and the findings files carry the material. + + **One entry per valid pass**, and since incomplete passes are excluded while still consuming + pass numbers, the record **states which pass numbers it covers**. A valid zero-finding pass is + recorded as zero, never omitted. **A count that cannot be recovered is written `?`, never + guessed and never written as `0`** — a cycle keeps its identity through the nonce rather than + through its pass files, as far as distinct nonces allow, so a resumed cycle may know a pass happened and not what it found, and + zero and unknown are different facts. **`?` is per series**: a pass whose Findings are unknown + may still have usable Blocker and Major counts, and a reader excludes the unknown value from + the comparisons that read that series while keeping the pass's other series. + + A `full` Gate-B pass, separate `spec`/`quality` calls, and a single-branch recovery are + **branches of one logical pass** contributing one summed entry — **the curve counts logical + passes; the hook counts calls**, and where they differ the body says so. **Both branches must be + issued against the same tracked reviewed commit** — which is not a claim that either reviewed + it. Resolve `HEAD` to its full object name **before** each call and pass it explicitly; the + reviewer reports no reviewed head, so there is nothing to take from the result. Keep the value + you passed **with that branch's result** rather than re-reading it + later — an intervening `WIP:` amend moves `HEAD`, so a value read afterwards is a different + commit — and require the two captured values to be **exactly equal** before the branches are + summed. Record it as the **full 40-character hex object name**, since abbreviations are + ambiguous across repositories and across time; if it changed between them they are not one + pass, the completed branch is recorded as incomplete and excluded, and the later branch begins + a new one. Ending the pass is the conservative direction; merging two revisions would produce + one entry describing two different artifacts. + + **What the curve is worth, stated rather than implied.** Durable **across** cycles; **not + within** a running one, since the commit does not exist until the cycle closes. And + **author-written and unchecked** — nothing compares it against the validated pass files, so + whatever reads it reads a self-reported curve and must not present it as measurement. + + **Recording a human exception.** Where a human decides that something **no applicable rule + required** was nonetheless worth skipping +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "Findings . Blockers . Majors ." \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated tree carrying Plan A's edits — the pattern lies on one line, contains no `**`, and is passed after `--`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 5: The squash carry + +**Spec:** §4 squash carry + +**Site** — pasted `grep -n` **against the post-Plan-A tree**, which is what Plan B edits: + +``` +CLAUDE.md:687: **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +plugins/dev-workflow/commands/workflow-init.md:864: **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +``` + +> **Extends the list; does not rewrite the sentence.** The successor story adds a decline record to this same passage later, and a rewrite here would drop what that adds — or be dropped by it. Both existing members and the reason clause are preserved verbatim. + +- [ ] **Replace, in both copies.** OLD: + +``` + **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +``` + +NEW: + +``` + **These records are one contract, and a partial adoption breaks it.** The nonce, the slot + naming, the provenance line, the curve, this carry rule **and the unknown-start activation + semantics that say what a cycle owes when its starting rules cannot be established** depend on + one another, and the requirement is that the adopted definitions **agree**, not merely that all + of them are present: a curve + without a cycle field cannot be attributed, a slot rule without a nonce has nothing to key on, + and a carry rule naming records a project does not produce is inert. **A project whose text + carries some of them and not others, or carries all of them in versions that disagree, stops + and has a human complete, revert or reconcile the adoption before running a gate under it** — + disagreement is the harder case and gets the same stop, because a project holding two + definitions of a record has no single answer to what it owes — the same answer, and for the same reason, as a partial + adoption of the floor rule. + + **On squash-merge, copy every evidence entry, every human-exception record, the provenance lines, the curves and any skipped cycle's skip record TOGETHER WITH THE SKIP REASON IT POINTS AT in the squash range into the squash body — a skip record carried without its reason is a pointer into a body the squash has made unreachable — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "the provenance lines, the curves and any skipped cycle's skip record" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated tree carrying Plan A's edits — the pattern lies on one line, contains no `**`, and is passed after `--`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 6: The activation list gains the three record duties + +**Spec:** §10 + +**Site** — pasted `grep -n` **against the post-Plan-A tree**, which is what Plan B edits: + +``` +CLAUDE.md:152:floor 3, and severity classified without the demotion; each further rule this change ships +plugins/dev-workflow/commands/workflow-init.md:352:floor 3, and severity classified without the demotion; each further rule this change ships +``` + +> **This is the extension Plan A's wording was built to permit.** Plan A shipped the strict-fallback list with two members and the clause "each further rule this change ships adds its own strict reading to this list" precisely so this task could append rather than rewrite — §10 says extending is safe and replacing is not. +> +> Plan B is the successor in that sentence's sense **within this change**; the loop-rule consolidation story is a different successor and extends it again later. + +- [ ] **Replace, in both copies.** OLD: + +``` +floor 3, and severity classified without the demotion; each further rule this change ships +adds its own strict reading to this list. +``` + +NEW: + +``` +floor 3, severity classified without the demotion, the provenance-line duty owed, the curve +duty owed, and the nonce duties at their strictest — the cycle is treated as post-rule, so it +owes a nonce, owes its provenance line and its curve or skip record, and uses that nonce in every +cycle record it does write — which changes what a record is named, never whether one is owed, so +the working record stays optional and a skipped cycle still writes no findings slots. Where it +cannot recover a nonce it starts a new cycle rather than claiming `none (pre-rule)`, that reserved +field being unavailable to a cycle whose start cannot be established. Each further rule this change ships adds its own strict +reading to this list. +``` + +- [ ] **Assert the new text is present.** + +```bash +grep -cF -- "the provenance-line duty owed, the curve" \ + CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +``` + +Before this task: `0` and `0`. After: `1` and `1`. Verified executable against a simulated tree carrying Plan A's edits — the pattern lies on one line, contains no `**`, and is passed after `--`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 7: The battery, and the hand-off to Plan C + +Plan B runs no Gate-B cycle. The single cycle covering all three plans is reviewed and closed by +Plan C. + +- [ ] **Run the battery, minus one step, for a stated reason** + +```bash +shellcheck --shell=sh plugins/dev-workflow/hooks/codex-gate.sh && \ +shellcheck --shell=sh --exclude=SC2015 plugins/dev-workflow/hooks/codex-gate.test.sh && \ +shellcheck --shell=sh scripts/check-invariants.sh && \ +shellcheck --shell=sh --exclude=SC2015 scripts/check-invariants.test.sh && \ +shellcheck --shell=sh scripts/check-version-bump.sh && \ +shellcheck --shell=sh scripts/check-version-bump.test.sh && \ +HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh && \ +HOOK_SH=dash dash plugins/dev-workflow/hooks/codex-gate.test.sh && \ +sh scripts/check-invariants.test.sh && sh scripts/check-invariants.sh && \ +sh scripts/check-version-bump.test.sh && \ +claude plugin validate . --strict && echo "BATTERY-GREEN (version-bump deferred)" +``` + +> **`sh scripts/check-version-bump.sh main` is deliberately absent, and only that one step**, for +> the same reason it is absent from Plan A: the manifest bump is Plan C's, so the checker would +> correctly fail here. Every other command must pass before Plan C opens. + +- [ ] **Hand off to Plan C** + +Plan C amends the same WIP commit, adds the manifest bump and the CHANGELOG entry, corrects the +user-facing sentences the two rule changes falsify, produces the evidence pack, runs the single +Gate-B loop against the recorded base SHA, and closes the cycle. Plan B's Gate-A loop must have +closed first. + +--- + +## Self-Review + +**Spec coverage.** §2.3 → Task 3. §4 including the squash carry → Tasks 4 and 5. §5 nonce and +slot rules → Tasks 1 and 2. §6's method → the accounting above. §10 extension → Task 6. + +**Placeholders.** None. + +**Type consistency.** ``, ``, ``, ``, ``, `` +and `` are used exactly as spec §2.3 and §4 define them, copied rather than restated. + +**Known limit, and it is the same boundary Plan A closed on.** Gate A reviews this plan, not the +edits. Each task's single check establishes that its edit landed at its site and nothing more. +What those edits do to the shipped files is Gate B's, reading the real diff. diff --git a/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c-rollout.md b/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c-rollout.md new file mode 100644 index 0000000..948a439 --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c-rollout.md @@ -0,0 +1,1483 @@ +# Plan C — rollout: falsified sentences, packaging, evidence, and the close + +> **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:** Correct every user-facing sentence Plans A and B falsify, ship the packaging, produce +the evidence the story's mode requires, run the one Gate-B cycle over the combined diff, and close +it. + +**Spec:** `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` (revision 36). +Plan C implements §7 and §8. + +**Story:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` + +> **Read the profile from that header at execution time.** This plan states no risk value, no +> security value, no validation mode and no pass count derived from any of them. Tasks 22 and 24 +> read the mode from the header and produce what it names. + +--- + +## Plan C of three + +| Plan | Ships | Status | +|---|---|---| +| A | the floor predicate and the severity test | Gate-A closed clean, pass 12 | +| B | the provenance line, the per-pass curve, the cycle nonce, slot naming | Gate-A closed clean, pass 7 | +| **C — this one** | rollout, packaging, evidence, and the single Gate-B cycle | this cycle | + +**Plan C edits the tree Plans A and B leave behind** for `workflow-init.md`; the user-facing docs +it corrects are untouched by either. Every `grep -n` anchor below was taken against the +correct base for its file. + +### The five obligations Plan C inherits + +These came from a Gate-B section Plan A dropped when the topology became one cycle. **A finding +whose section moved is relocated, not repaired**, so they are discharged here or nowhere: + +| From | What Plan C owes | +|---|---| +| pass-1 M8 | **single-branch Gate-B recovery** — delete only the failed branch, never both | +| pass-1 M9 | **record the floor knob's existence and bytes before the cycle, compare after** | +| pass-1 M10 | **never `git add -u`** — stage an explicitly inspected path set | +| pass-1 MINOR 12 | **build the closing body with `mktemp`**, not a fixed `/tmp` path | +| pass-1 B6 | **evidence revalidated after every fix and again before the closing amend** | + +--- + +## Global Constraints + +- **This Gate-B cycle runs under the rules in force at its start — the OLD ones.** Everything + Plans A and B ship binds only **after** the closing commit. **Carry this sentence in the + `additionalContext` of every Gate-B call**: a reviewer applying the new Minor-or-below ceiling + to the change that introduces it would under-iterate on exactly the diff needing most iteration. +- **The floor for this cycle is 3** — the constant, because the old rules govern it. Not the + derived value the diff introduces. +- **No file under `plugins/dev-workflow/hooks/` changes.** The floor knob + `.context/codex-gate.floor` is never written or removed; Task 13 reads it, and reading is what + the evidence requires. +- **Never `git commit --amend --no-edit` inside the cycle.** + `plugins/dev-workflow/hooks/codex-gate.sh:763` recognizes a WIP commit by grepping the **Bash + command string** for `-m ... wip`; an amend without `-m` is not recognized, and the hook resets, + discarding the cycle's passes. Every amend restates `-m "WIP: review-loop economics"`. +- **No ordinary commit until the close.** The cycle Plan A opened is still open. Every task here + amends it, including the ones touching only `docs/**.md` — the prose exemption decides whether a + *commit* needs Gate B, and this content is going into a commit that already does. +- **`git add` names paths explicitly, never `-u`.** Staging whole-tree changes would fold an + unrelated edit into the reviewed diff and close it under this story. +- **Line numbers are provenance, never instructions.** +- **Every task is skip-if-applied, and the preflight is a decision while the assert is a test.** + They answer different questions, so they are different commands and **reversing them is a real + defect**: an assert used as a preflight exits nonzero on `0`, which aborts every task that has + not run yet. **Preflight**, before the edit, where `0` is a valid answer meaning *apply*: + +```sh +case "$(grep -cF -- "" )" in + 0) : ;; # not applied — apply it + 1) echo "ALREADY APPLIED — skip this task"; exit 0 ;; + *) echo "AMBIGUOUS — stop, do not retry"; exit 1 ;; # duplicate or half-applied +esac +``` + + **Assert**, after the edit, where anything but `1` is a failure. The `test ... -eq 1` block in + each task below is that second command, and only that. +- **The assert string discriminates; it does not prove the block landed.** Before amending, + **read the edited site** and confirm the complete NEW block stands exactly once and — except for + Tasks 10 and 12 — no OLD text survives beside it. A sentinel counts `1` in a half-applied edit + and in a file holding OLD and NEW together. **This step is a reading, not a command, and that is + a limitation rather than a preference:** a multi-line pattern given to `grep -F` is read as + several alternative patterns, so a whole-block `grep` would silently OR the lines and report a + match on any one of them. +- **Tasks 10 and 12 re-emit their anchor inside their replacement**, so their OLD text survives + by design and only the NEW block discriminates. Every other task's OLD text is gone after it + runs. + +--- + +## Old-conditions accounting + +§6's method, applied to a different class: these are **user-facing statements that Plans A and B +make false**. Most are not §5 rules; **rows 15 and 16 are** — the skipped-cycle duty sentence in §5 +itself and in the scaffolded mirror — and they get the same accounting. For each, what it asserted +and what replaces it. + +| # | Statement | What it asserted | Disposition | +|---|---|---|---| +| 1 | `README.md` knob row | the knob moves the pass floor | **false as of Plan A** — the knob moves the hook's reminder threshold and never bound an agent. Replaced, with the distinction stated (Task 1) | +| 2 | `getting-started.md` Gate-A loop | three passes minimum | **replaced** by the derived floor; "final pass clean" and the zero-finding early exit are **kept** (Task 2) | +| 3 | same, plan loop | the same 3-pass loop | **replaced**; the rest of the sentence kept (Task 3) | +| 4 | same, hook message | the Gate-A floor wasn't met | **false** — the hook reports its own threshold, which at a derived floor of 1 reports a shortfall the cycle does not owe. Replaced (Task 4) | +| 5 | same, Gate-B loop | three passes, final clean | **replaced** by the derived floor; "final clean" kept (Task 5) | +| 6 | same, satisfied message | `3/3 cycle`, and `3 on current fingerprint` read as a tally | **replaced with named placeholders** — the numerator is the calls the hook *counted*, the denominator its own threshold, and the third value a *consecutive streak* on the current fingerprint. None is the floor (Task 6) | +| 7 | same, axes sentence | the axes never subtract **and** the floor is unchanged at every level | first clause **kept** — it is true and is the point; second **deliberately dropped**, since this change is what makes the floor vary (Task 7) | +| 8 | same, second knob mention | the knob moves the 3-pass floor | **false**, same as row 1. Both sites corrected, because fixing one would leave the other teaching it (Task 8) | +| 9 | `coding-workflow.md` axes sentence | as row 7 | same disposition (Task 9) | +| 10 | `workflow-init.md` before the template fence | *(nothing — this is an insertion)* | **rewrites nothing**; the note is added outside the fence so it never scaffolds (Task 10) | +| 11 | `plugin.json` version | `0.10.0` | **replaced** by `0.11.0` (Task 11) | +| 12 | `CHANGELOG.md` | *(nothing — an append)* | **rewrites nothing**; a new newest-first entry (Task 12) | +| 13 | `coding-workflow.md` model destination | the model goes in the evidence entry or the dispositions file | **replaced** by the per-pass curve's model field — neither old destination is keyed to a pass. The health-probe procedure above it is **kept** untouched (Task 13) | +| 14 | `process-pr-review.md` skip duties | a skipped cycle owes the skip reason, the battery result, and one evidence entry per cited profiled story | **all three kept and extended**: the provenance line and, in place of a curve, the skip record are **added**, because Plan B makes them owed by every cycle, skipped or not (Task 14) | +| 15 | `CLAUDE.md` §5 skip duties | as row 14, in the governing copy | same disposition (Task 15) | +| 16 | scaffolded template skip duties | as row 14, in the mirror | same disposition (Task 16) | +| 17 | `getting-started.md` skip duties | as row 14, in an explanatory duty summary | same disposition (Task 17) | +| 18 | `coding-workflow.md` skip duties | as row 14, in the other explanatory duty summary | same disposition (Task 18) | +| 19 | §5 findings-slot rule, as Plan B leaves it | the bare names serve every cycle with no nonce | **kept, unchanged.** The extension this row originally described — a deterministic discriminator for a no-nonce cycle where bare-slot files already exist — was **dropped on 2026-09-02** with Tasks 19 and 20. The rule ships as Plan B leaves it; this cycle's own non-bare slots are a recorded plan-local exception, not a shipped production | +| 20 | the same rule in the scaffolded mirror | as row 19 | same disposition, second copy (Task 20) | +| 21 | spec §8 parse-check item | the evidence is a **parse** check over both grammars | **kept, and its mechanism corrected**: the coverage requirement — constructed valid and invalid strings, features recorded per grammar — is **unchanged**; the word `parse` is replaced by the comparison that exists, a `grep -E` match against each grammar's productions, with the cardinality rule named as the one case decided by counting instead. §5's own rule sends a fix that changes specified behaviour into the same commit (Task 21) | + +**Rows 10 and 12 are insertions and are listed so a reader can confirm that rather than assume +it.** Each re-emits its anchor verbatim inside its replacement. + +**Rows 14-18 are one claim in five files.** The sweep that found three stopped at the prompt +copies; Gate-A pass 4 found the two documentation summaries, which enumerate the same duties and +omit the same two records. + +--- +## Task 1: The `codex-gate.floor` knob description + +**File:** `README.md` + +**Site** — pasted `grep -n`: + +``` +README.md:130:| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. | +``` + +> The knob never bound an agent — `$floor` appears in the hook's control flow, but that flow only selects which advisory message fires, and the hook exits 0 on every branch. The old line said it moved the floor, which was the clearest statement of the wrong model anywhere in the docs. + +- [ ] **Replace.** OLD: + +``` +| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. | +``` + +NEW: + +``` +| `codex-gate.floor` | a positive integer; moves the hook's reminder threshold. It does not change the floor §5 obliges, which is derived from the cited story's profile. | +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "moves the hook's reminder threshold. It does not change the floor" README.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add README.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 2: The Gate-A loop description + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:34:three passes minimum, final pass clean — the one early exit is a pass that comes +``` + +- [ ] **Replace.** OLD: + +``` +three passes minimum, final pass clean — the one early exit is a pass that comes +back with zero findings. +``` + +NEW: + +``` +the floor its profile derives, final pass clean — the one early exit is a pass that +comes back with zero findings. +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "the floor its profile derives, final pass clean" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 3: The plan-loop sentence + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:40:task-by-task plan (each task starts with a failing test); the same 3-pass loop runs +``` + +- [ ] **Replace.** OLD: + +``` +task-by-task plan (each task starts with a failing test); the same 3-pass loop runs +``` + +NEW: + +``` +task-by-task plan (each task starts with a failing test); the same loop runs at the derived floor +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "the same loop runs at the derived floor" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 4: The below-floor hook message + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:44:progress claims backed by test runs. If the Gate-A floor wasn't met, the hook says +``` + +> The hook reports against its own threshold, not against what the cycle owes. At a derived floor of 1 it will report a shortfall the cycle does not have — the named residual, and this sentence is where a reader would otherwise learn the opposite. + +- [ ] **Replace.** OLD: + +``` +progress claims backed by test runs. If the Gate-A floor wasn't met, the hook says +``` + +NEW: + +``` +progress claims backed by test runs. If the hook's own threshold wasn't met, it says +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "If the hook's own threshold wasn't met, it says" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 5: The Gate-B loop description + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:53:`mcp__codex__review` the same way: three passes, final clean. Verification is by +``` + +- [ ] **Replace.** OLD: + +``` +`mcp__codex__review` the same way: three passes, final clean. Verification is by +``` + +NEW: + +``` +`mcp__codex__review` the same way: the derived floor, final clean. Verification is by +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "the derived floor, final clean. Verification is by" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 6: The satisfied-message example + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:58:`✓ Codex Gate B satisfied (3/3 cycle, 3 on current fingerprint)`, the real commit replaces +``` + +> The literal `3/3` in the example is the hook's ratio. Writing `N/N` without saying which N it is would have replaced a wrong number with an ambiguous one. + +- [ ] **Replace.** OLD: + +``` +`✓ Codex Gate B satisfied (3/3 cycle, 3 on current fingerprint)`, the real commit replaces +``` + +NEW: + +``` +`✓ Codex Gate B satisfied (/ cycle, on current fingerprint)` — three different numbers: the calls the hook counted this cycle, the hook's own reminder threshold, and the **consecutive** counted calls on the current fingerprint since it last changed. The first is not the calls you made: the hook withholds the count for a recognized failure envelope, the backgrounding notice, and a result it can get no text from. The third is a streak, not a tally — the hook keeps the last fingerprint and that streak, so a pass on a changed fingerprint restarts it and an earlier matching pass separated by a different fingerprint is not counted. None of the three is the floor §5 obliges — the real commit replaces +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "the calls the hook counted this cycle" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 7: The axes-add-lenses sentence + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:84:is still owed and Gate A's floor is unchanged at every level. The caution bias is +``` + +> Two claims were tangled here: that the axes never subtract, which is true and stays, and that the floor is unchanged at every level, which this change makes false. Separated rather than reworded. + +- [ ] **Replace.** OLD: + +``` +is still owed and Gate A's floor is unchanged at every level. The caution bias is +``` + +NEW: + +``` +is still owed; Gate A's floor derives from the profile exactly as Gate B's does, and what the axes never subtract is the baseline questions. The caution bias is +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "Gate A's floor derives from the profile exactly as Gate B's does" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 8: The second knob mention + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:86:positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off` +``` + +> The same correction as Task 1, in the second place the docs describe the knob. Both sites say the same wrong thing and a fix to one would have left the other teaching it. + +- [ ] **Replace.** OLD: + +``` +positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off` +``` + +NEW: + +``` +positive integer) moves the hook's reminder threshold, and `touch .context/codex-gate.off` +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "moves the hook's reminder threshold, and" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 9: The coding-workflow axes sentence + +**File:** `docs/coding-workflow.md` + +**Site** — pasted `grep -n`: + +``` +docs/coding-workflow.md:79:gates for a risky or security-relevant change (they never subtract any: Gate A's floor and +``` + +- [ ] **Replace.** OLD: + +``` +gates for a risky or security-relevant change (they never subtract any: Gate A's floor and +the baseline questions are the same at every level), while the derived mode calibrates +``` + +NEW: + +``` +gates for a risky or security-relevant change (they never subtract a baseline question; the +floor itself derives from the profile, so it is not the same at every level), while the derived +mode calibrates +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "floor itself derives from the profile, so it is not the same at every level" docs/coding-workflow.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/coding-workflow.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 10: The item-1 n/a note, outside the fence + +**File:** `plugins/dev-workflow/commands/workflow-init.md` + +**Site** — pasted `grep -n`: + +``` +plugins/dev-workflow/commands/workflow-init.md:190:numbers) and say so in the report. +``` + +> **Outside the fence, deliberately.** Inside it the note would scaffold into every initialized project, where it is meaningless. And it is **not** written as a `Target model:` line: that would make this file's declaration count 2 and fail `scripts/check-invariants.sh`, which is the naive form of this fix. +> +> The fence here opens with four backticks because the template it wraps contains three-backtick blocks of its own; the note goes above that opener. + +- [ ] **Replace.** OLD: + +````` +numbers) and say so in the report. + +````markdown +````` + +NEW: + +````` +numbers) and say so in the report. + +> **Prompt-standards item 1 for the scaffolded `CLAUDE.md`: n/a, and why.** The file this +> template writes is model-agnostic by design — its executing model is whatever the reader of +> that project runs — so a `Target model:` line inside it would be false in every repo it lands +> in. Recorded as a reasoned n/a rather than skipped: the item is answered. **This note sits +> outside the fence** so it never scaffolds, and is deliberately **not** a `Target model:` line, +> which would make this file's declaration count 2 and fail `scripts/check-invariants.sh`. + +````markdown +````` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "Prompt-standards item 1 for the scaffolded" plugins/dev-workflow/commands/workflow-init.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 11: The manifest version bump + +**File:** `plugins/dev-workflow/.claude-plugin/plugin.json` + +**Site** — pasted `grep -n`: + +``` +plugins/dev-workflow/.claude-plugin/plugin.json:4: "version": "0.10.0", +``` + +> Minor, not patch: shipped rules change and no documented interface breaks. Invariant 12 requires a bump for any change under `plugins//`, and Plans A and B both changed `workflow-init.md`. + +- [ ] **Replace.** OLD: + +``` + "version": "0.10.0", +``` + +NEW: + +``` + "version": "0.11.0", +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- '"version": "0.11.0"' plugins/dev-workflow/.claude-plugin/plugin.json)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +The outer single quotes are what make the inner JSON quotes survive the shell — the same fix +pass 1 required. Before this task the count is `0`, so this exits nonzero if it is run early; +after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add plugins/dev-workflow/.claude-plugin/plugin.json +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 12: The CHANGELOG entry + +**File:** `plugins/dev-workflow/CHANGELOG.md` + +**Site** — pasted `grep -n`: + +``` +plugins/dev-workflow/CHANGELOG.md:25:## 0.10.0 +``` + +> Newest first, matching the file's stated convention. **Nothing enforces this** — neither invariant 12 nor `check-version-bump.sh` mentions the changelog — so it is carried by the story's criterion and by this task. + +- [ ] **Replace.** OLD: + +``` +## 0.10.0 +``` + +NEW: + +``` +## 0.11.0 + +- **The mandatory pass floor is now a function of the cited story's profile**, not the constant 3. + `max(risk, security) == 0` gives a floor of **1**; every resolvable profile above that, and an + artifact citing no story, gives **3**. A cited story whose profile is present but unresolvable + **stops and surfaces** rather than defaulting. Across a cited set the floor is 1 only if the set + is non-empty and every member is profiled, resolvable and at level 0. +- **The hook's ratio is a reminder threshold and controls nothing.** It always did; the text now + says so, and the `codex-gate.floor` knob is described as moving that threshold rather than the + obligation. `README.md` and `docs/getting-started.md` carried the old description and are + corrected. +- **Finding severity is decided by whether something in the system takes a different decision.** + Name what consumes the text and the decision that changes if it is wrong; if you cannot name + both, the finding is Minor or below. The review pass raising a finding is not an in-system + reader of the text it reviews, gates remain readers of rule text they will later apply, and a + human reader never satisfies the test. It sets a ceiling, never a floor, and never chooses + between Blocker and Major. +- **Two commit-body records are pinned**, so that a program can parse them — P8's deferred + measurement is the intended consumer, and **no parser for either form ships today**; the + evidence for this release matched constructed strings against the grammars instead: a + **provenance line** + carrying the cycle field, the derived floor and the cited set that produced it, and a + **per-pass curve** carrying Findings, Blockers and Majors per pass. One of each per cycle — a + change running five cycles records five. +- **A cycle nonce** attributes those records. Eight to sixteen characters from `[a-z0-9]`, from a + source of randomness, never derived from a name, timestamp or commit. It is + collision-**resistant**, not collision-proof, and the shipped text says where that bound bites + rather than implying a guarantee. +- **Findings slots take a per-cycle infix**, and the deletion step §5 already requires deletes + only paths carrying the cycle's own infix. A cycle that holds a **nonce** uses it; a cycle with + **no** nonce keeps the **bare** names. A cycle deletes only its own paths, never a bare path + belonging to somebody else and never another cycle's. That rule exists because a bare slot was + overwritten during this change's own development, destroying a previous cycle's findings file. + **What this release does not ship** is a rule for the remaining case — a nonce-less cycle where + bare-slot files already exist. This release's own Gate-B cycle is that case, and it used a + recorded plan-local naming exception rather than a shipped rule; a general production is + deferred to the loop-rule consolidation story. +- **What this change does not settle** is how demotion bears on the loop-health measures — the + per-pass counts, the clusters and the stop thresholds. That is the loop-rule consolidation + story's, and both documents say so, so the obligation cannot fall between them. + +## 0.10.0 +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "no parser for either form ships today" plugins/dev-workflow/CHANGELOG.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add plugins/dev-workflow/CHANGELOG.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 13: The model-recording instruction + +**File:** `docs/coding-workflow.md` + +**Site** — pasted `grep -n`: + +``` +docs/coding-workflow.md:279:the finding count in the pass record: the commit body's evidence entry, or the slot's +``` + +> Plan B pins a model field inside the per-pass curve. This sentence sent the value to the evidence entry or the dispositions file instead — **neither of which is keyed to a pass**, so a reader could not tell which pass a model belonged to. The health-probe procedure above it is untouched and is still how the value is established; only the destination changes. + +- [ ] **Replace.** OLD: + +``` +the finding count in the pass record: the commit body's evidence entry, or the slot's +dispositions file. This is +bookkeeping, not enforcement: nothing checks it, and a wrong entry looks exactly like a right +``` + +NEW: + +``` +the finding count in **the cycle's per-pass curve**, which pins a field for it — not the +evidence entry and not the dispositions file, neither of which is keyed to a pass. The health +probe above is how the value is established; the curve is where it goes. This is +bookkeeping, not enforcement: nothing checks it, and a wrong entry looks exactly like a right +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "the finding count in **the cycle's per-pass curve**" docs/coding-workflow.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/coding-workflow.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 14: The skipped-cycle duties — 1 of 5, `process-pr-review` + +**File:** `plugins/dev-workflow/commands/process-pr-review.md` + +**Site** — pasted `grep -n`: + +``` +plugins/dev-workflow/commands/process-pr-review.md:159: A skip removes the review and never the evidence. Every skipped cycle runs the battery +``` + +> **A contradiction between two shipped prompts, which invariant 11 forbids.** This command told a skipped cycle it owed the skip reason, the battery result and its evidence entries. Plan B makes the provenance line owed by **every** cycle, skipped or not, and requires a skip record in place of the curve. Left alone, an ordinary trivial-fix skip would close without records the other prompt says are mandatory. +> +> **This claim lives in five files, and Tasks 14 through 18 are one repair.** I fixed this one and reported it fixed; the sweep found the second and third a pass later, and the fourth and fifth a Gate-A pass after that — each time by grepping for the claim rather than for the phrasing. +> +> **The rule taken from it, and it is a rule about when rather than what: a fixed statement's other homes are found by grepping for the CLAIM at fix time, not by the next review pass.** Grepping for the phrasing finds the copy you already have; grepping for what the sentence asserts finds the others. `AGENTS.md` records the same failure in a different form, where four rounds each searched for the previous phrase and a synonym survived every time. + +- [ ] **Replace.** OLD: + +``` + A skip removes the review and never the evidence. Every skipped cycle runs the battery + and records, in the commit body, **the skip reason and the battery result**. On top of + that: one mode-derived evidence entry per cited **profiled** story, and none for an + unprofiled one — which owes the reason and battery result and nothing further. +``` + +NEW: + +``` + A skip removes the review and never the evidence. Every skipped cycle runs the battery + and records, in the commit body, **the skip reason and the battery result** — and, like + every other cycle, **its provenance line and, in place of a curve, its skip record**. On + top of that: one mode-derived evidence entry per cited **profiled** story, and none for an + unprofiled one — which owes the reason, the battery result, the provenance line and the + skip record, and nothing further. +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "its provenance line and, in place of a curve, its skip record" plugins/dev-workflow/commands/process-pr-review.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add plugins/dev-workflow/commands/process-pr-review.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 15: The skipped-cycle duties — 2 of 5, `CLAUDE.md` §5 + +**File:** `CLAUDE.md` + +**Site** — pasted `grep -n`: + +``` +CLAUDE.md:677:the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the +``` + +> The same claim as the previous task, in the governing copy. See that task's note for the sweep rule. + +- [ ] **Replace.** OLD: + +``` +the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the +battery and lands its evidence entry beside the reason; a skipped **unprofiled** story +records the reason and the battery result and nothing more, because it owes no mode-derived +entry and keeps exactly today's judgement-based skip. +``` + +NEW: + +``` +the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the +battery and lands its evidence entry beside the reason; a skipped **unprofiled** story +records the reason and the battery result, because it owes no mode-derived entry and keeps +exactly today's judgement-based skip. **Neither is excused the records every cycle owes** — +the provenance line, and a skip record in place of the curve. +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "Neither is excused the records every cycle owes" CLAUDE.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 16: The skipped-cycle duties — 3 of 5, the scaffolded template + +**File:** `plugins/dev-workflow/commands/workflow-init.md` + +**Site** — pasted `grep -n`: + +``` +plugins/dev-workflow/commands/workflow-init.md:856:the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the +``` + +> The same claim as Tasks 14 and 15, in the mirror. **The assert here counts 1 in this file and the previous task's counts 1 in `CLAUDE.md`** — the same pattern in two files, because the two copies carry the sentence independently and a fix to one has never implied a fix to the other. + +- [ ] **Replace.** OLD: + +``` +the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the +battery and lands its evidence entry beside the reason; a skipped **unprofiled** story +records the reason and the battery result and nothing more, because it owes no mode-derived +entry and keeps exactly today's judgement-based skip. +``` + +NEW: + +``` +the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the +battery and lands its evidence entry beside the reason; a skipped **unprofiled** story +records the reason and the battery result, because it owes no mode-derived entry and keeps +exactly today's judgement-based skip. **Neither is excused the records every cycle owes** — +the provenance line, and a skip record in place of the curve. +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "Neither is excused the records every cycle owes" plugins/dev-workflow/commands/workflow-init.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. Verified against a simulated tree carrying Plan A's and Plan B's edits. + +- [ ] **Amend the WIP commit.** + +```bash +git add plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 17: The skipped-cycle duties — 4 of 5, `getting-started.md` + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:105: still owes the battery, and records both the skip reason and its evidence entry in the +``` + +> **The same claim as Tasks 14-16, in an explanatory duty summary.** The three-file sweep stopped +> at the prompt copies. This page and the next task's enumerate the same duties and omit the same +> two records, and they are where a user actually reads what a skip owes — so the failure the +> three-file repair was for survives in the more likely place. + +- [ ] **Replace.** OLD: + +``` + still owes the battery, and records both the skip reason and its evidence entry in the + commit body. Gate A is not +``` + +NEW: + +``` + still owes the battery, and records the skip reason, the cycle's provenance line, a skip + record in place of the curve, and one evidence entry per cited profiled story, in the + commit body. Gate A is not +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "records the skip reason, the cycle's provenance line, a skip" docs/getting-started.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/getting-started.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 18: The skipped-cycle duties — 5 of 5, `coding-workflow.md` + +**File:** `docs/coding-workflow.md` + +**Site** — pasted `grep -n`: + +``` +docs/coding-workflow.md:129:change on security-relevant surface is not eligible. A skip removes the review, never +``` + +> The same claim as Task 17, in the other explanatory summary. **Both are corrected in the same +> revision**, because fixing one would leave the other teaching it — the rule row 8 of the +> accounting states, applied a second time. + +- [ ] **Replace.** OLD: + +``` +the evidence: the battery still runs, the reason is recorded in the commit body, and +so is one evidence entry per cited profiled story. **Explanatory** +``` + +NEW: + +``` +the evidence: the battery still runs, and the commit body carries the reason, the cycle's +provenance line, a skip record in place of the curve, and one evidence entry per cited +profiled story. **Explanatory** +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "the commit body carries the reason, the cycle's" docs/coding-workflow.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/coding-workflow.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 19: DROPPED — the slot rule admits a deterministic discriminator — 1 of 2, `CLAUDE.md` + +> **DROPPED by author decision, 2026-09-02. Do not execute this task or Task 20.** Shipping the +> discriminator would have put a slot production into both prompt copies that the approved spec's +> slot rules do not contain, leaving the source-of-truth section stale — Gate-A pass 7's first +> BLOCKER. The general production is deferred to the loop-rule consolidation successor story. +> **What replaces it for this cycle:** the `rle` slot names are a recorded plan-local naming +> exception under the old rules, approved by the author and recorded in the closing commit body +> and the field report. Task 23's second point cites Tasks 19 and 20 as admitting the name; read +> that citation as superseded by this exception. Task 21 is unaffected: it never touched the slot +> section, which is why dropping these two leaves nothing stale. §5 + +**File:** `CLAUDE.md` + +**Site** — pasted `grep -n`, against the tree Plan B leaves behind: + +``` +CLAUDE.md: > the nonce in every slot more than one cycle could write. The bare names are reserved for the +``` + +> **This is the contradiction Gate-A pass 6 found, and it was a real one.** Plan B's shipped rule +> sends a cycle with **no nonce** to the bare slot names. This cycle has no nonce — it is pre-rule +> and cannot mint one — so the shipped rule sends it to the bare names, **into the one workspace +> that already holds 61 bare-slot files**, where §5's delete-before-call step destroys them. Task +> 23 uses `gate-b--rle-pass-

`, which the shipped grammar admits nowhere: neither +> a bare name nor a nonce. **The plan and the prompt it ships cannot both be followed.** +> +> **Resolved one way, and this is the way:** the shipped rule gains the production. Spec §8 +> already describes it — *"its slot discriminator is short and deterministic, so it is not a +> nonce"* — so this makes the prompt state what the approved spec already assumes, rather than +> inventing a rule. The alternative, bare names plus a hand-rolled preservation step, recreates +> the incident the rule exists to prevent and leaves the spec's sentence describing nothing. +> +> **It admits a discriminator; it promises nothing about collisions.** A deterministic value is +> not drawn, so two cycles that pick the same one collide by construction. The text says so. + +- [ ] **Replace.** OLD — the complete sentence pair, so the replacement leaves no half-line: + +``` +> the nonce in every slot more than one cycle could write. The bare names are reserved for the +> legacy single-cycle case they already serve. **Distinct-nonce paths coexist by construction and +> are never in conflict** — a sibling cycle's slot is simply a different file. +``` + +NEW: + +``` +> the nonce in every slot more than one cycle could write. The bare names are reserved for the +> legacy single-cycle case they already serve — **with one exception, and it is the case that +> caused the incident**: a cycle with **no** nonce, writing into a workspace that already holds +> bare-slot files from earlier cycles, takes a **short, deterministic discriminator** in the +> nonce's position (`gate-b---pass-

`, and likewise for the Gate-A forms). +> **It is not a nonce and claims none of a nonce's properties** — it is derived rather than drawn, +> so two cycles choosing the same discriminator compute the same paths and are indistinguishable, +> exactly as two cycles drawing the same nonce would be. It binds the deletion step the same way: +> such a cycle **deletes only paths carrying its own discriminator**, never a bare path and never +> another cycle's. **Distinct-nonce paths coexist by construction and are never in conflict** — a +> sibling cycle's slot is simply a different file, and so is a distinct-discriminator path. +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "takes a **short, deterministic discriminator** in the" CLAUDE.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. + +- [ ] **Amend the WIP commit.** + +```bash +git add CLAUDE.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 20: DROPPED — the slot rule admits a deterministic discriminator — 2 of 2, the scaffolded template + +> **DROPPED by author decision, 2026-09-02, together with Task 19. Do not execute it.** See the +> note under Task 19 for the reason and for what replaces it. Task 23's second point and this +> plan's Self-Review both still say these two tasks admit the `rle` name into the shipped rule; +> read both as superseded by the recorded plan-local naming exception. + +**File:** `plugins/dev-workflow/commands/workflow-init.md` + +**Site** — pasted `grep -n`, against the tree Plan B leaves behind: + +``` +plugins/dev-workflow/commands/workflow-init.md: > the nonce in every slot more than one cycle could write. The bare names are reserved for the +``` + +> The same edit in the mirror. Plan B wrote this passage into both copies in one task; **Plan C +> corrects it in two**, because the assert counts `1` in each file separately and a fix to one +> has never implied a fix to the other. Invariant 11 forbids the two prompts disagreeing, and +> item 6 of the evidence pack is where that is demonstrated. + +- [ ] **Replace.** Same OLD and NEW as Task 19, in this file. + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "takes a **short, deterministic discriminator** in the" plugins/dev-workflow/commands/workflow-init.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. + +- [ ] **Amend the WIP commit.** + +```bash +git add plugins/dev-workflow/commands/workflow-init.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 21: The spec §8 wording correction + +**File:** `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` + +**Site** — pasted `grep -n`: + +``` +docs/superpowers/specs/2026-08-28-review-loop-economics-design.md:467:- **A parse check over both pinned grammars.** The branch's own instances cannot exercise them: +``` + +> **§5 sends this here rather than to a Gate-A reopening:** *"A fix that changes specified +> behaviour updates the spec in the same commit."* §8 asks for a **parse** check. No parser for +> either grammar exists in this repo, and none is built — a check that cannot execute is not a +> check. **The coverage requirement is untouched:** constructed strings, valid and invalid, +> features recorded per grammar. Only the named mechanism changes, and it changes to what exists. +> +> **A grep is not the whole of it, and revision 6 said it was.** That revision named cardinality +> as the single rule a match cannot decide. It is not the only one, and claiming so was the same +> overclaim in a smaller font. Every constraint a single regular match cannot decide is named in +> the replacement and checked by a separate comparison over the extracted fields. +> +> **This task carries a second replacement, in §1.** The same claim lives there in the present +> tense — *"a program parses them"* — about a program that does not exist. One claim, both sites, +> one pass. + +- [ ] **Replace.** OLD: + +``` +- **A parse check over both pinned grammars.** The branch's own instances cannot exercise them: + three lines cannot cover quoted paths, each unusable-knob cause, gapped pass ranges, split-model + passes, a skipped cycle, or the cardinality rule that `` matches ``. **The check + reads a set of constructed strings — valid ones that must parse and invalid ones that must be + rejected** — and records which grammar features each exercises. A grammar nothing ever parsed is + a format claim, not a format. +``` + +NEW: + +``` +- **A grammar check over both pinned grammars.** The branch's own instances cannot exercise them: + three lines cannot cover quoted paths, each unusable-knob cause, gapped pass ranges, split-model + passes, a skipped cycle, or the cardinality rule that `` matches ``. **The check + matches a set of constructed strings against each grammar's own productions with `grep -E` — + valid ones must match, invalid ones must not** — and records which grammar features each + exercises. **It is a grep plus field comparisons, not a parser**: these forms are pinned as + greppable field grammars and this repo ships no parser for either, so naming a parser would name + a check nobody can run. **A match decides the lexical productions and nothing else.** Every + constraint it cannot decide is checked by a comparison over the extracted fields: `` + having as many entries as `` enumerates; a `` occurring more than once; `` + ranges ascending and non-overlapping; a per-pass key present in one field and absent from its + partner. **That list is what reading both grammars for non-regular constraints produced, and it + is not proven complete** — a further one is checked the same way rather than folded into the + match. A grammar nothing was ever matched against is a format claim, not a format. +``` + +- [ ] **Assert the new text is present.** + +```bash +test "$(grep -cF -- "It is a grep plus field comparisons, not a parser" docs/superpowers/specs/2026-08-28-review-loop-economics-design.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. + +- [ ] **Replace, second site — §1, the same claim in the present tense.** OLD: + +``` +spec. Two things stay pinned as *required properties* because a shipped criterion reads them and a +program parses them: the provenance line (§2.3) and the per-pass curve (§4). +``` + +NEW: + +``` +spec. Two things stay pinned as *required properties* because a shipped criterion reads them and a +program is intended to parse them — P8's deferred measurement, which does not exist yet: the +provenance line (§2.3) and the per-pass curve (§4). +``` + +> **Left alone deliberately:** §2.3's *"P8 parses it"* and §2.4's *"the deferred P8 measurement +> parses it"* already name the consumer as deferred, and §2.4's *"a parser needs no special case"* +> is a property of the grammar rather than a claim that one exists. + +- [ ] **Assert the second replacement.** + +```bash +test "$(grep -cF -- "program is intended to parse them — P8's deferred measurement, which does not exist yet" docs/superpowers/specs/2026-08-28-review-loop-economics-design.md)" -eq 1 || { echo "ASSERT FAILED"; exit 1; } +``` + +Before this task the count is `0`, so this exits nonzero if it is run early; after, `1`. + +- [ ] **Amend the WIP commit.** + +```bash +git add docs/superpowers/specs/2026-08-28-review-loop-economics-design.md +git commit --amend -m "WIP: review-loop economics" +``` + +--- + +## Task 22: The evidence pack — everything that runs before the cycle + +Spec §8 names seven obligations. **All seven are discharged, split by when they can run**: six +here, and the two halves that depend on the cycle's own output in Task 24. The mode is read from +the story header at execution time. + +**The cycle base is `HEAD^`** — §5's own rule, that `baseSha` is the WIP commit's parent. Exactly +one WIP commit stands (Task 23 checks it), so no variable is recorded and no file holds it. + +- [ ] **1 — Battery.** The full `AGENTS.md` § Commands chain, green, **with the version-bump + checker given `HEAD^` as its base ref** — it takes one and **exits 2 with no argument**, so the + bare invocation is not a run. Plans A and B deferred this step for a stated reason; it runs now + because Task 11 landed the bump and the WIP exists. + +- [ ] **2 — A check that fails without the change.** Four assertions, two per revision: + +```bash +test "$(git show HEAD^:CLAUDE.md | grep -cF 'Blocker/Major-free pass 1 carrying a Minor')" -eq 1 || { echo "BASE LACKS THE OLD SENTENCE"; exit 1; } +test "$(git show HEAD^:CLAUDE.md | grep -cF 'a Blocker/Major-free pass below the floor')" -eq 0 || { echo "BASE ALREADY CARRIES THE NEW ONE"; exit 1; } +test "$(grep -cF 'Blocker/Major-free pass 1 carrying a Minor' CLAUDE.md)" -eq 0 || { echo "OLD SENTENCE SURVIVES"; exit 1; } +test "$(grep -cF 'a Blocker/Major-free pass below the floor' CLAUDE.md)" -eq 1 || { echo "NEW SENTENCE MISSING"; exit 1; } +``` + +**Grep the discriminating text, not the common tail.** Pre-change says **pass 1** keeps looping — +wrong at floor 1, where pass 1 *is* the floor; post-change says **below the floor**. **The +counterfactual is the third and fourth assertions**: without the change they fail, and the wiring +can produce that failure because the first two show the old text really is in the base. The +scaffolded mirror carries this rule too and is covered by item 6, which compares both copies. + +- [ ] **4a — The knob observation, before the cycle.** Take **one** observation of + `.context/codex-gate.floor` and derive everything from it, so bytes, digest and value class + describe the same read: + + - **absent** — no path. Record not-applicable with that reason. **Do not create one**; a fixture + supplying its own input proves nothing. + - **present but not a readable regular file** — a directory, a device, an unreadable file, **or a + symlink whose target does not resolve**. Record `unusable`. A broken symlink is + *present and unusable*, never absent — the state the stripped machinery got wrong and the one + this rule exists for. (The cause token this step originally recorded was withdrawn on + 2026-09-02; `unusable` no longer carries one.) + - **a readable regular file** — record bytes and digest, and classify the value as numeric or as + one of the pinned unusable causes. + + **Existence is not the check.** Task 24 repeats the observation and compares. + +- [ ] **5 — A grammar check over both pinned grammars**, features assigned **per grammar**. + Construct strings and match each against that grammar's own productions with `grep -E`: valid + ones must match, invalid ones must not. + + **Provenance:** a quoted path; the `unusable` value; **a valid numeric knob value**; + a cited-story-with-no-profile entry; `none` for no story cited; **a real `cycle ` field**. + **Curve:** a gapped `` such as `1,2,4`; a split-model pass; a `?` count; a skipped cycle's + skip record; **a `cycle ` field, the same nonce as the provenance instance**, so + cross-record agreement is exercised too. **Each grammar gets at least one string that must fail + to match** — for provenance, a malformed nonce. + + **The nonce and the numeric knob are in this list for a specific reason:** they are exactly the + two fields the live cycle cannot produce (Task 24), so without them the close would record as + covered two productions nothing ever exercised. What stays undemonstrable is narrower and is + named there: that a *drawn* nonce attributes a *live* cycle. A constructed one exercises the + form, not the attribution. + + **What a match cannot decide, checked by comparison instead** — each with one passing and one + failing instance: `` having as many entries as `` enumerates; a `` occurring + twice; `` ranges out of order or overlapping; a per-pass key present in one field and + absent from its partner. **That list came from reading both grammars for constraints no single + regular match decides, and it is not proven complete.** Both routes execute; neither is a + parser, which is why Task 21 corrects §8's word for it — **and revision 6's claim that + cardinality was the only such rule was itself an overclaim**, which is why this list is four + items and carries its own limit. + + Record which feature each string exercises. **This is where the grammars get their coverage** — + the cycle's own two records cannot, which is the reason §8 asked for constructed strings. + +- [ ] **6 — Parity across every changed rule**, in both copies. **One difference is expected and + is the only one**: the successor-story pointer, which `CLAUDE.md` carries and the template must + not. + +- [ ] **7 — A fresh twelve-item `docs/prompt-standards.md` pass** over each changed prompt + artifact invariant 11 names — the **resulting scaffolded template**, **`workflow-init.md`**, and + **`process-pr-review.md`**. One status row per item per artifact. **Item 1 for the scaffolded + template is `N/A`**; Task 10 ships the note saying why. + +--- + +## Task 23: The Gate-B cycle + +**Run the cycle per `CLAUDE.md` §5.** The floor, the file-first findings protocol, +delete-before-call, what makes a pass valid, single-branch recovery, re-review after every fix, the +clean-final-pass rule and the closing amend are **§5's, and this plan does not restate them**. +Revision 4 restated them, and Gate-A pass 4 spent seventeen of twenty-three findings reviewing the +restatement against the rules it was restating. **Three facts belong to this cycle and are not in +§5:** + +1. **The base is `HEAD^`.** §5's own rule: `baseSha` is the WIP commit's parent. Confirm first + that exactly one WIP commit stands and that it is not a merge — + +```bash +if ! git rev-parse --verify HEAD^ >/dev/null 2>&1; then echo "NO PARENT — no cycle base"; exit 1; fi +if git rev-parse --verify HEAD^2 >/dev/null 2>&1; then echo "WIP IS A MERGE — stop"; exit 1; fi +if ! git log -1 --pretty=%s | grep -q '^WIP: review-loop economics'; then echo "TIP IS NOT THE WIP"; exit 1; fi +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "PARENT IS ALSO A WIP — stacked, collapse first"; exit 1; fi +echo "BASE OK: $(git rev-parse HEAD^)" +``` + +**Every guard is an `if`, and the block ends on a command that succeeds.** Written as +` && { …; exit 1; }`, the last guard returns the failing grep's status **1 in the desired +state** — the parent correctly not being a WIP — so the block reports failure exactly when it +should report success, and under `set -e` the first negative guard aborts before the others run. + + The third asks about a **different** commit, which is why it can fail. A stacked WIP would + silently leave the earlier snapshot out of the only review. + +2. **The slots are `gate-b--rle-pass-

.md`**, and since Tasks 19 and 20 were + dropped, `rle` is admitted by **no shipped rule at all**: it is a **recorded plan-local naming + exception** under the old rules, approved by the author on 2026-09-02 and recorded in the + closing commit body and the field report. That is the whole of its authority. **§8 states the same thing** — *"its slot discriminator is short and + deterministic, so it is not a nonce"*. + + The reason it is needed here: this cycle is pre-rule and **cannot mint a nonce**, so the + shipped rule would send it to the bare names — into a workspace that already holds **61 files + in the bare `gate-b-*-pass-*` family**, where §5's delete-before-call step would destroy them. + That is precisely the incident the slot rule exists to prevent, about to be committed by the + plan that ships it. **Re-inventory before the first deletion rather than trusting the count**: + 61 was observed when this plan was written, and the discriminator is right at any number. + + **`rle` is not collision-resistant and claims nothing of the sort.** It is derived from the + change's name, so a second concurrent Plan-C executor computes the same paths. Before the + first deletion, confirm no live writer owns them; **stop rather than delete a target whose + owner you cannot establish.** + +3. **The cycle runs under the OLD rules**, per the activation constraint: **floor 3**, the + constant, not the derived value this diff introduces. Carry the old-rules sentence from Global + Constraints in every call's `additionalContext`. + +**Every call also carries** the union of the `Story:` headers of Plans A, B and C, and the current +evidence entry quoted verbatim. + +**Before the first call**, confirm `git status --porcelain` is empty and read +`git diff --name-only HEAD^..HEAD` against the three plans' declared surface. **This reads path +names, not content** — it catches a stray *file*, not a stray hunk inside a file the plans +legitimately touch. **Read the full `git diff HEAD^..HEAD` too**, against each task's own OLD-to-NEW +text above; that is the check that covers hunks, and it is a reading because the plan's OLD and NEW +blocks are what it compares against. + +--- + +## Task 24: The closing body, and the close + +**Runs after the final clean pass and before the amend.** The two evidence halves that need the +cycle's own output land here, then the body is composed from them. + +- [ ] **3 — A named verification of the risk path.** This cycle emits **one** provenance line. + Recompute its floor from the **union of the `Story:` headers of every plan contributing to the + reviewed diff** — Gate B reviews a diff and has no header of its own, which is what the shipped + rule says. (For this cycle all three plans cite one story, so the union is one path.) **The + observation that would exist if the claim were false is a line whose floor the cited profile + does not license.** + +- [ ] **4b — The knob observation, after the cycle.** Repeat Task 22 item 4a's single observation + and assert equality with the before-state: same path type, same bytes, same digest, or the same + recorded absence. **The knob is never written or removed by this plan**; this is what makes that + a demonstrated claim rather than an assurance. + +- [ ] **Revalidate the evidence entry** against the content the close will carry — §5 requires it + after every fix and again before the closing amend, and a fix changes the diff even when the + profile sits still. + +### What the closing body carries — and this is the complete list + +1. **The evidence entry**, one per cited **profiled** story, per §5. The story governing this + cycle is profiled, so there is one. +2. **This Gate-B cycle's provenance line.** +3. **This Gate-B cycle's per-pass curve.** +4. **Decline records**, if the cycle produced any. +5. **The `rle` naming exception**, which Task 23 requires be recorded here — a plan-local + exception under the old rules, approved by the author 2026-09-02, with no shipped rule + behind it. +6. **The standing decision record** for this cycle: the pass-2 discount, the dropped Tasks 19 + and 20, the withdrawn knob-cause and model-cause vocabulary with its accepted capability + cost, and the closing disposition. + +**Nothing else, and the exclusions are the point.** No records for the four Gate-A cycles, no +reconstruction, no reconstruction marker, no adjacent prose explaining one, and no line about the +superseded single-plan artifact. **Those four cycles closed before these rules bound anything, and +their record is `docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md`** — committed prose, which +git carries without a carry rule. Revision 4 put them in the body with a marker inside each record, +which broke the pinned grammar; revision 5 moved the marker beside the records, where the +squash-carry rule — which enumerates provenance lines, curves and skip records — does not reach it. +**The destination was the error, not the marker's position.** + +**The cycle field is `cycle none (pre-rule)`.** Plan B reserves that value for a cycle that began +before the rules shipped, and this one did. **The branch therefore demonstrates every field of both +forms except two** — a real nonce, and the knob clause's non-absent form if the knob is absent — +**recorded as undemonstrable here with their reasons**, not as gaps and not as satisfied. Task 22 +item 5's constructed strings are what cover the rest, which is why that obligation exists. **Any +cycle that both starts under these rules and closes discharges the nonce demonstration.** + +- [ ] **Close**, per §5's Mechanics: `git commit --amend -m` with the real message replacing the + WIP one, carrying the four items above. **Not `--no-edit`** — see Global Constraints. If the + amend fails, **the cycle is invalid, not retryable**: the hook resets Gate-B state on any + non-WIP commit command whether or not git succeeded. Repair while `HEAD` is still the WIP, run + the floor again, revalidate the evidence, then close. + +--- + +## Task 25: Complete the field report + +**File:** `docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md` + +**Runs after Task 24's closing amend**, as an **ordinary docs commit** — the first ordinary commit +this plan makes, and it is allowed because the cycle is closed. `docs/field-reports/**.md` is +explanatory prose, so Gate B is N/A. + +> **Why this task exists at all.** That file is the destination chosen for the four pre-rule +> Gate-A cycles' records, precisely so the observability those records carry survives outside the +> commit body. It currently says Plan C's cycle is **open at five passes** and promises the +> closing figures in a later revision. **Nothing scheduled that revision.** An artifact chosen for +> observability and then left knowingly incomplete is worse than no artifact, because a reader +> takes its silence for the end of the story. + +- [ ] **Extract Plan C's final curve mechanically**, the way that file's own numbers were taken: + +```bash +# Enumerate rather than counting up: an incomplete pass can leave a gap, and the curve +# grammar permits a gapped , so stopping at the first missing file drops every +# later pass from the record. +for n in $(ls .context/codex-reviews/ | sed -n 's/^gate-a-plan-planc-pass-\([0-9]*\)\.md$/\1/p' | sort -n); do + f=$(grep -cE '^(BLOCKER|MAJOR|MINOR|NIT) \|' ".context/codex-reviews/gate-a-plan-planc-pass-$n.md") + b=$(grep -c '^BLOCKER' ".context/codex-reviews/gate-a-plan-planc-pass-$n.md") + m=$(grep -c '^MAJOR' ".context/codex-reviews/gate-a-plan-planc-pass-$n.md") + printf 'pass %d: %s findings, %s B, %s M\n' "$n" "$f" "$b" "$m" + n=$((n+1)) +done +``` + +- [ ] **Replace the provisional passage** — the one saying the cycle is open at five passes and + promising later figures — with the closed cycle's full three-row block, in the same format the + Plan A and Plan B blocks use. + +- [ ] **Assert the provisional wording is gone and the closed record is present.** + +```bash +test "$(grep -cF -- "Open at 5 passes when this file was written" docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md)" -eq 0 || { echo "PROVISIONAL WORDING SURVIVES"; exit 1; } +# Absence is half the check: a replacement that deletes the provisional text and writes no +# closed record would pass it. Assert the closed record positively, and its cardinality. +test "$(grep -cE '^Findings( +[0-9]+,?)+' docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md)" -ge 3 || { echo "CLOSED CURVES MISSING"; exit 1; } +``` + +- [ ] **Commit it alone**, ordinary message, no `WIP:` prefix — the cycle is already closed and + this content was never part of the reviewed diff. + +--- + +## Self-Review + +**Spec coverage.** §7 rollout — Tasks 1-18. The shipped slot rule — Tasks 19 and 20. §8's own +wording — Task 21. **§8 evidence — Tasks 22 and 24**, all seven obligations, split at the only +line that could split them: whether the obligation needs the cycle's own output. The cycle and the +close — Tasks 23 and 24. The durable record — Task 25. + +**Revision 7's method was a claim sweep, not per-finding repair**, because pass 6 showed the +disease. Three of its findings were one claim corrected in one place and left standing in another +— all mine, all inside revision 6. Six claims were enumerated, every statement site grepped, and +every occurrence fixed in one pass: `AGENTS.md`'s own recipe, *search for the claim, not the +phrase*, used as the method rather than as a warning about it. The claim-to-site map is in the +commit body. + +**One of those claims changed what the plan ships**, and it was not a wording fix. The slot rule +was to gain a production (Tasks 19 and 20) — **that was dropped on 2026-09-02**, and the plan's +own slot form is now a recorded plan-local exception instead of a shipped rule. And **§8's +mechanism names four constraints a grep cannot decide instead of one**, because revision 6's +correction of an overclaim was itself an overclaim. + +**The dependency cycle is gone.** Revision 5 put the whole evidence pack before the cycle while +three of its seven items needed the cycle's output, so no task could truthfully complete. Items 3 +and 4b now sit after the final clean pass, where their inputs exist. + +**Reconstruction is gone, and the reason is worth keeping.** Three Gate-A passes contested it on +three different grounds — wrong destination, a marker that broke the grammar from inside, a marker +the squash-carry rule does not reach from outside. Two revisions answered the second and third and +left the first standing. **The destination was the error.** Pre-rule history is committed prose in +`docs/field-reports/`; the commit body carries only what this cycle natively produced. + +**`$base` is gone too.** §5 already says `baseSha` is the WIP commit's parent, so `HEAD^` is the +value, deterministic from git — no recorded variable, no file under `.context/`, and none of the +failure paths a recorded one brought with it. + +**What is still not mechanical**, said once rather than implied: the whole-block confirmation, +the parity comparison and the twelve-item pass are **readings, not commands**, and are named as +readings wherever they appear. Nothing checks a reading. That is a limit of this plan. + +**Inherited obligations.** M9 at Task 22 item 4a and Task 24 item 4b. M10 in Global Constraints. +**M8 and B6 are §5's own rules** — single-branch recovery, and evidence revalidated after every +fix and before the close — so Task 23 defers rather than restating. **MINOR 12 is moot**: it asked +for `mktemp` over a fixed `/tmp` path, and the body is now four items carried by §5's own amend. + +**Known limit.** Gate A reviews this plan, not the edits. What the edits do is Gate B's — Task 23, +reading the real combined diff of all three plans. diff --git a/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c1-user-docs.md b/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c1-user-docs.md new file mode 100644 index 0000000..32e4229 --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c1-user-docs.md @@ -0,0 +1,893 @@ +# Plan C1 — the user-facing floor description + +> **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:** Correct every sentence in `README.md` and `docs/getting-started.md` that Plan A makes +false about **where the pass floor comes from**. Nine sentences in eight replacements, two files, +one claim. + +**Spec:** `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` (revision 36). +C1 implements the part of §7 that lands in these two user-facing docs. + +**Story:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` + +> **Read the profile from that header at execution time.** This plan states no risk value, no +> security value, no validation mode and no pass count derived from any of them. The evidence the +> mode requires is produced by Plan C3, which runs the one Gate-B cycle. + +--- + +## Why this plan is small + +Plan C tried to carry this rollout, the packaging, the evidence and the Gate-B cycle in one +artifact. Its Gate-A cycle **ran seven passes and never converged** — Blocker/Major stayed between +15 and 21, and the last pass was the worst. The record is +`.context/codex-reviews/gate-a-plan-planc-CLOSURE.md`. Daniel split it by statement site. + +**C1 is the first of three.** C2 takes the packaging and the satisfied-message description; C3 +takes the evidence, the Gate-B cycle and the close. + +### The one claim this plan owns + +**Three passes is not a universal or constant floor.** §5 derives the floor from the profile and +the cited set, and the `codex-gate.floor` knob moves the hook's **reminder threshold** rather than +that floor. + +**Three is still the right answer in most states.** The claim being corrected is that it is +*always* the answer, and that the knob moves it. **This plan states no floor for this story** — +the value is recomputed from the story header at execution time, and a number written here would +be stale the moment that header moved. + +**The rule, exactly as Plan A ships it** (byte-frozen; quoted rather than summarised because a +summary of this is what pass 1 and pass 2 both caught): + +**The stop conditions come first, and they are not outvoted by the rest of the set.** A mixed +set — one unresolvable member beside several level-0 ones — **stops**; it does not fall through to +a floor. Reading a stop as 3 would turn a stop condition into a silent default, which is the one +thing Plan A says the derivation must never do. + +- **No value, and the cycle stops** for: a cited profile present but **unresolvable**; governing + headers that **disagree**; a `Story:` header that **cannot be read**. +- **Otherwise, floor 1 if and only if** the cited set is **non-empty** and **every** member is + **profiled**, **resolvable** and at **level 0** — all four conditions, since *every member* is + vacuously true of an empty set. +- **Otherwise, floor 3**: no story cited; any cited story unprofiled; or any resolvable member + above level 0. There are two levels, not three — `high` takes its rigor from lens sets and + evidence mode, not from extra passes. + +One derived value governs all three cycles, because they derive from the same cited-story set. + +Nine sentences state the claim wrongly, in eight replacements. They are the complete set in these +two files **as of this revision**, found by reading for the claim — every place a number of +passes, a floor, a threshold, gate satisfaction, or the knob is described. **Two of the nine were +found by review rather than by that reading** (pass 1 and pass 2 each found one), which is the +measure of how much the reading is worth. + +**Task 9 is a bounded mechanical check, not a proof of completeness.** It re-runs the *numeric* +spellings and the two knob phrasings. A tenth sentence stating the claim without a number would +pass it. The plan says so at the task rather than implying otherwise here. + +### What this plan does not own, so that nothing falls between the pieces + +| Not here | Where | Why not here | +|---|---|---| +| `docs/getting-started.md:58`, the satisfied-message example | **C2** | It describes what the hook's message *reports* — three numbers, three meanings. A different claim in the same file. The below-floor sites at lines 35 and 44 are **not** that claim and are Tasks 3 and 5 here. | +| CHANGELOG entry, manifest bump, the item-1 n/a sentence | **C2** | Packaging. | +| The parser claim, the findings-slot rule, spec §8, the evidence pack, the Gate-B cycle, the close, the field report | **C3** | C3's site list must name the two shipped prompt copies carrying *"because the deferred metrics work parses it"* — the two sites Plan C's own claim sweep missed. | +| `docs/coding-workflow.md` — the axes sentence and the model-recording sentence | **UNASSIGNED** | Neither a C1 file, nor packaging, nor the close. | +| The skipped-cycle duty sentence in five files | **UNASSIGNED** | One claim across `process-pr-review.md`, `CLAUDE.md`, the scaffolded template and both explanatory summaries. | + +**The two unassigned rows are a real gap in the split, and they block the combined close rather +than this plan.** The story requires every falsified shipped sentence corrected in the same +change, so C3 cannot close while they have no owner. Named here, routed to Daniel, and **not +absorbed** — taking them would make C1 a second Plan C, which is what the split exists to prevent. + +--- + +## Global Constraints + +- **These edits join the open cycle.** Every task amends the WIP commit with + `-m "WIP: review-loop economics"`. `plugins/dev-workflow/hooks/codex-gate.sh:763` recognizes a + WIP commit by grepping the Bash command string for `-m ... wip`. **An amend the hook does not + recognize as WIP is treated as a Gate-B cycle boundary and the cycle's Gate-B state is reset — + regardless of whether the commit itself succeeded**, since that branch cannot observe exit + status. Gate-A state is reset by skill events, not here. **Never `git commit --amend --no-edit`.** +- **Every amend runs three cheap guards before staging**, and it is worth being exact about what + they establish: the branch is `review-loop-economics`, `HEAD`'s subject is exactly the WIP + subject, and `HEAD^` is not itself a WIP. **They catch the wrong branch, a closed cycle, a + stacked WIP and a plain out-of-order run. They do not establish that `HEAD` is *this* cycle's + WIP** — a different commit with the same subject on the same branch passes all three, and every + amend changes the SHA, so there is no stable value to compare against without a lock this plan + does not have. **Single-executor, single-worktree is a precondition of this plan, not something + it verifies.** +- **Every amend inspects the index before staging.** `git commit --amend` commits the whole + index, so a change staged by anything else would ride along, and `git add ` does nothing + to prevent that. **Empty is fine and this task's own path already staged is fine** — that is + what an interrupted add-then-amend leaves behind, and rejecting it would dead-end the resume + path this plan advertises. **Any other staged path stops.** The scoped read is + `git diff HEAD -- `, which sees the index as well as the worktree; plain `git diff` does + not. +- **Every git command whose output is tested has its status checked first.** `test -z "$(cmd)"` + reads a *failed* command's empty output as a clean result, which would let a broken index or an + unreadable repository pass a safety precondition. +- **Every amend verifies the replacement reached `HEAD`** by reading the committed blob back. +- **No ordinary commit until C3 closes the cycle.** +- **Line numbers are provenance, never instructions.** +- **Preflight and assert are different commands and every task carries both, instantiated.** The + preflight is a state machine over the changed old and new lines where **every-new-zero means + apply**; the assert runs after the edit, where every changed new line must appear **exactly + once** and every replaced old line **zero** times. Reversing them would abort every task that + has not run yet. +- **Every replacement is line-complete and every check is `grep -cxF`** — whole-line equality. A + replacement that lands but drops a clause fails. This also removes the counting hazard a + substring check has, where two occurrences on one line count as one. +- **A multi-line replacement asserts only the lines that change.** Task 2 carries one line + through unaltered; asserting it would assert the file's prior state. +- **Neither file is touched by Plan A or Plan B**, so every anchor below was verified against the + **current** tree. No simulation, and none claimed. + +--- + +## Task 0: Checkpoint + +**Runs before Task 1.** It establishes the preconditions every later task assumes, and it records +the one value a rollback needs. + +- [ ] **Establish the starting state.** + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — collapse first"; exit 1; fi +st=$(git status --porcelain) || { echo "git status FAILED — stop"; exit 1; } +test -z "$st" || { echo "TREE NOT CLEAN — resolve before starting C1"; exit 1; } +echo "PRE-C1 WIP: $(git rev-parse HEAD) <-- record this value" +``` + +- [ ] **Rollback, if C1 must be undone.** Only with the recorded SHA, and only while the tree is + clean: + +```bash +sha=PASTE_THE_RECORDED_PRE_C1_WIP_SHA +st=$(git status --porcelain) || { echo "git status FAILED — stop"; exit 1; } +test -z "$st" || { echo "TREE NOT CLEAN — do not reset; inspect first"; exit 1; } +git log --oneline "$sha"..HEAD # read it: every commit here must be C1's +git rev-parse --verify "$sha^{commit}" >/dev/null 2>&1 || { echo "NOT A COMMIT — check the recorded value"; exit 1; } +git log -1 --pretty=%s "$sha" | grep -qx 'WIP: review-loop economics' || { echo "RECORDED SHA IS NOT THE WIP — stop"; exit 1; } +git reset --hard "$sha" +``` + +> **What this rollback does and does not cover.** It restores the branch to the recorded commit, +> and Plans A and B are behind that commit and untouched. It is **not** a general safety net: a +> clean worktree says nothing about *commits* made after the checkpoint by anything else, and +> `git status` cannot see them — so a `--hard` here would discard them. It also races anything +> writing concurrently, which is why single-executor is a precondition above. +> +> **It covers exactly one state: a clean tree whose only commits since the checkpoint are C1's.** +> Establish that by reading `git log ..HEAD` before resetting. **Any other state — a dirty +> tree, a staged edit, an unfamiliar commit — is a stop, and this plan supplies no recovery for +> it**, because a recovery procedure nobody has exercised is worse than an instruction to look. + +--- + +## Task 1: The knob row in `README.md` + +**File:** `README.md` + +**Site** — pasted `grep -n`: + +``` +README.md:130:| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. | +``` + +> The knob never bound an agent. `$floor` appears in the hook's control flow, but that flow only +> selects which advisory message fires, and the hook exits 0 on every branch. This line was the +> clearest statement of the wrong model anywhere in the docs. + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- '| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. |' README.md) + n1=$(grep -cxF -- '| `codex-gate.floor` | a positive integer; moves the hook'\''s reminder threshold. It does not change the floor §5 obliges, which §5 derives from the profile and the cited set. |' README.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ]; then + if [ -n "$(git diff HEAD -- README.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. | +``` + +NEW: + +``` +| `codex-gate.floor` | a positive integer; moves the hook's reminder threshold. It does not change the floor §5 obliges, which §5 derives from the profile and the cited set. | +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- '| `codex-gate.floor` | a positive integer; moves the hook'\''s reminder threshold. It does not change the floor §5 obliges, which §5 derives from the profile and the cited set. |' README.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- '| `codex-gate.floor` | a positive integer; moves the 3-passes-per-gate floor. |' README.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "README.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- README.md || { echo "git diff FAILED — stop"; exit 1; } +git add README.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:README.md | grep -cxF -- '| `codex-gate.floor` | a positive integer; moves the hook'\''s reminder threshold. It does not change the floor §5 obliges, which §5 derives from the profile and the cited set. |')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 2: The intro's account of what the hook reminds about + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:7:normally; the skills and gates structure *how Claude works*, and the hook reminds +``` + +> **The claim's earliest site, and the first sweep missed it.** "The hook reminds you when a gate +> isn't satisfied" presents the hook's counter as gate state. It is not: the hook reports its own +> threshold and fingerprint, and §5 decides satisfaction. Under a floor-1 cycle the hook can warn +> after the owed floor is already met; after enough counted calls it can report its threshold +> satisfied while findings are still open. +> +> **Line 1 of the replacement is unchanged** and carries no assert of its own — only the two lines +> that actually change do. + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- 'both of you when a gate isn'\''t satisfied. Your job is the decision points —' docs/getting-started.md) + n1=$(grep -cxF -- 'both of you when its own counter or fingerprint says a gate may not have run —' docs/getting-started.md) + n2=$(grep -cxF -- '§5 decides whether one is satisfied. Your job is the decision points —' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ] && [ "$n2" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ] && [ "$n2" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +normally; the skills and gates structure *how Claude works*, and the hook reminds +both of you when a gate isn't satisfied. Your job is the decision points — +``` + +NEW: + +``` +normally; the skills and gates structure *how Claude works*, and the hook reminds +both of you when its own counter or fingerprint says a gate may not have run — +§5 decides whether one is satisfied. Your job is the decision points — +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- 'both of you when its own counter or fingerprint says a gate may not have run —' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- '§5 decides whether one is satisfied. Your job is the decision points —' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'both of you when a gate isn'\''t satisfied. Your job is the decision points —' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'both of you when its own counter or fingerprint says a gate may not have run —')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- '§5 decides whether one is satisfied. Your job is the decision points —')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 3: The Gate-A loop and its counter example + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:34:three passes minimum, final pass clean — the one early exit is a pass that comes +``` + +> **Two sentences, one task, on purpose.** The loop description and the `1/3` example sit in one +> paragraph and state the same claim; the first sweep split them, and a substring assert could then +> pass while a load-bearing clause was dropped. Replaced together, **every resulting line is a +> complete line** and each is asserted whole. +> +> `1/3` stays as an example — the literal is the hook's own ratio. What changes is that the +> sentence now says which number is whose. "final pass clean", the zero-finding early exit and +> "not an error" are all kept. + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- 'three passes minimum, final pass clean — the one early exit is a pass that comes' docs/getting-started.md) + o2=$(grep -cxF -- 'back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` are' docs/getting-started.md) + o3=$(grep -cxF -- 'the counter, not an error. Your job: arbitrate disputed findings — Codex is' docs/getting-started.md) + n1=$(grep -cxF -- 'the floor §5 derives, final pass clean — the one early exit is a pass that comes' docs/getting-started.md) + n2=$(grep -cxF -- 'back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` count' docs/getting-started.md) + n3=$(grep -cxF -- 'the calls against the hook'\''s own reminder threshold, not against the floor §5 obliges,' docs/getting-started.md) + n4=$(grep -cxF -- 'and are not an error. Your job: arbitrate disputed findings — Codex is' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$o2" -eq 1 ] && [ "$o3" -eq 1 ] && [ "$n1" -eq 0 ] && [ "$n2" -eq 0 ] && [ "$n3" -eq 0 ] && [ "$n4" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$o2" -eq 0 ] && [ "$o3" -eq 0 ] && [ "$n1" -eq 1 ] && [ "$n2" -eq 1 ] && [ "$n3" -eq 1 ] && [ "$n4" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +three passes minimum, final pass clean — the one early exit is a pass that comes +back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` are +the counter, not an error. Your job: arbitrate disputed findings — Codex is +``` + +NEW: + +``` +the floor §5 derives, final pass clean — the one early exit is a pass that comes +back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` count +the calls against the hook's own reminder threshold, not against the floor §5 obliges, +and are not an error. Your job: arbitrate disputed findings — Codex is +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- 'the floor §5 derives, final pass clean — the one early exit is a pass that comes' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` count' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'the calls against the hook'\''s own reminder threshold, not against the floor §5 obliges,' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'and are not an error. Your job: arbitrate disputed findings — Codex is' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'three passes minimum, final pass clean — the one early exit is a pass that comes' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +test "$(grep -cxF -- 'back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` are' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +test "$(grep -cxF -- 'the counter, not an error. Your job: arbitrate disputed findings — Codex is' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'the floor §5 derives, final pass clean — the one early exit is a pass that comes')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'back with zero findings. Hook messages like `⚠ Codex Gate A below floor (1/3)` count')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'the calls against the hook'\''s own reminder threshold, not against the floor §5 obliges,')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'and are not an error. Your job: arbitrate disputed findings — Codex is')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 4: The plan-loop sentence + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:40:task-by-task plan (each task starts with a failing test); the same 3-pass loop runs +``` + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- 'task-by-task plan (each task starts with a failing test); the same 3-pass loop runs' docs/getting-started.md) + n1=$(grep -cxF -- 'task-by-task plan (each task starts with a failing test); the same loop runs at the derived floor' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +task-by-task plan (each task starts with a failing test); the same 3-pass loop runs +``` + +NEW: + +``` +task-by-task plan (each task starts with a failing test); the same loop runs at the derived floor +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- 'task-by-task plan (each task starts with a failing test); the same loop runs at the derived floor' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'task-by-task plan (each task starts with a failing test); the same 3-pass loop runs' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'task-by-task plan (each task starts with a failing test); the same loop runs at the derived floor')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 5: The below-floor hook message + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:44:progress claims backed by test runs. If the Gate-A floor wasn't met, the hook says +``` + +> The hook reports against its **own threshold**, not against what the cycle owes. At a derived +> floor of 1 it announces a shortfall the cycle does not have — the named residual of this change, +> and the same claim Task 3's example carries. + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- 'progress claims backed by test runs. If the Gate-A floor wasn'\''t met, the hook says' docs/getting-started.md) + n1=$(grep -cxF -- 'progress claims backed by test runs. If the hook'\''s own threshold wasn'\''t met, it says' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +progress claims backed by test runs. If the Gate-A floor wasn't met, the hook says +``` + +NEW: + +``` +progress claims backed by test runs. If the hook's own threshold wasn't met, it says +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- 'progress claims backed by test runs. If the hook'\''s own threshold wasn'\''t met, it says' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'progress claims backed by test runs. If the Gate-A floor wasn'\''t met, the hook says' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'progress claims backed by test runs. If the hook'\''s own threshold wasn'\''t met, it says')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 6: The Gate-B loop description + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:53:`mcp__codex__review` the same way: three passes, final clean. Verification is by +``` + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- '`mcp__codex__review` the same way: three passes, final clean. Verification is by' docs/getting-started.md) + n1=$(grep -cxF -- '`mcp__codex__review` the same way: the derived floor, final clean. Verification is by' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +`mcp__codex__review` the same way: three passes, final clean. Verification is by +``` + +NEW: + +``` +`mcp__codex__review` the same way: the derived floor, final clean. Verification is by +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- '`mcp__codex__review` the same way: the derived floor, final clean. Verification is by' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- '`mcp__codex__review` the same way: three passes, final clean. Verification is by' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- '`mcp__codex__review` the same way: the derived floor, final clean. Verification is by')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 7: The axes sentence + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:84:is still owed and Gate A's floor is unchanged at every level. The caution bias is +``` + +> **Three claims share this sentence and only one is false.** That the profile supplies +> eligibility rather than the skip: **kept**. That the battery is still owed: **kept**. That Gate +> A's floor is unchanged at every level: **this change makes it false**, and it is the only clause +> replaced. **Nothing is added** — an earlier draft introduced a clause about baseline questions +> the old sentence never made. + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- 'is still owed and Gate A'\''s floor is unchanged at every level. The caution bias is' docs/getting-started.md) + n1=$(grep -cxF -- 'is still owed; Gate A'\''s floor derives from the profile and the cited set exactly as Gate B'\''s does. The caution bias is' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +is still owed and Gate A's floor is unchanged at every level. The caution bias is +``` + +NEW: + +``` +is still owed; Gate A's floor derives from the profile and the cited set exactly as Gate B's does. The caution bias is +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- 'is still owed; Gate A'\''s floor derives from the profile and the cited set exactly as Gate B'\''s does. The caution bias is' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'is still owed and Gate A'\''s floor is unchanged at every level. The caution bias is' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'is still owed; Gate A'\''s floor derives from the profile and the cited set exactly as Gate B'\''s does. The caution bias is')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 8: The second knob mention + +**File:** `docs/getting-started.md` + +**Site** — pasted `grep -n`: + +``` +docs/getting-started.md:86:positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off` +``` + +> The same correction as Task 1, in the second place the docs describe the knob. **Both sites say +> the same wrong thing**, and this repo's own record is that a fix to one has never implied a fix +> to the other. + +- [ ] **Preflight — a state machine over both texts, where every-new-zero is the signal to apply.** + +```sh + o1=$(grep -cxF -- 'positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off`' docs/getting-started.md) + n1=$(grep -cxF -- 'positive integer) moves the hook'\''s reminder threshold, and `touch .context/codex-gate.off`' docs/getting-started.md) +if [ "$o1" -eq 1 ] && [ "$n1" -eq 0 ]; then + : # not applied — apply it +elif [ "$o1" -eq 0 ] && [ "$n1" -eq 1 ]; then + if [ -n "$(git diff HEAD -- docs/getting-started.md)" ]; then + echo "REPLACED BUT NOT YET IN THE WIP — run the amend step only, then skip"; exit 0 + # the amend step accepts this task's path already being staged, so an interrupted + # add-then-amend resumes there rather than dead-ending on a non-empty index + fi + echo "ALREADY APPLIED AND COMMITTED — skip"; exit 0 +else + echo "MIXED OR DUPLICATE STATE — stop, do not retry"; exit 1 +fi +``` + +- [ ] **Replace.** OLD: + +``` +positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off` +``` + +NEW: + +``` +positive integer) moves the hook's reminder threshold, and `touch .context/codex-gate.off` +``` + +- [ ] **Assert** — every changed line present exactly once, whole; every replaced line gone. + +```bash +test "$(grep -cxF -- 'positive integer) moves the hook'\''s reminder threshold, and `touch .context/codex-gate.off`' docs/getting-started.md)" -eq 1 || { echo "NEW LINE MISSING OR DUPLICATED"; exit 1; } +test "$(grep -cxF -- 'positive integer) moves the 3-pass floor, and `touch .context/codex-gate.off`' docs/getting-started.md)" -eq 0 || { echo "OLD LINE SURVIVES"; exit 1; } +``` + +- [ ] **Amend** — identity first, index empty, scoped diff read, then verify it reached `HEAD`. + +```bash +git symbolic-ref --short HEAD | grep -qx 'review-loop-economics' || { echo "WRONG BRANCH — stop"; exit 1; } +git log -1 --pretty=%s | grep -qx 'WIP: review-loop economics' || { echo "HEAD IS NOT THE WIP — stop"; exit 1; } +if git log -1 --pretty=%s HEAD^ | grep -q '^WIP:'; then echo "STACKED WIP — stop"; exit 1; fi +staged=$(git diff --cached --name-only) || { echo "git diff --cached FAILED — stop"; exit 1; } +case "$staged" in + "") : ;; + "docs/getting-started.md") echo "NOTE: only this task's path is staged — an interrupted amend; continuing" ;; + *) echo "UNEXPECTED STAGED PATHS: $staged — inspect before staging"; exit 1 ;; +esac +git diff HEAD -- docs/getting-started.md || { echo "git diff FAILED — stop"; exit 1; } +git add docs/getting-started.md || exit 1 +git commit --amend -m "WIP: review-loop economics" || exit 1 +test "$(git show HEAD:docs/getting-started.md | grep -cxF -- 'positive integer) moves the hook'\''s reminder threshold, and `touch .context/codex-gate.off`')" -eq 1 || { echo "REPLACEMENT NOT IN HEAD"; exit 1; } +``` + +**Read that `git diff HEAD` before staging.** It covers the worktree *and* the index, which +`git diff` alone does not — and `git commit --amend` commits the whole index, so a staged change +this task never made would ride along. The empty-index check above is what makes that observable. + +--- + +## Task 9: Confirm the claim has no tenth site + +**Runs last.** This re-runs the mechanical part of the discovery search against the finished +files. **It is a bounded check, not a completeness proof** — see the boundary stated under the +first command, and the plan's own count of sites that only review found. + +- [ ] **No numeric floor claim survives, except the one C2 owns.** + +```bash +if grep -nEi '3-pass|three passes|3 passes|3-passes-per-gate|[0-9]/3' README.md docs/getting-started.md \ + | grep -vF 'Codex Gate B satisfied (3/3 cycle'; then + echo "A NUMERIC FLOOR CLAIM SURVIVES — read the hits printed above"; exit 1 +fi +``` + +> **The one expected hit is filtered, not tolerated.** The satisfied-message example at +> `docs/getting-started.md:58` prints `3/3 cycle` and belongs to **C2**; C1 runs first, so that +> line is still there when this check runs. Filtering it by exact text is what lets the check +> reach its passing state while C2 is still owed — an unfiltered version could never pass in the +> stated order, which pass 2 caught. +> +> **This sweep covers the numeric spellings only.** Tasks 2, 5 and 7 correct sentences that state +> the claim with no number — *"when a gate isn't satisfied"*, *"If the Gate-A floor wasn't met"*, +> *"Gate A's floor is unchanged at every level"* — and those are covered by their own tasks' +> old-line-gone asserts, not by this grep. Said here because a sweep that looks complete and is +> not is worse than one whose boundary is written down. + +- [ ] **The knob's non-binding property holds at both sites.** + +```bash +test "$(grep -cF -- "moves the hook's reminder threshold" README.md)" -eq 1 || { echo "README KNOB LINE WRONG"; exit 1; } +test "$(grep -cF -- "moves the hook's reminder threshold" docs/getting-started.md)" -eq 1 || { echo "GETTING-STARTED KNOB LINE WRONG"; exit 1; } +if grep -nF -e "moves the 3-pass floor" -e "moves the 3-passes-per-gate floor" README.md docs/getting-started.md; then + echo "A KNOB-BINDS-THE-FLOOR CLAIM SURVIVES"; exit 1 +fi +``` + +> **Fixed strings, not a general pattern, and deliberately.** A regex broad enough to catch any +> re-binding of the floor was written first and **errored on this machine's `grep`** — +> *"exceeds complexity limits"*. A command that cannot run reports nothing and reads as a pass, +> which is worse than a narrow check. These three catch the two phrasings that existed and confirm +> the replacement landed in both files; a *newly invented* way of saying the knob moves the floor +> is caught by a reader, not by this. +> +> **The two knob rows are deliberately not identical** — `README.md` names the source, the +> getting-started line does not. The check is on the property, not on sameness. + +- [ ] **If either check produced a fix**, amend as the tasks above do and re-run both. A clean + Task 9 stages nothing and amends nothing — the only task here that does not. + +--- + +## Self-Review + +**Scope.** Nine sentences in eight replacements, two files, one claim. Every anchor verified +against the current tree; neither file is touched by Plan A or Plan B, so no simulation was needed +and none is claimed. + +**Every replacement is line-complete, and every check is whole-line equality.** Pass 1 caught +substring sentinels that could pass after a clause was dropped; pass 2 caught the two remaining +mid-line replacements with the same hole. Tasks 2 and 3 were restructured — the intro's three +lines and the Gate-A paragraph's four — so that no replacement lands mid-line anywhere in the +plan and `grep -cxF` covers all of them. + +**Task 3 merges two sentences the first sweep had split.** They sit in one paragraph and carry one +claim, and splitting them is what let a substring check look sufficient. + +**Two sites came from review, not from the original sweep**: the `1/3` counter example (pass 1) +and the intro's *"when a gate isn't satisfied"* (pass 2). Both were found by reading for the +claim, both are now owned, and the fact that a claim-grep missed them twice is recorded in the +plan rather than smoothed over. + +**The derivation rule is quoted, not summarised.** Both earlier revisions summarised it and both +summaries were wrong — first by making the source a single story, then by omitting the +above-level-0 arm and two of the three stop conditions. + +**Rollback is a task, not a sentence.** Task 0 records the SHA and establishes the clean-tree +precondition that makes `--hard` safe; the plan says what to do when that precondition fails. + +**Two rows in the scope table have no owner**, and that is stated as a gap in the split rather +than solved here. They block C3's close, not this plan. Absorbing them would rebuild Plan C. + +**Known limit.** Gate A reviews this plan, not the edits. What the edits do is Gate B's — C3's. diff --git a/docs/superpowers/specs/2026-08-28-review-loop-economics-design.md b/docs/superpowers/specs/2026-08-28-review-loop-economics-design.md new file mode 100644 index 0000000..5020c06 --- /dev/null +++ b/docs/superpowers/specs/2026-08-28-review-loop-economics-design.md @@ -0,0 +1,614 @@ +# Review-loop economics: pass floor and severity semantics — Design + +**Date:** 2026-08-29 · **Revision:** 36 (rules only) · **Gate-A passes 1-33** +**Story:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` +**Profile:** read from that header, never from here. + +Prompt-only, in two mirrored copies: `CLAUDE.md` §5 and `/workflow-init`'s inline template. +**No file under `plugins/dev-workflow/hooks/` changes, and no hook state file is written.** + +**This document states rules and decisions.** Concrete formats, per-site wordings, the +old-conditions passage list, and every procedure live in the plan, reviewed there against this +spec. Two things stay pinned as *required properties* because a shipped criterion reads them and a +program is intended to parse them — P8's deferred measurement, which does not exist yet: the +provenance line (§2.3) and the per-pass curve (§4). + +**One constraint on the edit itself:** `codex-gate.sh:94` greps `CLAUDE.md` for the §5 heading to +build every reminder's citation, so **the heading must keep matching +`^#{1,6}[[:space:]]+([0-9]+\.)?[[:space:]]*Cross-Model Review`**. + +--- + +## 1. Why these two parts ship together + +Part 1 makes the mandatory pass floor a function of the story profile. Part 2 decides finding +severity by whether something in the system takes a different decision. + +**They are coupled by an argument.** Part 2's test settles a part-1 question: a path-derived +`docs-only` arm for the floor would be **wrong here**, because `docs/hardening-log.md` is a +`docs/**.md` path that drives rung escalation. Splitting these two would separate a rule from the +argument that decides it. + +The §5 loop-rule consolidation is **not** in this spec; it moved to +`docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md`. Nothing here waits on it. + +--- + +## 2. The pass floor (part 1) + +**One predicate.** `max(risk, security) == 0` → floor **1**. Every resolvable profile above that, +and an **absent** profile, → **3**. Two levels, not three: `high` takes its rigor from lens sets +and evidence mode. + +**A present but unresolvable profile is not "everything else".** §5 already requires it to **stop +and surface the cause**, and that rule stands unchanged — the predicate applies only to profiles +that resolve, and to artifacts citing no story. Reading an unresolvable profile as 3 would convert +an existing stop condition into a silent default. + +**Unanimity across a cited set.** Floor 1 **if and only if the cited set is non-empty and every +member is profiled, resolvable, and at level 0** — all four conditions, because "every cited story" +is vacuously true of an empty set. **No story cited, or any cited story unprofiled, yields 3. Any +cited story whose profile is present but unresolvable stops and surfaces**, which is the §5 rule +above and not a floor of 3. This follows §5's own precedent — "skip-eligible only +if **every** cited story is" — and is the only reading consistent with invariant 2's firing +direction. + +**One derived value governs the Gate-A spec loop, the Gate-A plan loop and the Gate-B cycle.** Not +because they are one cycle — §5 is explicit that they are **three separate cycles** — but because +they derive from **the same cited-story set**. Both copies state the rule and this reason. + +### 2.1 The floor lives in the text + +**Nothing here writes `.context/codex-gate.floor`.** The floor is derived and **stated**; §5's text +is what binds an agent. + +**The knob stays the user's** — never written, never removed, never read for the derivation. It is +the **hook's reminder threshold** and never bound an agent: `$floor` appears in control flow, but +that flow only selects which advisory message fires, and the hook exits 0 on every branch +(invariant 1). + +**Precedence, shipped as text:** + +> **The derived floor is the pass count a cycle owes, and the hook's ratio is a reminder threshold +> that controls nothing.** Where the cycle's own closure rules are satisfied, a below-threshold +> reminder is **noted in the pass report and disregarded.** +> +> **This replaces the pass-count number and nothing else.** Every other rule §5 states about how a +> cycle closes stands as written, and none of them is restated here — a summary is where their +> conditions would get dropped. + +**Named residual, disclosed in both copies:** the hook's messages state its own threshold as an +obligation, so at level 0 they report a shortfall the cycle does not owe. **Hook text is out of +scope by decision**; the precedence rule plus invariant 1 are what make it tolerable, not the +reminder being harmless. + +### 2.2 What a pass report states + +The **derived floor**, the **risk and security values read**, and the **cited stories they were +read from**. A report giving the number alone leaves a reader unable to check the derivation while +passes are still being spent, which is the only time checking it is cheap. + +### 2.3 The provenance line — required properties + +One line per cycle, in its closing commit body. **The grammar is pinned here, not in the plan** — +the parent story requires one form and says this spec states it, and P8 — deferred, and not +built yet — is intended to parse it. (Revision 15 +moved it out and was wrong to; pass 9 had already settled this, and the test it settled on is +whether anything but a person parses it.) + +``` +; floor per ; hook reminder threshold + + := "cycle " | "cycle none (pre-rule)" + := [a-z0-9]{8,16} + := [1-9][0-9]* + := "none" | "{" ("," )* "}" + each appears at most once; a repeated path, + with or without conflicting levels, is malformed + := " (level " ("0"|"1"|"2") ")" | " (unprofiled)" + := | + := [A-Za-z0-9._/-]+ contains no delimiter, quote or whitespace + := a double-quoted string, non-empty, whose only escapes are \" and \\ ; + a path containing a newline or other control + character is NOT representable — the cycle stops + and surfaces rather than emitting one + := "absent" | [1-9][0-9]* | "unusable" +``` + +Everything that quotes this line elsewhere quotes an instance of it; there is no informal variant. +The properties the grammar exists to satisfy: + +- **Every cycle records it**, default floor or not, so an absent line is never ambiguous between + "the default applied" and "someone forgot". **One line per cycle run**, so a change running + five cycles records five — the count is of cycle runs, not of the three cycle kinds. +- It carries that cycle's **cycle field** — **the nonce (§5) for any cycle started after these + rules ship, and `none (pre-rule)` only for a cycle that began before them** — the **derived + floor**, and **the cited set that produced + it with each member's level as a numeral** — one floor and one set, not an entry per story, + since unanimity makes the floor a property of the set. +- It distinguishes **a cited story with no profile** from **no story cited**. +- It records the **workspace knob whenever the file exists**: the value if one was read, + otherwise `unusable`. **It does not name why.** An earlier revision required a cause token — + unreadable, empty, non-numeric, out-of-range — on the reasoning that those need different + fixes. That was withdrawn on 2026-09-02, knowingly and at a cost: four successive attempts to + state the classification behind those tokens were each wrong in a different way, the last one + demonstrably (a file of `1`, NUL, `2` is accepted by the hook as twelve, in `sh`, `dash` and + `bash` alike). The record now says **that** a knob was unusable, no longer **why**; whoever + needs why reads the file and the hook. +- It is **machine-extractable and has one form covering every case**, because the deferred P8 + measurement parses it. + +### 2.4 A profile or cited set that moves mid-cycle + +Three existing rules compose; no new rule. The floor derives from the **current** profile at each +pass; **passes already run keep counting**; **closing requires the floor as currently derived.** + +**These are pass-count rules, so they apply while §5 says a gate is running** and are silent +otherwise. What §5 says about when a gate runs — including how a moving profile or cited set bears +on that — is §5's, unchanged and deliberately not summarised here. **This change's only +skip-specific addition is in §4**: a skip record in place of a curve. + +**Two things are outside that scope and are not suspended by a skip.** The **provenance line** is +owed by every cycle, skipped or not (§2.3). And an **accepted in-set Blocker or Major stays in the +fix set**: acceptance put it there, not the citation, so neither removing a story nor becoming +skip-eligible discharges it. + +**Any profile change costs at least one further pass**, in either direction and whether +or not the floor number moves, because §5 already requires the **final clean pass** to run under the current +profile — so no already-banked pass can be it. **That further pass must be clean and every other +closure duty must be satisfied**; it is one more pass, not a licence to close on the next one. **What a lowering drops is whatever the changed values drop, not a fixed pair**: a mode-only +override changes the evidence obligations while leaving the axis-derived lens sets alone, and +security `high` → `standard` keeps the security lens set while changing what evidence is owed. The +rule is that **every derived obligation is recomputed from the current profile**; naming lenses and +mode as a package was wrong in both directions. +That is the pre-existing profile-change path; the variable floor rides it. + +**The cited set is re-read at each pass, and the final clean pass runs against the current set** — +**whenever its membership changes, not only when the floor number moves.** Adding a high-risk story +to a set already at floor 3 leaves the number alone while adding that story's lens set, its +evidence obligations and its review scope; a pass run before it joined did not cover them. + +**A profile change moves the number only sometimes, in both directions**: level 2 → level 1 leaves +the floor at 3, exactly as level 0 → level 0 leaves it at 1. The one-further-pass consequence +attaches to the **profile or set changing**, not to the number moving — the final clean pass must +run under the current profile and against the current set regardless. Where the number does move, +upward is a raise and downward a lowering. **What a removal discharges, precisely:** obligations are +recomputed from the current set, so removing a story does remove *that story's* lenses and evidence +duty. It **never discharges an accepted in-set Blocker or Major** — the user's acceptance put that +finding in the fix set, not the citation, so removing the citation does not take it out. + +--- + +## 3. Severity semantics (part 2) + +**One procedure decides severity. The subject list is illustration, not a second rule.** + +> Name **what in the system consumes this text** — whatever *acts* on it — and the decision that +> act takes differently if the text is wrong. **Both are required.** If you cannot name both, the +> finding is **Minor or below**; collect, never iterate. + +The exclusions are contract, not commentary: + +- **The reader must consume the text in the system's *operation*, not in reviewing it. The review + pass raising the finding is not an in-system reader of the text it reviews.** Without this the + test demotes nothing. **Gates remain legitimate readers** of rule text they will later apply. +- **A human reader never satisfies the test** — §5's prose exemption already prices that cost as + non-gating. +- **The list of reader kinds is illustrative, not closed**, because this ships into projects whose + readers we have never seen (invariant 10). +- **The test sets a ceiling, not a floor**, and **never chooses between Blocker and Major** — + Mechanics still decides that. +- **The instrument carve-out is symmetric**: an instrument finding keeps its severity whenever it + shows the instrument changes what a gate concludes about product behaviour — a false green, and + equally a false red or a check blocking a valid change. +- **Rationale prose is Minor only when no rule's application depends on it**, not categorically. + `docs/prompt-standards.md` requires that rules carry their why, because models follow motivated + rules better, and invariant 11 makes that binding — so rationale a reader must consult to apply + a rule passes the test. +- **This removes arbitrariness, not judgement**, and the shipped text says so. +- **Coverage-first is unchanged**: the reviewer reports every finding with severity and confidence; + the filter is ours. + +**Kinship, stated in the shipped text:** the **finding-level analog of the path-level prose +exemption** — one principle at two granularities, text that *describes* the product versus text +that *is* the product. + +**Expected effect, with its limit, disclosed rather than claimed.** Distributions like PR #23's +should demote substantially, but **how much is not predictable** — that commit's own body describes +harness defects the symmetric carve-out keeps. Ledger rows and story criteria keep their severity +because escalation and the assigned-fix-set rule read them. **The amount is a prediction, and it stays one**: §4 explains why the durable record cannot +measure demotion — no finding is classified both ways — so what the story routes to P8 is a +comparison of recorded severity mixes across cycles, with **both** confounds named (the curves are +self-reported and unvalidated; the cycles compared reviewed different artifacts), not a measurement +of this rule's effect. + +--- + +## 4. The per-pass curve — required properties + +Each of the three cycles records its own per-pass finding and Blocker counts in its own commit +body, labelled with the cycle it describes — **Findings, Blockers and Majors per pass**, since the +severity rule moves the Blocker/Major line. **Gate B alone would leave the dominant cost +unrecorded** — the loops this story cites as evidence are Gate-A loops. **Pinned here for the same +reason as the provenance line:** + +``` +; (passes , ): Findings . Blockers . Majors . + + := "Gate-A spec" | "Gate-A plan" | "Gate B" + := ("," )* strictly ascending, non-overlapping + :=

|

"-"

+

:= [1-9][0-9]* + := ("," )* exactly as many entries as enumerates + := 0 | [1-9][0-9]* | "?" "?" = the count is unrecoverable for that pass + := | ("; " )* + := "pass "

" " ("+" )* + := | | "undetermined" + "undetermined" means the model could not be determined; + a real model so named is written as + := [!-~]{1,} minus ; : , ( ) + " and space, and not the + literal "undetermined", which is reserved + printable ASCII only; a control character makes the + identifier unrepresentable, handled below + := a non-empty double-quoted string, same two escapes as ; + a reported identifier containing a control character is + written `undetermined` — and the raw value is NOT + reproduced anywhere in the body, since a commit message + cannot safely carry one (NUL cannot appear at all). + (the source-and-rejected-bytes obligation this line + carried was withdrawn 2026-09-02 with the cause + vocabulary; nothing replaces it) +``` + +**`` keys must be exactly the passes `` expands to, each once, ascending** — a +per-pass model list that omits or repeats a pass is malformed, not partially informative. **Every +model contributing to a split logical pass is listed** for that pass, joined by `+`; recording one +of two contributing models is the same loss as recording none. + +A skipped cycle writes `; : skipped (see skip reason)` and no counts. +`cycle none (pre-rule)` is the **only** admissible alternative to a nonce, reserved for a cycle +that began before the nonce rule shipped (§10 decides which cycles those are) — it is a production of the grammar rather than a +magic string beside it, so the one-form claim holds and a parser needs no special case. The +properties the form exists to satisfy: + +- **Records Majors as well as Findings and Blockers**, because the severity rule moves the + Blocker/Major line rather than the total, so a record of totals and Blockers alone could not show + even a change in the mix. (What that record supports, and what it cannot, is stated below — it is + a comparison, not a measurement of the rule's effect.) **Subject categories are deliberately not recorded**: they are a judgement per finding + rather than a count, and the findings files carry the material for anyone who wants it. + +**What the curve makes answerable, and what it does not — because the pre-rule baseline is thinner +than the forward record.** Going forward, post-rule cycles carry Findings, Blockers and Majors per pass, so **their recorded +severity mixes are comparable**. **That is not the same as measuring demotion**, and the difference +matters: demotion is what happens to *one finding* under two classifications, and nothing here +records a finding classified both ways. Comparing mixes across cycles that reviewed different +artifacts is evidence about the population, not about the rule. **A demotion figure would need a +paired classification the durable record does not carry**, so what P8 can report is a change in +recorded mixes with the confound named. **The `fic2` baseline is thinner, and precisely how matters.** Its closing commit `3cdd075` +carries the complete per-pass **totals and Blockers and no Major series at all**; the committed +field report adds **Majors for passes 1–5 only**, and the field report carries **some** per-finding +subject material — it classifies the four pass-5 findings by subject, quotes one verbatim, and +gives aggregate pass-2 clusters — but not a per-finding classification across all seven passes. So the baseline supports **total-volume and Blocker comparison across all +seven passes**, a **Major comparison over five of them**, and nothing finer — which the report must +state whenever it uses the Major series. Saying this +here keeps a later reader from computing a demotion figure the baseline cannot bear. +- Carries that cycle's **cycle field**, so a curve can be attributed to the cycle that produced + it — **the nonce for a post-rule cycle, `none (pre-rule)` for one that began before the rules + shipped.** A pre-rule record is **not attributable to a cycle**, and the text says so rather than + implying the field always identifies one. +- **One entry per valid pass**, and because incomplete passes are excluded and consume pass + numbers, the record **states which pass numbers it covers**. A valid zero-finding pass is + recorded as zero, never omitted. +- **A count that cannot be recovered is written `?`, never guessed and never written as `0`.** A + cycle can keep its identity while its pass files are gone — §5's recovery turns on the nonce, not + on the findings files — so a resumed cycle may know a pass happened and not what it found. Zero + and unknown are different facts, and a record that cannot tell them apart understates every curve + containing one. + **`?` is per series, and so is its exclusion.** A pass whose Findings are unknown may still have + a known Blocker and Major count, and those remain usable. **P8 excludes a `?` value from the + comparisons that read that series and keeps the pass's other series**, reporting exclusions + **per series** rather than per pass. Discarding a whole pass because one of its three numbers is + unknown would throw away good data — and would do so unevenly, since the series most often lost + is not the same across cycles. +- A `full` Gate-B pass, separate `spec`/`quality` calls, and a single-branch recovery are + **branches of one logical pass** contributing one summed entry. **The curve counts logical + passes; the hook counts calls**, and where they differ the body says so. +- **Both branches of one logical pass must be issued against the same artifact revision**, + identified by the **tracked reviewed commit** passed to each call — the plan fixes how it is + encoded, but which thing is compared is a decision, not a format. **What this establishes is + that the two calls were aimed at one revision, not that either branch read it**: the reviewer + reports no reviewed revision, so no stronger claim is available. **If it changed between them they are not one pass**: the + completed branch is recorded as an incomplete pass and excluded, and the later branch begins a + new one. Ending the pass is the conservative direction; merging two revisions would produce one + entry describing two different artifacts. +- **The model each pass ran under is recorded**, per the existing convention in + `docs/coding-workflow.md`, written `undetermined` where it cannot be determined rather than + guessed, and admitting provider-qualified identifiers. +- A **legitimately skipped** cycle records the skip rather than a silent gap. + +**What it is worth, stated rather than implied.** Durable **across cycles**. **Not within a running +cycle** — the commit does not exist until the cycle closes. And **author-written and unchecked**: +nothing compares it against the validated pass files, so **P8 reads a self-reported curve** and +must not present it as measurement. + +**Squash carry.** §5's rule names only evidence entries and human-exception records; the +**provenance lines, the curves and a skipped cycle's skip record** are added. *(The successor story +adds the decline record to the same passage; extending is required, replacing would unship these.)* + +--- + +## 5. The cycle nonce + +Both shipped records carry a **cycle field**, because a record that cannot be attributed to a cycle +cannot be told apart from another cycle's when several are read together. **That is a limitation, +not a disqualification** — the `fic2` baseline is entirely pre-rule and is used precisely because a +human knows which cycle it came from. What attribution buys is that a *later* reader does not have +to. **§5 defines three cycle *kinds* — the Gate-A spec loop, the +Gate-A plan loop and the Gate-B cycle — and one cycle field is produced per cycle *run*, not per +kind: a change carrying several plans runs a Gate-A plan cycle for each, so it produces +`2 + number_of_plans` records, and for post-rule cycles that many distinct nonces.** A **pre-rule** cycle has no nonce — §10's activation rule is what makes a cycle pre-rule; its field is the +reserved `none (pre-rule)` and its records are not cycle-attributable; the activation rule that +creates that case is §10's. Everything below describes +**post-rule cycles**, which is every cycle started after the implementation commit. + +- Generated once at cycle start, immutable, and **collision-resistant operationally: 8 to 16 + characters drawn uniformly from `[a-z0-9]`, from a source of randomness** — the same bound the + grammar pins, 8 being where collision resistance starts and 16 where the field stops being a + usable infix — never derived from a + name, a timestamp or a commit, each of which collides exactly where sibling cycles do. +- Constrained so it is **safe as a slot infix and a path component**. +- **It appears in every record the cycle writes** — for every cycle started after these rules + ship — and **the record set is named rather than left open**: the **provenance line**, the + **per-pass curve** (including a skip record standing in for one), and, because §5's slot and + companion rules already key them to a cycle, the cycle's **findings slots** and its **advisory + working record**. It is not required in records this change neither introduces nor keys to a + cycle — the evidence entry and a human-exception record among them — which are the successor's + to consider if it needs them. **A pre-rule cycle has no nonce and cannot acquire one**, so its records carry + `none (pre-rule)` in the cycle field and are, by construction, not cycle-attributable. That is a + bounded, self-terminating exception: it applies only to cycles already running when the rules + land, and no later cycle can enter the state. +- **A nonce is a *candidate* for recovery only if it is keyed to this cycle's type (Gate-A spec, + Gate-A plan, or Gate B) and this cycle's artifact, and that cycle is still open.** History + normally holds many closed cycles' nonces and they are not candidates; a working record left by a + closed cycle is not one either, and **the working record is retired at closure** so it cannot + become one later. +- **Recovery has two sources**, because a Gate-A cycle's commit does not exist while it runs: the + advisory working record during the cycle, and history at its commit. Recovering a single + candidate from **either** keeps identity. **No candidate, disagreeing sources, or more than one + candidate → no identity, start a new cycle** — which costs passes rather than letting one cycle's + records read as another's. +- **A cycle does not start without a valid, unique nonce.** Where generation fails — randomness + unavailable, an invalid value, or a collision with an open cycle — retry within a bounded policy + the plan fixes, then **stop and surface**. **No deterministic fallback**, since a derived value + collides exactly where sibling cycles do, which is the property the nonce exists to avoid. +- **The advisory working record is a cycle record too**: it carries the nonce, and the slot rules + below apply to it as they do to findings slots. §5's optional-companion *status* is unchanged. + +**Slot rules this change adds** — the plan fixes the spelling, these are the properties. §5's +current slot grammar admits three exact names and no per-cycle component, which is why this is an +addition rather than a reference: +- **A cycle that has a nonce uses it in every slot more than one cycle could write.** The bare name + is reserved for the legacy single-cycle case it already serves. +- **A target owned by another nonce is refused, not overwritten**, and the refusal names the + collision. §5 already stops on a target that survives deletion; this extends that to a target + that must not be deleted at all. +- These rules exist because a bare slot was in fact overwritten during this cycle, destroying a + previous cycle's findings file. + +--- + +## 6. Old-conditions accounting + +**Method.** For each passage this change rewrites: list what its existing prose requires, then mark +each requirement **kept**, **moved**, or **deliberately dropped**. A requirement neither kept nor +explicitly dropped is a dropped condition. **No passage is rewritten without its accounting.** + +**A passage that both this change and the successor rewrite appears in both accountings**, each +covering its own change — two changes to one passage owe two answers. + +**Where it is produced and gated.** In one artifact, written **once against the frozen final text** +and **reviewed before any replacement text is written**. **That review is part of the Gate-A plan +cycle, not a fourth cycle** — the topology stays at three, and the artifact is an input the plan +cycle reviews alongside the plan. Its acceptance is that plan cycle's clean pass; **no replacement +text is written while it is outstanding**, and a finding against it **feeds back** — the artifact +is regenerated against corrected text — rather than being absorbed. **The plan carries the passage list and executes +this method against it** — and because the dispositions are deferred, **that list is the sole guard +against a dropped condition**, so it is re-checked whenever the design adds a rule. + +**The two copies are accounted for separately** where their text differs, since they already +diverge substantially and one accounting cannot cover both. + +--- + +## 7. Rollout, and what this change falsifies + +**No prerequisite is owed here, contrary to earlier revisions.** They claimed the template lacked +the rationale §3's kinship points at and required adding it. **The template already carries the +principle** — "Those describe the product rather than being it, so they carry no gate at all" — +and the kinship claim rests on that describe-versus-be distinction, not on `CLAUDE.md`'s longer +cost clause. What the copies differ in is the *phrasing of the cost*, which the kinship does not +depend on. **So no seam is opened**, and the divergence between the copies is untouched by this +change beyond the rules it actually edits. + +**Statements this change falsifies must be corrected in the same change** — in `README.md`, +`docs/getting-started.md` and `docs/coding-workflow.md`, wherever they assert a fixed three-pass +floor, describe the knob as moving the §5 floor, say the hook reports an unmet Gate-A floor, or +wait for a `3/3` message before closing. The plan carries the site list. **The rewording describes +the knob as what it mechanically is** and names the sanctioned lever for fewer obliged passes — the +profile, or `codex-gate.off` for reminders. Nothing is deprecated. + +**Gate-B classification of the doc edits.** §5's exemption requires **every** staged path to be +explanatory documentation, and **a mixed commit forfeits it**. So the doc edits either land in +their own docs-only commit or ride with the prompt change and take full Gate B. **The plan chooses +and states which.** + +**What the template edit reaches.** It changes what `/workflow-init` writes into **new or +re-initialized** projects. It does **not** update a downstream project's existing `CLAUDE.md` — +invariant 9 forbids silent overwriting. Adoption is by re-running the scaffolder. + +**Packaging.** Editing the template triggers invariant 12: a `plugin.json` **version bump**, which +`scripts/check-version-bump.sh` enforces on pull requests. A **`CHANGELOG.md` entry** is also in the +implementation surface, as this repo's convention for every manifest version — **but nothing +enforces it**: neither invariant 12 nor the checker mentions the changelog, and the checker verifies +only that the version string differs from the base ref's. Saying CI enforces both would be a claim +about a gate that does not make it. + +--- + +## 8. Evidence plan + +Mode `battery+check+verification` (no `+abuse-path`; security is `none`). + +- **Battery** — the full `AGENTS.md` § Commands chain, green. +- **A check that fails without the change.** No automated test is possible for prose, so this takes + §5's other permitted route — a **named verification** owing the same counterfactual. Posed as a + question about behaviour under floor 1, **the pre-change text answers wrongly at identified + sites** and the post-change text answers correctly. **Both revisions get read**: a verification + consulting only the post-change text cannot fail and would report success because of how it was + wired. +- **A named verification of the risk path** — that the floor is derived by the agent and nothing + mechanical checks it. It recomputes each provenance line's floor from the cited stories' headers; + the observation that would exist if the claim were false is a line whose number the profiles do + not license. It also confirms **no floor file is present at close where none was present at + start**, which detects a *persisting* write and **cannot** detect a transient one. +- **A conditional verification that a user's knob survives untouched** — byte-identical across a + cycle where one exists; **recorded not-applicable with its reason where none does**, since + creating one would be a fixture supplying its own input. +- **A grammar check over both pinned grammars.** The branch's own instances cannot exercise them: + three lines cannot cover quoted paths, each unusable-knob cause, gapped pass ranges, split-model + passes, a skipped cycle, or the cardinality rule that `` matches ``. **The check + matches a set of constructed strings against each grammar's own productions with `grep -E` — + valid ones must match, invalid ones must not** — and records which grammar features each + exercises. **It is a grep plus field comparisons, not a parser**: these forms are pinned as + greppable field grammars and this repo ships no parser for either, so naming a parser would name + a check nobody can run. **A match decides the lexical productions and nothing else.** Every + constraint it cannot decide is checked by a comparison over the extracted fields: `` + having as many entries as `` enumerates; a `` occurring more than once; `` + ranges ascending and non-overlapping; a per-pass key present in one field and absent from its + partner. **That list is what reading both grammars for non-regular constraints produced, and it + is not proven complete** — a further one is checked the same way rather than folded into the + match. A grammar nothing was ever matched against is a format claim, not a format. +- **Parity across every changed rule**, in both copies, since they already diverge and parity + cannot be asserted from a whole-section diff. +- **A fresh twelve-item `docs/prompt-standards.md` pass.** Invariant 11 binds **each changed + prompt artifact as a complete prompt**, not the diff — so the items are read against **each changed prompt artifact invariant 11 names**: the resulting + **scaffolded template** and **`plugins/dev-workflow/commands/workflow-init.md`** as the outer + command prompt, which carries its own target-model declaration separate from the template it + writes. **Root `CLAUDE.md` is not in that list** — invariant 11 enumerates skills, commands, agent + definitions, hook messages and scaffolded templates — so this change does not run the checklist + against it and takes no position on whether it should be run; that question is untouched here, + which is why no item of it needs a disposition, with the changed regions as the + reason the pass is owed rather than its scope. Item 7's whole-artifact reading is the clearest + case, not the only one. + **Item 1 is inapplicable to the scaffolded `CLAUDE.md`, recorded as a reasoned n/a** (decided + 2026-08-29). The item asks a prompt to name its executing model. **The scaffolded file is + model-agnostic by design**: its executing model is whatever the end user runs, unknowable when + the template is written, so a `Target model:` line written into it would be **false in every + repo it lands in**. A standard cannot require writing a falsehood, and **an n/a recorded with its + reason answers the item rather than skipping it** — the pattern `AGENTS.md`'s own commands table + already uses (its typecheck row reads "n/a — no typed sources (shell + markdown)"). **This n/a is scoped to the scaffolded `CLAUDE.md` and to item 1**, and reaches nothing else: + every other item still binds that artifact, and every item still binds + `plugins/dev-workflow/commands/workflow-init.md` as the outer command prompt. **Root `CLAUDE.md` + is not part of this decision and is not being ruled on** — whether invariant 11 reaches project + `CLAUDE.md` files at all is a separate question this change neither raises nor answers. + **Nothing else moves for this:** `scripts/check-invariants.sh` is untouched and stays out of + scope, `workflow-init.md` keeps its single declaration for the outer command, and the template + adds none. **The implementation records the status where a future template editor meets it** — one + sentence in `workflow-init.md` beside the template and outside the fence, so it never scaffolds. + +- **This branch's own three closing bodies carry the final forms**, since two story criteria + require it. **No reconstruction is needed and none is claimed:** the Gate-A spec cycle has not + closed — it is still in Gate A as this is written — and the Gate-A plan cycle has not started, so + both write their closing bodies natively in the pinned forms. + **Which of this branch's cycles can supply a nonce natively follows from when the rules bind, and + §10 fixes that at the implementation commit — not at this spec's close.** So the Gate-A plan + cycle and the Gate-B cycle also begin before the rules ship, and all three are pre-rule cycles by + §10's own rule. **All three therefore write `cycle none (pre-rule)`**, and the branch demonstrates + every field of both forms except the two it cannot — the cycle identifier, which the first + post-ship cycle demonstrates, and the knob clause's non-absent form, which needs a workspace that + has a knob. Claiming + otherwise would be claiming a demonstration the branch cannot contain. + **The nonce is the one field this branch cannot supply natively, and the activation rule governs + it.** That cycle began before the nonce rule existed; its slot discriminator + is short and deterministic, so it is not a nonce, and minting one now would be late-created + provenance dressed as a cycle record. **A cycle already running finishes under the rules it + started with** (§10), so this one records its provenance line and curve with the cycle field written + `cycle none (pre-rule)`, which the grammar admits as a production. **The first cycle started after these rules ship carries a real + one**, and the verification confirms that rather than pretending this branch demonstrates it. + **The verification confirms all three bodies before closure**, and **records the two fields it + cannot demonstrate as undemonstrable-here with their reasons** rather than as gaps or as + satisfied — the same honesty as the item-1 n/a above. It states exactly what they prove: **every + field of both pinned forms except two** — the **cycle identifier**, which no cycle + on this branch can supply, and the **knob clause's non-absent form**, which cannot be shown where + no user knob exists (the conditional verification above records that as not-applicable rather + than as satisfied). **The nonce is verified by an obligation placed on a future cycle, not by naming one in advance.** + Selecting "the next" cycle cannot work: concurrent siblings have no total ordering, and a cycle + running after a revert would be under the restored rules and owe no nonce at all. Instead the + implementation commit **records the obligation**, and **any cycle that both starts under these + rules and closes discharges it** — its provenance line and curve must carry a real nonce, the + same one in both, distinct from any cycle open when it was generated. Whoever closes such a cycle + performs the check and records the discharge. + **"Any", deliberately, not "the first":** concurrent cycles share no ordering and no atomic + discharge state, so two closers could each see no prior discharge or each defer to the other. + **A duplicate discharge is harmless and explicitly allowed** — the obligation is satisfied by the + first record of it that exists, and a second changes nothing. A rule needing exactly one + discharger would need coordination this design does not have. That cycle's provenance line and curve must carry a real nonce, + the two must carry the **same** one, and it must differ from that of **any cycle open at the time + it was generated** — which is the uniqueness the rule actually requires, and all it can check. Recording that as a checkpoint rather than as a satisfied criterion + is the difference between a demonstration and a claim. + +**Revalidation.** §5 requires it before every re-review and before the closing amend. Verifications +reading closing commits are produced against the `WIP:` snapshot and **re-read against the content +the close will carry** — the amend *is* the closing act. **What that establishes is bounded**: the +index can change between the read and the commit. + +--- + +## 9. Out of scope + +Hook code, including the reminder's own wording; gate-call observability; the pass-counter anomaly; +the CodeRabbit plan-metadata contradiction; the fixture-per-predicate question; any remedy to the +supersession convention; deprecating or repurposing the user-facing floor knob; teaching the hook +about profiles; the §5 loop-rule consolidation and everything its successor story owns; and general reconciliation of the two copies' divergence, +which this change does not touch at all (§7 withdrew the seam an earlier revision claimed to need). + +--- + +## 10. Risks and activation + +- **When these rules bind.** From the commit that ships them, and **a cycle already running + finishes under the rules it started with**. **Where a cycle's starting rules cannot be + established it takes the stricter reading of every part this change touches** — floor 3, severity + classified without the demotion, **the provenance-line duty owed**, the curve duty owed, and the + nonce duties at their strictest — five parts, matching the five this change touches. Not + a re-derivation, which could hand a level-0 cycle a floor of 1 and *skip* passes on the strength + of not knowing when it started. A user knob set above 3 is not lowered by this fallback. *(The + successor extends this list to the rules it ships; extending is safe, replacing is not.)* +- **A revert is itself a shipping commit for the old rules.** **The activation rule wins where the + start is determinable**; the fallback covers only where it is not. Without that precedence a + revert makes every in-flight cycle ambiguous. +- **Downstream has no shipping commit.** Adoption binds from the `/workflow-init` run that + **actually writes** the text, which invariant 9 permits to write nothing, be declined, or be + merged in part. **The rules bind only over the text a project's `CLAUDE.md` contains**, and a + partial adoption can persist undetected. **This is in tension with §1's coupling argument and the + tension is real**: a project taking the floor rule without the severity test gets a floor whose + `docs-only` question §1 says the severity test settles. What prompt text can do is done; what it + cannot is said. +- **The gate-off surface — routes known today, not a complete list**, since an enumeration read as + complete guarantees what it omits. **One route is created here**: a stated floor the cited set + does not license, which could not exist before there was a derived floor to state. Pre-existing + and unchanged: omitting a higher-risk cited story, minting or editing a profile to level 0, + presenting an incomplete set, falsifying evidence entries, silencing reminders, or not running a + pass and reporting that it ran. **None of this is a guard.** +- **A user-set floor is not the gate-off lever**: it moves what the hook says. The lever is a + *stated* floor the profiles do not license. +- **The reachability test needs judgement** where §5 is trying to remove it; §3 says so. +- **The curve is self-reported**; P8 inherits that limit. +- **The expected demotion is a prediction, and it is not one this design makes checkable.** P8 + reads a **self-reported curve** nothing validates against the findings files, and those curves + come from **cycles reviewing different artifacts**, so a difference between them is evidence + about the population as much as about the rule. **What P8 can report is a change in recorded + severity mixes, with both limits named.** A demotion figure would need one finding classified + under both rules, which nothing here produces. If the recorded mixes shift far less than hoped, + the rule may still be correct and the economics claim was what was wrong — and that inference is + the reader's to make with the confound in view, not P8's to assert. diff --git a/docs/superpowers/specs/2026-08-30-dark-factory-vision.excalidraw b/docs/superpowers/specs/2026-08-30-dark-factory-vision.excalidraw new file mode 100644 index 0000000..aa68110 --- /dev/null +++ b/docs/superpowers/specs/2026-08-30-dark-factory-vision.excalidraw @@ -0,0 +1 @@ +{"type": "excalidraw", "version": 2, "source": "https://excalidraw.com", "appState": {"viewBackgroundColor": "#ffffff", "gridSize": 20}, "files": {}, "elements": [{"type": "text", "id": "hd_title", "x": 140, "y": 30, "width": 420, "height": 35, "text": "Dark Factory · Landkarte", "originalText": "Dark Factory · Landkarte", "fontSize": 28, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#1e40af", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 101, "version": 1, "versionNonce": 102, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "hd_sub", "x": 140, "y": 80, "width": 900, "height": 34, "text": "Die Straße läuft von links nach rechts. Violett arbeitet ein Modell, Blau Mechanik, Orange bist du, Grün ist das Ziel.\nGestrichelt = fehlt noch · Ellipsen = Loops (iterieren bis clean) · dick = Hauptstraße · rot = Ausfahrt zu dir · orange = Audit-Wege & Rückführ-Spur", "originalText": "Die Straße läuft von links nach rechts. Violett arbeitet ein Modell, Blau Mechanik, Orange bist du, Grün ist das Ziel.\nGestrichelt = fehlt noch · Ellipsen = Loops (iterieren bis clean) · dick = Hauptstraße · rot = Ausfahrt zu dir · orange = Audit-Wege & Rückführ-Spur", "fontSize": 13, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 103, "version": 1, "versionNonce": 104, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "hd_summary", "x": 140, "y": 136, "width": 880, "height": 18, "text": "Auf einen Blick: Einwurf → Karte → dein Go → Takt → bauen & prüfen (×lanes) → Stichprobe → main", "originalText": "Auf einen Blick: Einwurf → Karte → dein Go → Takt → bauen & prüfen (×lanes) → Stichprobe → main", "fontSize": 14, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#1e40af", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 105, "version": 1, "versionNonce": 106, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "rectangle", "id": "z_intake", "x": 380, "y": 245, "width": 900, "height": 170, "strokeColor": "#94a3b8", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 111, "version": 1, "versionNonce": 112, "isDeleted": false, "groupIds": [], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "z_intake_t", "x": 396, "y": 253, "width": 300, "height": 18, "text": "① INTAKE — Karte statt Produktion", "originalText": "① INTAKE — Karte statt Produktion", "fontSize": 13, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#1e40af", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 113, "version": 1, "versionNonce": 114, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "rectangle", "id": "z_prod", "x": 1670, "y": 445, "width": 1550, "height": 560, "strokeColor": "#94a3b8", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 115, "version": 1, "versionNonce": 116, "isDeleted": false, "groupIds": [], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "z_prod_t", "x": 1686, "y": 453, "width": 560, "height": 18, "text": "② PRODUKTION — eine Lane pro Ast (×lanes) · Reviewer nie der Autor", "originalText": "② PRODUKTION — eine Lane pro Ast (×lanes) · Reviewer nie der Autor", "fontSize": 13, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#1e40af", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 117, "version": 1, "versionNonce": 118, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "rectangle", "id": "z_abn", "x": 3290, "y": 445, "width": 900, "height": 300, "strokeColor": "#94a3b8", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 119, "version": 1, "versionNonce": 120, "isDeleted": false, "groupIds": [], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "z_abn_t", "x": 3306, "y": 453, "width": 300, "height": 18, "text": "③ ABNAHME — Stichprobe & Smoke", "originalText": "③ ABNAHME — Stichprobe & Smoke", "fontSize": 13, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#1e40af", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 121, "version": 1, "versionNonce": 122, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "rectangle", "id": "z_mon", "x": 1670, "y": 1080, "width": 1540, "height": 220, "strokeColor": "#94a3b8", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 123, "version": 1, "versionNonce": 124, "isDeleted": false, "groupIds": [], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "z_mon_t", "x": 1686, "y": 1088, "width": 520, "height": 18, "text": "④ ÜBERWACHUNG — beobachtet & meldet, fixt nie selbst", "originalText": "④ ÜBERWACHUNG — beobachtet & meldet, fixt nie selbst", "fontSize": 13, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#1e40af", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 125, "version": 1, "versionNonce": 126, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "ellipse", "id": "st_in", "x": 75, "y": 510, "width": 150, "height": 100, "strokeColor": "#c2410c", "backgroundColor": "#fed7aa", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 201, "version": 1, "versionNonce": 202, "isDeleted": false, "groupIds": ["g_in"], "boundElements": [{"id": "r_1", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_in", "x": 115, "y": 551, "width": 70, "height": 18, "text": "Einwurf", "originalText": "Einwurf", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 203, "version": 1, "versionNonce": 204, "isDeleted": false, "groupIds": ["g_in"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_in", "x": 50, "y": 625, "width": 200, "height": 30, "text": "Ideen · Stories · Bugs\njederzeit, folgenlos", "originalText": "Ideen · Stories · Bugs\njederzeit, folgenlos", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 205, "version": 1, "versionNonce": 206, "isDeleted": false, "groupIds": ["g_in"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_pool", "x": 405, "y": 525, "width": 170, "height": 70, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 211, "version": 1, "versionNonce": 212, "isDeleted": false, "groupIds": ["g_pool"], "boundElements": [{"id": "r_1", "type": "arrow"}, {"id": "r_2", "type": "arrow"}], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_pool", "x": 442, "y": 551, "width": 96, "height": 18, "text": "Story-Pool", "originalText": "Story-Pool", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 213, "version": 1, "versionNonce": 214, "isDeleted": false, "groupIds": ["g_pool"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_pool", "x": 385, "y": 625, "width": 210, "height": 30, "text": "einzige Wahrheit · Status-Spalte\nlanes-Regler: deiner", "originalText": "einzige Wahrheit · Status-Spalte\nlanes-Regler: deiner", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 215, "version": 1, "versionNonce": 216, "isDeleted": false, "groupIds": ["g_pool"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_frei", "x": 745, "y": 525, "width": 170, "height": 70, "strokeColor": "#c2410c", "backgroundColor": "#fed7aa", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 221, "version": 1, "versionNonce": 222, "isDeleted": false, "groupIds": ["g_frei"], "boundElements": [{"id": "r_2", "type": "arrow"}, {"id": "r_3", "type": "arrow"}], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_frei", "x": 791, "y": 551, "width": 78, "height": 18, "text": "Freigabe", "originalText": "Freigabe", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 223, "version": 1, "versionNonce": 224, "isDeleted": false, "groupIds": ["g_frei"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_frei", "x": 715, "y": 625, "width": 230, "height": 30, "text": "dein „Go“ auf den Wellen-Plan\nRegler: Story → Welle → Standing-Auto", "originalText": "dein „Go“ auf den Wellen-Plan\nRegler: Story → Welle → Standing-Auto", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 225, "version": 1, "versionNonce": 226, "isDeleted": false, "groupIds": ["g_frei"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_takt", "x": 1060, "y": 515, "width": 140, "height": 95, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 231, "version": 1, "versionNonce": 232, "isDeleted": false, "groupIds": ["g_takt"], "boundElements": [{"id": "r_3", "type": "arrow"}, {"id": "r_4", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_takt", "x": 1087, "y": 553, "width": 86, "height": 18, "text": "Takt-Loop", "originalText": "Takt-Loop", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 233, "version": 1, "versionNonce": 234, "isDeleted": false, "groupIds": ["g_takt"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_takt", "x": 1078, "y": 576, "width": 104, "height": 30, "text": "die Uhr — Loop", "originalText": "die Uhr — Loop", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 235, "version": 1, "versionNonce": 236, "isDeleted": false, "groupIds": ["g_takt"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_orch", "x": 1370, "y": 505, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 241, "version": 1, "versionNonce": 242, "isDeleted": false, "groupIds": ["g_orch"], "boundElements": [{"id": "r_4", "type": "arrow"}, {"id": "r_5", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_orch", "x": 1392, "y": 551, "width": 116, "height": 18, "text": "Orchestrator", "originalText": "Orchestrator", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 243, "version": 1, "versionNonce": 244, "isDeleted": false, "groupIds": ["g_orch"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_orch", "x": 1385, "y": 574, "width": 130, "height": 30, "text": "plant · öffnet Lanes\npro Tick frisch", "originalText": "plant · öffnet Lanes\npro Tick frisch", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 245, "version": 1, "versionNonce": 246, "isDeleted": false, "groupIds": ["g_orch"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_spec", "x": 1710, "y": 505, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 251, "version": 1, "versionNonce": 252, "isDeleted": false, "groupIds": ["g_spec"], "boundElements": [{"id": "r_5", "type": "arrow"}, {"id": "r_6", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_spec", "x": 1747, "y": 551, "width": 86, "height": 18, "text": "Spec-Loop", "originalText": "Spec-Loop", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 253, "version": 1, "versionNonce": 254, "isDeleted": false, "groupIds": ["g_spec"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_spec", "x": 1725, "y": 580, "width": 130, "height": 16, "text": "Design + Gate A", "originalText": "Design + Gate A", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 255, "version": 1, "versionNonce": 256, "isDeleted": false, "groupIds": ["g_spec"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_plan", "x": 2030, "y": 505, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 261, "version": 1, "versionNonce": 262, "isDeleted": false, "groupIds": ["g_plan"], "boundElements": [{"id": "r_6", "type": "arrow"}, {"id": "r_7", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_plan", "x": 2067, "y": 551, "width": 86, "height": 18, "text": "Plan-Loop", "originalText": "Plan-Loop", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 263, "version": 1, "versionNonce": 264, "isDeleted": false, "groupIds": ["g_plan"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_plan", "x": 2045, "y": 580, "width": 130, "height": 16, "text": "Plan + Gate A", "originalText": "Plan + Gate A", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 265, "version": 1, "versionNonce": 266, "isDeleted": false, "groupIds": ["g_plan"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_bau", "x": 2350, "y": 505, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 271, "version": 1, "versionNonce": 272, "isDeleted": false, "groupIds": ["g_bau"], "boundElements": [{"id": "r_7", "type": "arrow"}, {"id": "r_8", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_bau", "x": 2394, "y": 551, "width": 72, "height": 18, "text": "Bau-Loop", "originalText": "Bau-Loop", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 273, "version": 1, "versionNonce": 274, "isDeleted": false, "groupIds": ["g_bau"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_bau", "x": 2365, "y": 574, "width": 130, "height": 30, "text": "TDD rot bis grün\nAC read-only", "originalText": "TDD rot bis grün\nAC read-only", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 275, "version": 1, "versionNonce": 276, "isDeleted": false, "groupIds": ["g_bau"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_verify", "x": 2670, "y": 505, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 281, "version": 1, "versionNonce": 282, "isDeleted": false, "groupIds": ["g_verify"], "boundElements": [{"id": "r_8", "type": "arrow"}, {"id": "r_9", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_verify", "x": 2721, "y": 551, "width": 58, "height": 18, "text": "Verify", "originalText": "Verify", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 283, "version": 1, "versionNonce": 284, "isDeleted": false, "groupIds": ["g_verify"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_verify", "x": 2685, "y": 574, "width": 130, "height": 30, "text": "Battery + Gate B\nnie der Autor", "originalText": "Battery + Gate B\nnie der Autor", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 285, "version": 1, "versionNonce": 286, "isDeleted": false, "groupIds": ["g_verify"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_pr", "x": 2985, "y": 525, "width": 170, "height": 70, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 291, "version": 1, "versionNonce": 292, "isDeleted": false, "groupIds": ["g_pr"], "boundElements": [{"id": "r_9", "type": "arrow"}, {"id": "r_10", "type": "arrow"}], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_pr", "x": 3056, "y": 551, "width": 28, "height": 18, "text": "PR", "originalText": "PR", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 293, "version": 1, "versionNonce": 294, "isDeleted": false, "groupIds": ["g_pr"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_pr", "x": 2960, "y": 625, "width": 220, "height": 30, "text": "Bots: opportunistisch\nnichts blockiert", "originalText": "Bots: opportunistisch\nnichts blockiert", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 295, "version": 1, "versionNonce": 296, "isDeleted": false, "groupIds": ["g_pr"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "diamond", "id": "st_sample", "x": 3325, "y": 495, "width": 170, "height": 130, "strokeColor": "#b45309", "backgroundColor": "#fef3c7", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 301, "version": 1, "versionNonce": 302, "isDeleted": false, "groupIds": ["g_sample"], "boundElements": [{"id": "r_10", "type": "arrow"}, {"id": "r_11", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_sample", "x": 3360, "y": 551, "width": 100, "height": 18, "text": "Stichprobe?", "originalText": "Stichprobe?", "fontSize": 14, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 303, "version": 1, "versionNonce": 304, "isDeleted": false, "groupIds": ["g_sample"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_sample", "x": 3300, "y": 640, "width": 220, "height": 30, "text": "Sample-Gate: zieht x %\nArchitektur-Merges: immer", "originalText": "Sample-Gate: zieht x %\nArchitektur-Merges: immer", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 305, "version": 1, "versionNonce": 306, "isDeleted": false, "groupIds": ["g_sample"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_mq", "x": 3665, "y": 525, "width": 170, "height": 70, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 311, "version": 1, "versionNonce": 312, "isDeleted": false, "groupIds": ["g_mq"], "boundElements": [{"id": "r_11", "type": "arrow"}, {"id": "r_12", "type": "arrow"}], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_mq", "x": 3696, "y": 551, "width": 108, "height": 18, "text": "Merge-Queue", "originalText": "Merge-Queue", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 313, "version": 1, "versionNonce": 314, "isDeleted": false, "groupIds": ["g_mq"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_mq", "x": 3640, "y": 625, "width": 220, "height": 30, "text": "seriell: rebase → Smoke\nnur Grünes landet", "originalText": "seriell: rebase → Smoke\nnur Grünes landet", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 315, "version": 1, "versionNonce": 316, "isDeleted": false, "groupIds": ["g_mq"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_main", "x": 4015, "y": 510, "width": 150, "height": 100, "strokeColor": "#047857", "backgroundColor": "#a7f3d0", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 321, "version": 1, "versionNonce": 322, "isDeleted": false, "groupIds": ["g_main"], "boundElements": [{"id": "r_12", "type": "arrow"}, {"id": "r_exit", "type": "arrow"}], "link": null, "locked": false}, {"type": "text", "id": "t_main", "x": 4070, "y": 551, "width": 40, "height": 18, "text": "main", "originalText": "main", "fontSize": 15, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 323, "version": 1, "versionNonce": 324, "isDeleted": false, "groupIds": ["g_main"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "text", "id": "s_main", "x": 4010, "y": 625, "width": 160, "height": 16, "text": "Fabrik endet hier", "originalText": "Fabrik endet hier", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 325, "version": 1, "versionNonce": 326, "isDeleted": false, "groupIds": ["g_main"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "r_1", "x": 229, "y": 560, "width": 172, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 401, "version": 1, "versionNonce": 402, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [172, 0]], "startBinding": {"elementId": "st_in", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_pool", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "arrow", "id": "r_2", "x": 579, "y": 560, "width": 162, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 403, "version": 1, "versionNonce": 404, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [162, 0]], "startBinding": {"elementId": "st_pool", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_frei", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_2", "x": 590, "y": 536, "width": 140, "height": 15, "text": "klassifiziert", "originalText": "klassifiziert", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 405, "version": 1, "versionNonce": 406, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_3", "x": 919, "y": 560, "width": 137, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 407, "version": 1, "versionNonce": 408, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [137, 0]], "startBinding": {"elementId": "st_frei", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_takt", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_3", "x": 920, "y": 536, "width": 135, "height": 15, "text": "freigegeben", "originalText": "freigegeben", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 409, "version": 1, "versionNonce": 410, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_4", "x": 1204, "y": 560, "width": 162, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 411, "version": 1, "versionNonce": 412, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [162, 0]], "startBinding": {"elementId": "st_takt", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_orch", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_4", "x": 1230, "y": 536, "width": 110, "height": 15, "text": "weckt", "originalText": "weckt", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 413, "version": 1, "versionNonce": 414, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_5", "x": 1534, "y": 560, "width": 172, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 415, "version": 1, "versionNonce": 416, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [172, 0]], "startBinding": {"elementId": "st_orch", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_spec", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_5", "x": 1540, "y": 536, "width": 160, "height": 15, "text": "Story + Karte", "originalText": "Story + Karte", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 417, "version": 1, "versionNonce": 418, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_6", "x": 1874, "y": 560, "width": 152, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 419, "version": 1, "versionNonce": 420, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [152, 0]], "startBinding": {"elementId": "st_spec", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_plan", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_6", "x": 1910, "y": 536, "width": 80, "height": 15, "text": "Spec", "originalText": "Spec", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 421, "version": 1, "versionNonce": 422, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_7", "x": 2194, "y": 560, "width": 152, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 423, "version": 1, "versionNonce": 424, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [152, 0]], "startBinding": {"elementId": "st_plan", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_bau", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_7", "x": 2230, "y": 536, "width": 80, "height": 15, "text": "Plan", "originalText": "Plan", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 425, "version": 1, "versionNonce": 426, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_8", "x": 2514, "y": 560, "width": 152, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 427, "version": 1, "versionNonce": 428, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [152, 0]], "startBinding": {"elementId": "st_bau", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_verify", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_8", "x": 2550, "y": 536, "width": 80, "height": 15, "text": "Diff", "originalText": "Diff", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 429, "version": 1, "versionNonce": 430, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_9", "x": 2834, "y": 560, "width": 147, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 431, "version": 1, "versionNonce": 432, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [147, 0]], "startBinding": {"elementId": "st_verify", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_pr", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_9", "x": 2870, "y": 536, "width": 80, "height": 15, "text": "grün", "originalText": "grün", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 433, "version": 1, "versionNonce": 434, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_10", "x": 3159, "y": 560, "width": 162, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 435, "version": 1, "versionNonce": 436, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [162, 0]], "startBinding": {"elementId": "st_pr", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_sample", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "arrow", "id": "r_11", "x": 3499, "y": 560, "width": 162, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 439, "version": 1, "versionNonce": 440, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [162, 0]], "startBinding": {"elementId": "st_sample", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_mq", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_11", "x": 3495, "y": 536, "width": 170, "height": 15, "text": "nicht gezogen", "originalText": "nicht gezogen", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 441, "version": 1, "versionNonce": 442, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_12", "x": 3839, "y": 560, "width": 172, "height": 0, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 443, "version": 1, "versionNonce": 444, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [172, 0]], "startBinding": {"elementId": "st_mq", "focus": 0, "gap": 4}, "endBinding": {"elementId": "st_main", "focus": 0, "gap": 4}, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_12", "x": 3850, "y": 536, "width": 150, "height": 15, "text": "Smoke grün", "originalText": "Smoke grün", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 445, "version": 1, "versionNonce": 446, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "arrow", "id": "r_exit", "x": 4169, "y": 560, "width": 170, "height": 0, "strokeColor": "#047857", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 3, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 447, "version": 1, "versionNonce": 448, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [170, 0]], "startBinding": {"elementId": "st_main", "focus": 0, "gap": 4}, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow"}, {"type": "text", "id": "rl_exit", "x": 4180, "y": 536, "width": 170, "height": 15, "text": "→ Projekt-CI", "originalText": "→ Projekt-CI", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#047857", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 449, "version": 1, "versionNonce": 450, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.25}, {"type": "ellipse", "id": "st_intake", "x": 410, "y": 275, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9002, "version": 1, "versionNonce": 9003, "isDeleted": false, "groupIds": ["g_intake"], "boundElements": [], "link": null, "locked": false}, {"type": "text", "id": "t_intake", "x": 442, "y": 314, "width": 96, "height": 15, "text": "Intake-Loop", "originalText": "Intake-Loop", "fontSize": 14, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9004, "version": 1, "versionNonce": 9005, "isDeleted": false, "groupIds": ["g_intake"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_intake", "x": 400, "y": 391, "width": 180, "height": 15, "text": "Karte: 8 Dimensionen · Batch", "originalText": "Karte: 8 Dimensionen · Batch", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9006, "version": 1, "versionNonce": 9007, "isDeleted": false, "groupIds": ["g_intake"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "ellipse", "id": "st_bew", "x": 750, "y": 275, "width": 160, "height": 110, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9008, "version": 1, "versionNonce": 9009, "isDeleted": false, "groupIds": ["g_bew"], "boundElements": [], "link": null, "locked": false}, {"type": "text", "id": "t_bew", "x": 765, "y": 314, "width": 130, "height": 15, "text": "Bewertungs-Loop", "originalText": "Bewertungs-Loop", "fontSize": 12.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9010, "version": 1, "versionNonce": 9011, "isDeleted": false, "groupIds": ["g_bew"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_bew", "x": 730, "y": 391, "width": 200, "height": 15, "text": "Architektur-Urteile · Meta-Stories", "originalText": "Architektur-Urteile · Meta-Stories", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9012, "version": 1, "versionNonce": 9013, "isDeleted": false, "groupIds": ["g_bew"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_arch", "x": 1085, "y": 295, "width": 170, "height": 70, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9014, "version": 1, "versionNonce": 9015, "isDeleted": false, "groupIds": ["g_arch"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_arch", "x": 1117, "y": 321, "width": 106, "height": 15, "text": "Architektur", "originalText": "Architektur", "fontSize": 14, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9016, "version": 1, "versionNonce": 9017, "isDeleted": false, "groupIds": ["g_arch"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_arch", "x": 1080, "y": 375, "width": 180, "height": 15, "text": "AGENTS.md: Baum · Ziele", "originalText": "AGENTS.md: Baum · Ziele", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9018, "version": 1, "versionNonce": 9019, "isDeleted": false, "groupIds": ["g_arch"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_ph0", "x": 1085, "y": 150, "width": 170, "height": 60, "strokeColor": "#c2410c", "backgroundColor": "#fed7aa", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9020, "version": 1, "versionNonce": 9021, "isDeleted": false, "groupIds": [], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_ph0", "x": 1136, "y": 171, "width": 68, "height": 15, "text": "Phase 0", "originalText": "Phase 0", "fontSize": 14, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9022, "version": 1, "versionNonce": 9023, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_ph0", "x": 1270, "y": 152, "width": 210, "height": 45, "text": "einmalig: Baum v1 + Ziele\nGreenfield aus dem Pool,\nBestand aus dem Code", "originalText": "einmalig: Baum v1 + Ziele\nGreenfield aus dem Pool,\nBestand aus dem Code", "fontSize": 10.5, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9024, "version": 1, "versionNonce": 9025, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_pool_intake", "x": 455, "y": 520, "width": 0, "height": 128, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9026, "version": 1, "versionNonce": 9027, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, -128]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "a_intake_pool", "x": 525, "y": 392, "width": 0, "height": 128, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9028, "version": 1, "versionNonce": 9029, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, 128]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_newstory", "x": 330, "y": 448, "width": 110, "height": 15, "text": "neue Story", "originalText": "neue Story", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9030, "version": 1, "versionNonce": 9031, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "l_karte", "x": 543, "y": 448, "width": 60, "height": 15, "text": "Karte", "originalText": "Karte", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9032, "version": 1, "versionNonce": 9033, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_intake_bew", "x": 573, "y": 318, "width": 174, "height": 0, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9034, "version": 1, "versionNonce": 9035, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [174, 0]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "a_bew_intake", "x": 747, "y": 346, "width": 174, "height": 0, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9036, "version": 1, "versionNonce": 9037, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [-174, 0]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_urteil", "x": 590, "y": 296, "width": 140, "height": 15, "text": "fragt Urteil an", "originalText": "fragt Urteil an", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9038, "version": 1, "versionNonce": 9039, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "l_urteil2", "x": 585, "y": 352, "width": 150, "height": 15, "text": "Architektur-Urteil", "originalText": "Architektur-Urteil", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9040, "version": 1, "versionNonce": 9041, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_bew_arch", "x": 912, "y": 330, "width": 171, "height": 0, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9042, "version": 1, "versionNonce": 9043, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [171, 0]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_pflegt", "x": 915, "y": 306, "width": 170, "height": 15, "text": "liest & pflegt Baum", "originalText": "liest & pflegt Baum", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9044, "version": 1, "versionNonce": 9045, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_bew_pool", "x": 790, "y": 388, "width": 225, "height": 134, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9046, "version": 1, "versionNonce": 9047, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [-225, 134]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_meta", "x": 618, "y": 470, "width": 190, "height": 15, "text": "Meta-Stories in den Pool", "originalText": "Meta-Stories in den Pool", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9048, "version": 1, "versionNonce": 9049, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_ph0_arch", "x": 1170, "y": 212, "width": 0, "height": 80, "strokeColor": "#c2410c", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9050, "version": 1, "versionNonce": 9051, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, 80]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_baumv1", "x": 1185, "y": 240, "width": 70, "height": 15, "text": "Baum v1", "originalText": "Baum v1", "fontSize": 10.5, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9052, "version": 1, "versionNonce": 9053, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_views", "x": 405, "y": 790, "width": 200, "height": 70, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9054, "version": 1, "versionNonce": 9055, "isDeleted": false, "groupIds": ["g_views"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_views", "x": 430, "y": 815, "width": 150, "height": 15, "text": "Views · gerechnet", "originalText": "Views · gerechnet", "fontSize": 13, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9056, "version": 1, "versionNonce": 9057, "isDeleted": false, "groupIds": ["g_views"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_views", "x": 330, "y": 870, "width": 350, "height": 30, "text": "Roadmap · Wellen-Plan · Dashboard · As-built\naus dem Pool gerechnet — nie gepflegt", "originalText": "Roadmap · Wellen-Plan · Dashboard · As-built\naus dem Pool gerechnet — nie gepflegt", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9058, "version": 1, "versionNonce": 9059, "isDeleted": false, "groupIds": ["g_views"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_pool_views", "x": 480, "y": 597, "width": 0, "height": 191, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9060, "version": 1, "versionNonce": 9061, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, 191]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "a_views_frei", "x": 607, "y": 808, "width": 193, "height": 210, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9062, "version": 1, "versionNonce": 9063, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [193, -210]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_wplan", "x": 655, "y": 712, "width": 110, "height": 15, "text": "Wellen-Plan", "originalText": "Wellen-Plan", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9064, "version": 1, "versionNonce": 9065, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_audit", "x": 3495, "y": 270, "width": 170, "height": 70, "strokeColor": "#c2410c", "backgroundColor": "#fed7aa", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9066, "version": 1, "versionNonce": 9067, "isDeleted": false, "groupIds": ["g_audit"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_audit", "x": 3552, "y": 296, "width": 56, "height": 15, "text": "Audit", "originalText": "Audit", "fontSize": 14, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9068, "version": 1, "versionNonce": 9069, "isDeleted": false, "groupIds": ["g_audit"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_audit", "x": 3680, "y": 278, "width": 220, "height": 30, "text": "du, mit Punchlist\n„Nein“ = immer wieder du", "originalText": "du, mit Punchlist\n„Nein“ = immer wieder du", "fontSize": 10.5, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9070, "version": 1, "versionNonce": 9071, "isDeleted": false, "groupIds": ["g_audit"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_sample_audit", "x": 3425, "y": 505, "width": 120, "height": 161, "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9072, "version": 1, "versionNonce": 9073, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [120, -161]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_gezogen", "x": 3400, "y": 412, "width": 80, "height": 15, "text": "gezogen", "originalText": "gezogen", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9074, "version": 1, "versionNonce": 9075, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_audit_mq", "x": 3667, "y": 325, "width": 90, "height": 197, "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9076, "version": 1, "versionNonce": 9077, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [90, 197]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_ja", "x": 3735, "y": 410, "width": 50, "height": 15, "text": "„Ja“", "originalText": "„Ja“", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9078, "version": 1, "versionNonce": 9079, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_audit_nein", "x": 3493, "y": 300, "width": 1063, "height": 200, "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9080, "version": 1, "versionNonce": 9081, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [-1063, 0], [-1063, 200]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_nein", "x": 2760, "y": 280, "width": 340, "height": 15, "text": "„Nein“ → Punchlist zurück in die Lane", "originalText": "„Nein“ → Punchlist zurück in die Lane", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9082, "version": 1, "versionNonce": 9083, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "ret_verify_bau", "x": 2740, "y": 613, "width": 300, "height": 87, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9084, "version": 1, "versionNonce": 9085, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, 87], [-300, 87], [-300, 0]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_findings", "x": 2520, "y": 706, "width": 150, "height": 15, "text": "Findings zurück", "originalText": "Findings zurück", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9086, "version": 1, "versionNonce": 9087, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "ret_mq_verify", "x": 3650, "y": 597, "width": 888, "height": 163, "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9088, "version": 1, "versionNonce": 9089, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, 163], [-888, 163], [-888, 17]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_smokerot", "x": 3130, "y": 766, "width": 280, "height": 15, "text": "Smoke rot → zurück in die Lane", "originalText": "Smoke rot → zurück in die Lane", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#1e3a5f", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9090, "version": 1, "versionNonce": 9091, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_lane2", "x": 1720, "y": 830, "width": 300, "height": 54, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9092, "version": 1, "versionNonce": 9093, "isDeleted": false, "groupIds": ["g_l2"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_lane2", "x": 1740, "y": 849, "width": 262, "height": 15, "text": "Lane 2 · Spec→Plan→Bau→Verify→PR", "originalText": "Lane 2 · Spec→Plan→Bau→Verify→PR", "fontSize": 11.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9094, "version": 1, "versionNonce": 9095, "isDeleted": false, "groupIds": ["g_l2"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_lane3", "x": 1720, "y": 904, "width": 300, "height": 54, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9096, "version": 1, "versionNonce": 9097, "isDeleted": false, "groupIds": ["g_l3"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_lane3", "x": 1745, "y": 923, "width": 250, "height": 15, "text": "Lane 3 … bis zum lanes-Budget", "originalText": "Lane 3 … bis zum lanes-Budget", "fontSize": 11.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9098, "version": 1, "versionNonce": 9099, "isDeleted": false, "groupIds": ["g_l3"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_orch_l2", "x": 1462, "y": 608, "width": 256, "height": 247, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9100, "version": 1, "versionNonce": 9101, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [256, 247]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "a_orch_l3", "x": 1450, "y": 613, "width": 268, "height": 316, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9102, "version": 1, "versionNonce": 9103, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [268, 316]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "a_l2_out", "x": 2022, "y": 857, "width": 1376, "height": 227, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9104, "version": 1, "versionNonce": 9105, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [1323, 0], [1376, -227]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "a_l3_out", "x": 2022, "y": 931, "width": 1399, "height": 293, "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9106, "version": 1, "versionNonce": 9107, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [1356, 0], [1399, -293]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_l2pr", "x": 3280, "y": 838, "width": 40, "height": 15, "text": "PR", "originalText": "PR", "fontSize": 10.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#6d28d9", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9108, "version": 1, "versionNonce": 9109, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_esk", "x": 2140, "y": 120, "width": 280, "height": 70, "strokeColor": "#dc2626", "backgroundColor": "#fee2e2", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9110, "version": 1, "versionNonce": 9111, "isDeleted": false, "groupIds": ["g_esk"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_esk", "x": 2185, "y": 146, "width": 190, "height": 15, "text": "Eskalation zu dir", "originalText": "Eskalation zu dir", "fontSize": 14, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9112, "version": 1, "versionNonce": 9113, "isDeleted": false, "groupIds": ["g_esk"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_esk", "x": 2440, "y": 128, "width": 300, "height": 30, "text": "Pflicht-Stop · Rückfrage · hängt\nstoppt & meldet, statt zu drehen", "originalText": "Pflicht-Stop · Rückfrage · hängt\nstoppt & meldet, statt zu drehen", "fontSize": 10.5, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9114, "version": 1, "versionNonce": 9115, "isDeleted": false, "groupIds": ["g_esk"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "a_esk", "x": 1480, "y": 502, "width": 710, "height": 307, "strokeColor": "#dc2626", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9116, "version": 1, "versionNonce": 9117, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [710, -307]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "rectangle", "id": "st_vet", "x": 1720, "y": 1130, "width": 170, "height": 54, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9118, "version": 1, "versionNonce": 9119, "isDeleted": false, "groupIds": ["g_st_vet"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_st_vet", "x": 1754, "y": 1149, "width": 102, "height": 15, "text": "Vet-Preflight", "originalText": "Vet-Preflight", "fontSize": 12.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9120, "version": 1, "versionNonce": 9121, "isDeleted": false, "groupIds": ["g_st_vet"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_st_vet", "x": 1705, "y": 1196, "width": 200, "height": 30, "text": "mechanisch, vor jedem\nModell-Knoten", "originalText": "mechanisch, vor jedem\nModell-Knoten", "fontSize": 10, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9122, "version": 1, "versionNonce": 9123, "isDeleted": false, "groupIds": ["g_st_vet"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_ana", "x": 1990, "y": 1130, "width": 170, "height": 54, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9124, "version": 1, "versionNonce": 9125, "isDeleted": false, "groupIds": ["g_st_ana"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_st_ana", "x": 2016, "y": 1149, "width": 118, "height": 15, "text": "Analytics+Trace", "originalText": "Analytics+Trace", "fontSize": 12.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9126, "version": 1, "versionNonce": 9127, "isDeleted": false, "groupIds": ["g_st_ana"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_st_ana", "x": 1975, "y": 1196, "width": 200, "height": 30, "text": "Kosten · Trace-ID\nspeist Sample-Schwellen", "originalText": "Kosten · Trace-ID\nspeist Sample-Schwellen", "fontSize": 10, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9128, "version": 1, "versionNonce": 9129, "isDeleted": false, "groupIds": ["g_st_ana"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_judge", "x": 2260, "y": 1130, "width": 170, "height": 54, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9130, "version": 1, "versionNonce": 9131, "isDeleted": false, "groupIds": ["g_st_judge"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_st_judge", "x": 2290, "y": 1149, "width": 110, "height": 15, "text": "Judge/Watchdog", "originalText": "Judge/Watchdog", "fontSize": 12.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9132, "version": 1, "versionNonce": 9133, "isDeleted": false, "groupIds": ["g_st_judge"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_st_judge", "x": 2245, "y": 1196, "width": 200, "height": 30, "text": "hängt? thrash? → rote\nAusfahrt zu dir", "originalText": "hängt? thrash? → rote\nAusfahrt zu dir", "fontSize": 10, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9134, "version": 1, "versionNonce": 9135, "isDeleted": false, "groupIds": ["g_st_judge"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_e2e", "x": 2530, "y": 1130, "width": 170, "height": 54, "strokeColor": "#1e3a5f", "backgroundColor": "#93c5fd", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9136, "version": 1, "versionNonce": 9137, "isDeleted": false, "groupIds": ["g_st_e2e"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_st_e2e", "x": 2584, "y": 1149, "width": 63, "height": 15, "text": "E2E-Loop", "originalText": "E2E-Loop", "fontSize": 12.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9138, "version": 1, "versionNonce": 9139, "isDeleted": false, "groupIds": ["g_st_e2e"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_st_e2e", "x": 2515, "y": 1196, "width": 200, "height": 30, "text": "nightly · Fehlschlag\nwird Story", "originalText": "nightly · Fehlschlag\nwird Story", "fontSize": 10, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9140, "version": 1, "versionNonce": 9141, "isDeleted": false, "groupIds": ["g_st_e2e"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "rectangle", "id": "st_drift", "x": 2800, "y": 1130, "width": 170, "height": 54, "strokeColor": "#6d28d9", "backgroundColor": "#ddd6fe", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9142, "version": 1, "versionNonce": 9143, "isDeleted": false, "groupIds": ["g_st_drift"], "boundElements": [], "link": null, "locked": false, "roundness": {"type": 3}}, {"type": "text", "id": "t_st_drift", "x": 2842, "y": 1149, "width": 86, "height": 15, "text": "Drift-Audit", "originalText": "Drift-Audit", "fontSize": 12.5, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#374151", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9144, "version": 1, "versionNonce": 9145, "isDeleted": false, "groupIds": ["g_st_drift"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "text", "id": "s_st_drift", "x": 2785, "y": 1196, "width": 200, "height": 15, "text": "Code↔Spec · meldet nur", "originalText": "Code↔Spec · meldet nur", "fontSize": 10, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#64748b", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9146, "version": 1, "versionNonce": 9147, "isDeleted": false, "groupIds": ["g_st_drift"], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "spur", "x": 2900, "y": 1040, "width": 2580, "height": 440, "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9148, "version": 1, "versionNonce": 9149, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [-2580, 0], [-2580, -400], [-2502, -440]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "text", "id": "l_spur", "x": 1050, "y": 1014, "width": 560, "height": 15, "text": "Rückführ-Spur: Fehlschläge & Funde werden neue Stories (mit Trace-ID)", "originalText": "Rückführ-Spur: Fehlschläge & Funde werden neue Stories (mit Trace-ID)", "fontSize": 11, "fontFamily": 3, "textAlign": "center", "verticalAlign": "top", "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9150, "version": 1, "versionNonce": 9151, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "containerId": null, "lineHeight": 1.3}, {"type": "arrow", "id": "feed_e2e", "x": 2615, "y": 1128, "width": 0, "height": 82, "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9152, "version": 1, "versionNonce": 9153, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, -82]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}, {"type": "arrow", "id": "feed_drift", "x": 2885, "y": 1128, "width": 0, "height": 82, "strokeColor": "#b45309", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "seed": 9154, "version": 1, "versionNonce": 9155, "isDeleted": false, "groupIds": [], "boundElements": null, "link": null, "locked": false, "points": [[0, 0], [0, -82]], "startBinding": null, "endBinding": null, "startArrowhead": null, "endArrowhead": "arrow", "roundness": null, "lastCommittedPoint": null}]} \ No newline at end of file diff --git a/docs/superpowers/specs/2026-08-30-dark-factory-vision.md b/docs/superpowers/specs/2026-08-30-dark-factory-vision.md new file mode 100644 index 0000000..843ecbc --- /dev/null +++ b/docs/superpowers/specs/2026-08-30-dark-factory-vision.md @@ -0,0 +1,872 @@ +# Dark Factory — target vision and decomposition + +**Date:** 2026-08-30 · **Kind:** decomposition document, not an implementation spec. +Each *leaf* build item below becomes its own story through the normal workflow +— the numbered entries in §7 are ordering groups, and where one carries +lettered leaves (2a, 4a…, 5a…, 6a…) those leaves are the story owners +(intake → spec → Gate A → plan → Gate A → execute → Gate B). Nothing in this +document is executable on its own, and nothing here overrides a shipped rule. + +Brainstormed 2026-08-30 (Daniel + sparring session); all decisions below are +Daniel's, taken in that session. Inspiration: the loop/graph-engineering framing +from an external video (four loop maturity stages; artifacts as the only handoff +between fresh-context nodes; a "dark factory" as a repository that ships its own +code, policed by an adversarial model with sampled human audit). + +## 1. The vision + +Stories and ideas flow into a pool. The factory turns approved pool items into +merged, reviewed software with the human concentrated where human judgement +measurably matters — and, at full maturity, sampled rather than omnipresent: + +``` +Story-Pool — filling never triggers production [missing] + → Intake-Loop (PROACTIVE: a new story appearing triggers + classification, debounced/batched; attaches the card, + triggers NO production) [missing] + ↳ reads the Architektur (AGENTS.md) [exists, needs one extension] + ↳ asks the Bewertungs-Loop for the architecture + verdict, and attaches the answer it returns [missing] + ⟳ Bewertungs-Loop (the single producer of every + architecture verdict: answers Intake's request, + continuously re-evaluates the tree against the pool, + produces the meta-stories of decision 3, and + re-classifies the cards an architecture merge made + stale) [missing] + → Freigabe (the ONLY production trigger; the human, or + a standing rule the human installed — decision 7; + reads a rendered wave plan) [human] + → Takt-Loop (polls freigegebene stories of the active + wave → wakes the orchestrator → a lane opens). The + orchestrator is itself a model-operated node here, + so the vet preflight, fresh context, artifact-only + handoff and the judge all cover it [missing] + → Spec-Loop (design + Gate A — the item was already + intaken and profiled at classification, so intake + does not run twice) [exists] + → Plan-Loop (writing-plans + Gate A) [exists] + → Bau-Loop (executing-plans, goal-based, TDD) [exists] + → Verify (battery + Gate B — adversarial model) [exists] + → PR (open it, then apply the routing table in + docs/pr-review-bots.md — its Wait-for list is empty, + so nothing blocks: read what the opportunistic bots + have posted, handle later posts as follow-ups) [exists] + → Sample-Gate (draws x% of PRs for human audit) [missing] + ↳ drawn → Audit (human, sampled) [missing] + ↳ not drawn → straight on (the ordinary path) [missing] + → Merge-Queue (serial: rebase onto main → smoke on the + candidate → green lands, red returns to the lane) [missing] + → Merge — the factory ends here; release and deploy + are the project's own CI (§8) [missing] + + ⟳ E2E-Loop (clock: nightly or at wave close; a failure + is filed back into the Story-Pool, §9) [missing] + ⟳ Drift-Audit (clock: docs drift, reverse traceability; + report-only, findings land in the Story-Pool) [missing] + + ┄ Vet preflight — mechanical checks run before every + model-operated node above (§10) [missing] + ┄ Judge/watchdog — sidecar over the model loops and + the queue; liveness only, escalates to the human [missing] +``` + +Every *model-operated* node is a loop with its own fresh context — the pool is +a store, Freigabe and Audit are human, the merge queue is a mechanical +operation. Only the artifact crosses an edge (story, spec, plan, diff, PR) — +never the intermediate steps. A reviewer +therefore judges only the result, never the process: separate eyes need +separate heads, and separate heads come from separate context. Everything that +matters must be *in* the artifact; process facts reach a reviewer only reified +as artifacts (records, findings, evidence entries). The one deliberate +exception is the judge/watchdog: it watches process signals (idle, thrash, no +progress) and judges only liveness, never quality. Mandatory +stops, scope questions and architecture re-evaluations escalate to the human; +the factory stops and reports instead of spinning. A human override becomes a +labeled example for tightening the rules — and it moves only knobs the human +already owns: it never waives a mandatory stop, a gate obligation, profile +evidence or an AGENTS.md invariant (CLAUDE.md §5: such a record "supplies no +permission"). + +The kit already holds the middle of this pipeline, and in one respect exceeds +the inspiration: the adversarial verifier is a different model *family* +(Codex), not merely a different context. + +## 2. Decisions (Daniel; 2026-08-30 unless dated otherwise) + +1. **Orchestrator lives hybrid.** The orchestrator is kit prompts (a skill / + agent definition any session can load — the product stays prompts). Only the + *clock* uses platform mechanics: a scheduled/loop wake-up starts an + orchestrator session. No daemon, no server-side infrastructure. The stage-4 + event wake (§3) is another adapter under this same boundary — a platform + trigger that *starts* a session, never a resident listener. Where a platform + offers no such trigger there is no stage-4 wake at all: the story waits for + the next scheduled tick. That is stage-3 polling at the configured latency + and is named as such rather than counted as proactive. +2. **Project truth is AGENTS.md, and the architecture tree is subordinate to + the pool.** Three things can disagree and stay distinct: the **approved + tree** in AGENTS.md (normative — what classification reads), the **code** + (observed fact), and the **pool** (intent). On a green field the approved + tree is built *from* the pool; on an existing codebase tree v1 is read from + the code (§9). Phase 0's job is to make the approved tree agree with the + code before it becomes authoritative. Afterwards a code/tree divergence is + drift that the audit reports, and a pool/tree divergence is what produces a + meta-story (decision 3) — neither is resolved silently. Either way + the tree is continuously re-evaluated against the pool (Bewertungs-Loop), + versioned, living in AGENTS.md beside the invariants and conventions. No + second architecture document that could drift. +3. **Architecture re-evaluation is a meta-story through the same factory.** + When a story breaks the tree, classification produces a story "extend the + architecture for X" with a high risk profile (heavy review, human in the + loop); the triggering story waits on it. A meta-story *is* the architecture + amendment, so it is classified as one and never produces a further + meta-story — that is what stops the recursion — while keeping the branch + lock and the human escalation its high profile earns. One process for + everything — the factory rebuilds itself the same way it builds features. +4. **Classification is the mandatory first station.** Every new pool item gets + the card (§5 below) before anything else; only "freigegeben" is pulled by + the clock. +5. **End state is sampled audit, not per-merge approval.** The adversarial + gate checks every merge; the human audits a sample. No merge skips both + gates — so **reviewer availability is an input to authorization**, and the + answer to an outage is to **wait where the outage happens**: a Gate-A + outage stops the lane before any candidate exists, a Gate-B outage holds + the work at Verify, and a candidate already queued stays queued. Nothing + advances past an unfinished gate, and **no human substitutes for the + gate**. + Sampled audit is QA, never authorization, and never stands in for an active + gate. "Gateless" exists only as a *declared project state*, and the shipped + declaration is the tracked **INACTIVE notice** `/workflow-init` writes into + the project's own CLAUDE.md ("the gates below do not run"). The + `.context/codex-gate.off` marker beside it is not that declaration: it + suppresses the hook reminder in one workspace, and shipped CLAUDE.md says + plainly that with it in place "the gates still apply". (Whether a clone + sees the marker depends on the repo: this kit ignores all of `.context/`, + while `/workflow-init` has target projects ignore `/.context/codex-reviews/` + specifically — so in a scaffolded project the marker is visible and + committable.) Neither file authorizes anything. That leaves the + reviewer-availability question exactly where its own record left it. (Until step 6 of the build path + matures, merge remains human anyway.) +6. **Filling the pool never triggers production; classification is a + proactive loop of its own.** (It is not free of *all* consequence — it + spends tokens, writes a card and a status, may create a meta-story and may + queue a question. What it cannot do is open a lane or merge code.) A new story's appearance (debounced into + mini-batches) triggers exactly one thing: the Intake-Loop attaches the + classification card and the architecture verdict. Production starts only + through the human's Freigabe plus the clock. Two rules guard the seam: the + Intake-Loop only attaches cards, status changes only through defined + operations (human: freigeben; loop: klassifiziert; a standing rule: + freigeben, carrying the committed rule as its authority and leaving its own + audit record — decision 7) — never free-form edits by several writers on + one field; and in Phase 0 the architecture verdict + waits until tree v1 exists. The intake zone (pool, Intake-Loop, + Architektur) is the factory's first stage-4 proactive loop — at the front + of the pipeline, not the end. "Unclassified" is a transient marker, never a + working state: the pool is the single entry, and the marker exists only as + the intake trigger, as visible backlog when the loop stalls, and for Phase + 0's deferred architecture verdicts. +7. **Freigabe reads a rendered wave plan; its granularity is a maturity knob.** + The card answers "can this be built" — Freigabe answers "should this be + built now": value and timing, profile confirmation, capacity, and a + spot-check of the card (a Freigabe that regularly needs deep thought means + the card is missing a dimension → labeled example, tighten intake). The + knob: per story → batch → wave ("Go" on the rendered wave plan) → standing + auto-Freigabe (notified, not asked; the standing rule sets the status + *freigegeben* itself, so decision 4 holds — the clock still pulls only + *freigegeben*). Three things hold on every rung: flags + always escalate (high profile, architecture ⚠, rückfragen, scope doubt); + the knob is human-owned and committed, changed only by defined operation + (like `lanes`); and the plan is rendered on every rung — as a question or + as a notice. Raising the rung follows measured P8 evidence, never precedes + it (§8, "No autonomy expansion ahead of the measured evidence"). +8. **Four-eyes principle, with a scaling guard.** No artifact passes only its + author. The second pair of eyes is another model *family*; where a project + has none configured, the honest answer is to be **gateless and say so** — a + declared, visible project state, never self-reviewed and not a same-family + agent either. A *runtime* outage is a different thing and is no licence to + close a cycle: the work waits (decision 5). What ships today is "be + gateless — not self-reviewed" (`docs/coding-workflow.md`, + `/workflow-init`), and this vision assumes nothing beyond it. What was + closed with a negative answer is narrower than "a same-family reviewer": + three fallback designs across nine Gate-A passes and 303 findings failed to + produce a safe *sanctioned zero-pass closure* + (`docs/superpowers/specs/2026-08-14-reviewer-availability-fallback-design.md`). + A same-family tier-2 reviewer is a different question and is **still open** + as a tracked, unshipped story + (`docs/superpowers/stories/2026-08-14-tier-2-same-family-reviewer-story.md`) + — "a weaker review is still a review", per that design's §7, if its + containment proves buildable. If it ever ships, this decision follows it; + until then the second pair of eyes is another family, or none. Human eyes only where the frequency is bounded — O(waves + + exceptions), never O(stories) — **as the end state**, which the rollout + deliberately does not satisfy yet: decision 7's lower rungs are per-story + and decision 5 keeps merge human until step 6. The bound is the target the + knobs move toward, not a rule the bootstrap already obeys. Every mandatory + human touchpoint carries a maturity knob that lowers with + P8 evidence — but a knob exists only at *routine* touchpoints: Freigabe + granularity, sample rates, wave opening. The four paths §8 protects (mandatory stops, + profile confirmations, scope changes, architecture meta-stories) carry no + such knob and stay human at every rung. Where a knob does exist, presence + is a dial that falls with trust and rises again on adverse evidence, never + a ratchet. + Two concrete rules: an architecture merge triggers re-classification of + the cards on the touched branches (their architecture verdicts are stale — + mechanical, no human involved); and the Sample-Gate draws architecture + merges at 100% as the starting value, knob downward with evidence — + bounded, because architecture changes batch into one meta-story per wave + (§9). +9. **Acceptance criteria are read-only inside a lane** (2026-08-31). A + builder never edits the acceptance criteria of the story it builds — that + is the reward-hacking vector the inspiration warns about. An acceptance + criterion found wrong during build is a story mutation, not a spec edit: + the lane stops, the story returns to the pool flagged "AC change needed", + the human decides (an exception path, so O(exceptions)), and the story + re-enters classification. Descriptive spec details a fix changes are + still updated in the same commit (CLAUDE.md §5 stands); every spec change + is captured as spec-delta (§10, step 2c) and shown to the reviewer beside + the diff, so baseline and change are both visible. Mechanically: + acceptance criteria carry IDs (§10 second sweep), the AC block's + fingerprint is compared at Gate B, and a changed block without the pool + round-trip is a Blocker. + +## 3. Maturity ladder + +| Stage | Loop kind | Status in the kit | +|---|---|---| +| 1 | Turn-based — skills with self-checks, red-first tests | shipped | +| 2 | Goal-based — acceptance criteria as target, gates loop to clean with mandatory stops | shipped | +| 3 | Time-based — clock loops: poll the pool, drift audits, PR-bot processing | missing | +| 4 | Proactive — event-triggered: first instance is the intake zone (a story appearing triggers classification, decision 6); later a story turning "freigegeben" wakes the orchestrator without waiting for the tick | missing | + +## 4. Conventions and roadmap — where each truth lives + +- **Kit conventions** (travel with the plugin): the workflow itself — gates, + profiles, loop rules, prompt standards, the classification card. Home: the + kit's prompts. They are the product. +- **Project conventions** (per target project): AGENTS.md — architecture tree, + invariants, verified commands, conventions, **plus two extensions this vision + requires: project goals and an explicit out-of-scope list**, which the + classification's reject verdict reads, **and two verified command roles, + `smoke` and `e2e`**, beside quality/lint/test (test layers, §9). One document, read by gates, bots and + triage alike. +- **A project's roadmap is a view, not a document**: pool items plus status, + priority, dependencies and **wave** yield the order. The pool is the single + source; a separate roadmap file would be a second copy that drifts. +- **The execution plan is a computed view too** — the wave dry-run: dependency + graph (classification) + tree topology (the parallelism map) + waves and + priorities (human) + lanes and throttles yield lane assignments and order. + Recomputed at wave opening and every tick, never stored; the human + intervenes through the inputs, never by editing the plan. It generalizes + the orchestrator dry-run (build step 3). Waves are milestones that also + *steer* — the clock pulls only the active wave — so the roadmap is the + milestone-level view and the wave plan its per-milestone detail. +- **The live status is a computed view too — the dashboard (decided + 2026-08-31).** The orchestrator writes a state snapshot every tick; + deterministic code — never a model, so a tick costs no tokens — renders + it into a local `status.html` (auto-refresh, with a visible staleness + stamp: "Stand 17:03 · Tick 42" — a dashboard that silently ages is worse + than none), a status-line ticker, and optionally a menu-bar script; a + console command renders the same snapshot on demand. The top element is + always the human's decision queue (flags, rückfragen, mandatory stops, + drawn audits) — the queue that throttles the factory. Events that need + the human push (§10); the dashboard is for looking, never for polling. + LLM summaries only on demand. Rejected: a daemon/TUI and a hosted + artifact page (account-bound). The wave plan and the dashboard share one + renderer — the plan is the forecast, the dashboard is the now. +- **Waves structure a new project.** Phase 0 assigns every initial pool item a + wave mark (wave 1, 2, … or named milestones) — the deliberate "these + subareas develop together first, those later" decision, usually aligned with + tree branches but not required to be. The orchestrator pulls only from the + active wave (a focus throttle beside the lanes budget: lanes = how much at + once, wave = what at all). Opening the next wave is the human's call; it + becomes automatic on prior-wave completion only where the human has raised + decision 7's knob to a standing rule for it, because an unasked wave opening + is a production trigger like any other (settled by build step 4). + Later stories get their wave mark at classification. +- **The kit's own roadmap** to this vision is §7 of this document. + +## 5. The classification card + +Every new pool item is classified before anything else. Dimensions 1–4 exist +in the intake skill today; 5–8 are new: + +1. **Size** — story, or epic that must be split. +2. **Risk/security profile** — drives lenses, evidence mode **and the pass + floor**. The floor became profile-derived when build step 1 shipped + (0.11.0); before that the kit had a fixed 3-pass floor, and this line said + so. Lenses remain different questions rather than more passes. +3. **Completeness** — too thin → one question round back to the human (several + targeted questions in it, as the shipped intake skill does); nothing is + invented. +4. **Invariant touch-list** — which AGENTS.md invariants the item affects. +5. **Scope verdict** — inside project goals? Duplicate of a pooled or in-flight + story? Violates an invariant by design? (Source: the goals/out-of-scope + extension of AGENTS.md.) +6. **Architecture verdict** — from the Bewertungs-Loop: seamless, or + re-evaluation needed (→ meta-story per decision 3). +7. **Dependencies** — needs story Y first → wait mark and ordering. +8. **Wave** — which development wave the item belongs to (§4); assigned in + Phase 0 for initial items, at classification for later ones. + +Verdicts: **freigeben / teilen / rückfragen / warten-auf / ablehnen** (with +reason, back to the human — rejection is never deletion). Split rules: multiple +independent subsystems in one item; mixed profiles in one item (so the cheap +part gets the cheap floor); mixed architecture verdict (the seamless part +proceeds, the breaking part waits on the meta-story). + +## 6. Gaps (current state → vision) + +1. Story pool with status — the factory's trigger. *(missing)* +2. Classification station beyond intake's current card (scope, architecture, + dependency and wave dimensions). *(missing)* +3. Architecture tree + Bewertungs-Loop in AGENTS.md. *(missing)* +4. Orchestrator as product — the sparring-session practice codified as + skill/agent: question routing, artifact handoff, prediction ledger, batched + human decisions. *(missing; exists as practice)* +5. Time-based and proactive loops (stage 3/4). *(missing)* +6. Sample-Gate + sampled audit. *(missing)* +7. Fresh context per stage is convention, not enforced — long sessions + measurably degrade. *(partial)* +8. Same-repo parallelism — N worktrees × 1 agent works today; the record/nonce + rules the review-economics story shipped in 0.11.0 are the foundation for + more. *(foundation shipped; the parallelism itself is not)* + +## 7. Build path (each *leaf* is one story; numbered entries are ordering groups) + +1. ~~Finish the review-economics story~~ — **shipped in 0.11.0**: floors by + profile, severity by reachable consequence, and the two measurable records. + Without calibrated review economics every factory is a token furnace, which + is why this was step 1. +2. **Measure before automating further** — three ordered leaves, not one + story: + - **2a** loop-rule consolidation (the successor story to step 1). + - **2b** the tracked P8 passive-metrics story + (`docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md`), + which is **read-only over the ledger and git** and stays that way. This + vision does not widen it. + - **2c** a separate telemetry story for everything this vision adds that P8 + does not do: run analytics with cost and duration per step, a trace ID + carried through every stage artifact, mechanical spec-delta capture, and + live cost counters. That is new state and new instrumentation, so it + cannot ride inside 2b. +3. Orchestrator story (codify the role as skill/agent per decision 1) — the + role, the artifact handoff, and the *interface* of the tick execution plan. + The working dry-run reads the pool, the projection, waves, lanes and the + tick, so it lands after 4d and 5a rather than here (§4). +4. **Story pool and classification** (decisions 2, 4, 6, 7 and 9). An epic, so + §5's own split rule applies to it; the ordered stories, behind named + interfaces: + - **4a** pool storage, item identity, and the status state machine — every + state, its authorizing operation, its precondition and its terminal or + resumable outcome. + - **4b** classification: the eight card dimensions, the verdicts, the split + and dependency rules — including the proactive Intake-Loop, which is the + factory's first stage-4 loop (decision 6), so stage 4 starts here rather + than at step 6. + - **4c** architecture: Phase 0 and tree v1 (§9), the machine-readable + projection generated from AGENTS.md, **and the Bewertungs-Loop runtime** + — its continuous pool-versus-tree evaluation, its meta-story batching per + wave, and the re-classification an architecture merge triggers. The + bootstrap alone would leave §1's Bewertungs-Loop unbuilt. + - **4d** Freigabe and wave control: the rendered wave plan, the granularity + knob and its standing rules, wave opening and closing (decision 7). No + other step owned this, and the pipeline cannot run without it. + - **4e** decision 9's mechanics: acceptance-criterion IDs, the read-only + rule inside a lane, the AC-block comparison at Gate B and the pool + round-trip — plus given/when/then normalization and the per-AC test + report as a handoff artifact, which is the machine-checkable goal + condition the fingerprint protects and which no other leaf owned. + Ordered **before any autonomous lane execution** — it is the + reward-hacking guard, and a build path that ships lanes without it looks + complete while the guard is missing. +5. **Stage 3, plus the merge queue** (polling, drift audits, PR polling — and + one station, 5b, that is not a clock loop at all). Also an epic; its + stories are separate failure domains behind one shared contract: + - **5a** the clock-loop contract itself: tick, run lock, environment + preflight, usage-limit hold/resume, the report-only default, and the + branch-claim and lane-budget rules the scheduler needs. + - **5b** the merge-queue station (rebase + smoke on the candidate, §9). It + is **not** a clock loop: it is a serial mechanical station triggered by + queue entry, so it takes the run-lock and artifact halves of 5a's + contract and none of the tick, usage-hold or report-only halves. + - **5c** the E2E clock loop (failures auto-filed as pool stories, §9). + - **5d** the drift audits: docs drift and reverse traceability, + report-only, findings filed as pool items. + - **5e** the PR poller: a report-only clock loop that notices a PR needing + attention and wakes the shipped `process-pr-review` station inside a + lane. It does not process the PR itself — that command fixes, commits and + hardens, which is lane work and not something a report-only clock loop + may do. +6. **Stage 4 and sampled audit** (decision 5) — the highest-risk step, so it + splits like the others: + - **6a** the stage-4 orchestrator wake: a story turning *freigegeben* wakes + the orchestrator without waiting for the tick. + - **6b** the Sample-Gate and the audit state: the drawing rule, the audit + verdict's binding to the candidate it reviewed, and the rejection path + back to the lane. + - **6c** graduated automatic merge: mechanical risk thresholds beneath the + semantic profile, starting with level-0 stories only. + - **6d** the promotion path past level 0 — the profile threshold at which + automation widens to standard and high stories, the evidence that moves + it, and its downgrade triggers. Without this leaf every other story can + finish while most work still waits for per-merge human approval, and the + end state is never reached. + Merge stays human until 6c ships and holds. + +## 8. Non-goals + +- No daemon or server-side runner (decision 1). +- No second architecture or roadmap document (decisions 2, §4). +- No removal of the human from mandatory stops, profile confirmations, scope + changes, or architecture meta-stories — "dark" means sampled presence, not + absence. +- No autonomy expansion ahead of the measured evidence (P8) that the review + economics support it. +- No deployment stage. The factory ends at the merge; what follows (release + tags, environments, direct deploy) is the project's own CI. + +## 9. Parallelism and flow control (decisions 2026-08-30, Daniel) + +- **Phase 0 exists once.** Initial brainstorming builds architecture tree v1 + plus goals/out-of-scope before production starts. It is the only + everything-waits moment; after it, intake never freezes. It has two inputs: + a green field derives tree v1 from the pool; an existing project reads it + from the codebase (natural home: a `/workflow-init` extension). Tree v1 need + only be good enough to judge with — meta-stories correct it in use + (decision 3). A single incoming story is simply a mini-wave: the views + collapse to one line and no *station* is bypassed — though a behaviourally + trivial change can still take the shipped Gate-B triviality skip, and an + undrawn PR does not reach Audit; §11 records the first as an open end-state + question. +- **Architecture churn blocks branches, never the factory.** An + architecture-relevant story is not rejected: classification parks it with + warten-auf behind its meta-story, and the meta-story locks exactly the tree + branches it touches. Stories on other branches keep flowing. The + Bewertungs-Loop batches architecture-relevant items into one meta-story per + wave instead of serial tree churn; the meta-story's priority is the human's + knob (pull it forward to unlock branches sooner, or defer it and let the + parked stories wait). +- **The tree is the parallelism map.** Disjoint branches run in parallel + (N worktrees × one pipeline each — possible today; the record/nonce rules + are the foundation); same branch serializes. Classification's dependency and + architecture verdicts yield the schedule. Within a story the stages stay + serial — the plan needs the spec, the build needs the plan, Verify needs the + diff — but independent work *inside* one stage may fan out to subagents, as + Gate B's two review branches already do. What does + not parallelize: the serial passes of one review loop, the merge queue to + main, and the human's decisions. +- **The parallelism budget is a user-owned knob.** It lives in the story + pool's header (one committed, visible place, e.g. `lanes: 3`), is never + written by an agent, and is read fresh by the orchestrator at every tick — + raising it buys throughput while the tree has disjoint branches; lowering it + throttles token spend (`lanes: 0` pauses intake of new lanes; running lanes + drain). Two automatic throttles on top: no new lane opens while any lane + stands in a mandatory stop, and no new lane opens while more than N + decisions are queued for the human — the measured bottleneck is decision + bandwidth, not compute. The first of those is a **global** brake, and it + narrows the branch-only guarantee above: architecture churn parks *stories* + per branch, but a mandatory stop in any lane halts *new lanes everywhere* + until it clears. Running lanes drain either way. +- **Three test layers, three cost classes** (decided 2026-08-31). The lane + battery (seconds, every cycle, exists) · a **smoke gate in the merge queue** + (minutes, every merge, new) · the **full E2E suite as a clock loop** (hours, + nightly or at wave close, stage 3). The merge queue is a station of its own: + serial, per candidate rebase onto current main → smoke on the composed + candidate → green lands, red returns to the lane as an artifact (like Gate-B + findings) while the queue continues with the next branch; repeated failure + escalates via the judge. The bounded claim: the queue never lands a + candidate whose configured smoke command fails. That is not a claim about + main's full-system health — only the E2E layer speaks to that, and it can + still find a failure on already-merged main. Landing is automatic from + build step 6 onward; until then the green outcome is a human merge + (decision 5). + This closes the parallelism blind spot (disjoint lanes each green, their + composition broken) and owns the merge-coordinator mechanics (rebase, + retry). An E2E failure becomes a pool story automatically, classified by + the normal intake, carrying the trace ID of the suspect merge. Concrete + tools stay project truth: AGENTS.md gains the command roles `smoke` and + `e2e`. Cadence and flaky rules belong to step 5. +- **Role separation across running agents (decided 2026-08-31).** Spawned + subagents separate automatically — one task, fresh context, no shared + chat. Long-lived interactive sessions do not, so three rules hold: + **star, not mesh** — lanes never talk to each other or sideways; + everything crosses the orchestrator or is an artifact. **Role = task** — + an input artifact and an output artifact define a role, never chat + history; only the orchestrator lives long, and even it wakes fresh per + tick with the pool as its memory. **Identity lives in artifacts, not + session names** (names proved unstable across a restart on 2026-08-31): + lock files, slot infixes, status fields with defined writers. Enforced + by mechanics already decided: one worktree per lane, the run lock, + defined status operations, the record/nonce rules. The human-facing + sparring chat is the one persistent conversation; its boundary is + decisions and questions, never production. [owner: step 3] +- **Shortcuts are shorter lanes, never side doors (decided 2026-08-31).** + Everything enters through the pool (seconds) and leaves through Verify + and the merge-queue smoke; what shrinks in between is decided by the + card, never by the builder (decision 8: an author never rates itself + trivial). Four cases: a **trivial lane** collapses Spec- and Plan-Loop — + the story text is the spec, no plan — with the light review the profile + rules already grant; a **hotfix lane** gets speed from priority, not + from skipped review — a standing "hotfix" wave bypasses wave steering + and jumps the queue, Verify stays full; an **experiment** runs in a + throwaway worktree and never merges — its artifact is a report, not a + diff, so no gates apply where nothing lands, and learnings become pool + stories; and **hand work by the human** is always allowed, but what + wants onto main passes the same gates — four eyes has no owner + exception. [owner: step 4 (card-driven depth), step 5 (hotfix wave)] +- **Hooks are subscribers to station-boundary events (decided + 2026-08-31).** The factory emits an event at every station boundary + (story arrived, classified, freigegeben, lane opened, merge landed, + escalation, audit drawn) — the trace infrastructure (2c) produces these + anyway. Project-owned hooks subscribe in two classes: **passive** hooks + notify, render or log (push, status line, dashboard and analytics are + the first four subscribers) and may change nothing; **active** hooks may + do exactly one thing — put a story into the pool (per the fix-permission + rule: never a direct fix), so everything mutating passes classification + and Freigabe. The E2E loop is the first active hook. [owner: 2c + (events), step 5 (subscriptions)] + +## 10. Prior art: godarkfactory.com (reviewed 2026-08-30) + +A shipped, self-hosted dark factory (Go binary `godark`, Elastic License 2.0, +beta): GitHub issues grouped into milestone "phases", topological dependency +waves, a three-agent loop (implement → quality+functional review → auto-merge) +in Docker sandboxes, all agents Claude. Validates most of this vision's shape +— milestones↔waves, scenario specs↔pre-build checks, define-architecture / +define-conventions↔Phase 0, watch↔clock loops, needs-human-review↔escalation. + +**Adopted into the build path** (owning step in brackets): +- Local SQLite analytics — cost/duration/retries per step, written + non-fatally post-run; plus a trace ID per story propagated through every + stage artifact, and mechanical spec-delta capture. [step 2c — new state and + instrumentation, so not the read-only P8 story] +- Orchestrator dry-run (print the tick's execution plan before spending + tokens) and a forced-fresh-session knob (`max_resume_retries` analog) — + turns gap 7 into a mechanism. [step 3] +- Mechanical vet preflight on pool items and artifacts before any model pass; + numeric split thresholds for the teilen verdict (their working values: max + 5 acceptance criteria, 7 test cases); a machine-readable architecture + projection (may_depend_on / must_not_depend_on, cycle-checked) *generated + from* AGENTS.md — never a second source. Their GitHub-issues-as-pool is a + working data point for the pool-storage question. [step 4] +- A judge watchdog (idle / tool-thrash / no-progress supervisor that is not + the hanging model) and push notifications on run events — the human is the + bottleneck; push, don't make them poll. [step 5] +- Graduated auto-merge with mechanical risk thresholds (max lines/files) as + an independent floor under the semantic profile; punchlist generation as + the artifact a sampled audit works from. [step 6] + +**Deliberately not adopted:** `quality_strictness_decay` (their default — +review gates weaken as retries mount; the inverse of the clean-final-pass +rule); same-model review (their implementer and reviewers share one model +family — correlated blind spots; our adversarial gate stays cross-model); +daemon/Docker/GitHub as hard requirements (§8, "No daemon or server-side +runner", stands); stop-the-world +sequential milestones (branch-scoped locks and the lanes budget replace it). + +**Their documented scars, kept as constraints here:** manual state mutation +under an automated resolver breaks it — every hand-edit of pool status must +be a defined operation; planning artifacts need a defined home so they never +contaminate implementation branches; run artifacts need a retention stance +from day one (their P0 gap: unbounded disk growth); absent real-time cost +visibility is what makes a token furnace invisible (their P0 gap; ours is +2c's live counters — not the read-only P8 story, which is forbidden to +instrument anything). + +**Second sweep (2026-08-31), against their full docs and the inspiration +video.** The sweep produced two working notes that live outside the repo and +are deliberately not cited by path, because they do not survive a clone. The +durable record is the list below. +Adopted, with owning step: +- Mechanical write protection for pool status, the `lanes` knob and the + architecture section of AGENTS.md (their `protected_paths` / + `denied_commands`). This would be the kit's first *blocking* hook, and + invariant 1 ("the hook always exits 0") forbids one. Arguing that a + local-state check carries none of the external dependency invariant 1 names + is an argument *for amending* it, not an exemption from it — so this item's + prerequisite is an **architecture meta-story amending invariant 1** + (decision 3). Invariant 1 is not the only one it meets: invariant 2 ("on + uncertainty, fire") would turn every parse or environment failure into a + denied write, and invariant 4 keeps the hook POSIX `sh` with `jq` optional. + The meta-story must decide fail-open or fail-closed for each local failure + mode and say which of the three invariants it amends and which it preserves. + Until it lands there is no mechanical write protection at all. What exists + is **authorized-write tooling** (a repo-owned command an agent may choose to + use) and **merge-time detection** (a required CI check). Neither prevents an + unauthorized local write, and the factory can schedule or build from one for + as long as it takes a commit to reach CI. That bypass window is the reason + the blocking mechanism is wanted, and the interim path does not close it. + [prerequisite meta-story, then step 4/5] +- A run lock per orchestrator tick with stale-lock cleanup by the judge, so + a double-firing clock never runs two orchestrators. [step 5] +- Usage-limit hold/resume: a hit rate limit pauses the run with a countdown + and resumes — never counted as a failure (their v0.18 lesson). [step 5] +- Model strength per role as a project knob (cheap builder, strong + reviewer), with one asymmetry rule: the reviewer is never weaker than the + builder — cross-model stays. [step 1/3] +- Minimum review cost/duration as an INCOMPLETE signal: a pass under the + floor does not count toward the pass floor. No new gate — the kit's + existing INCOMPLETE semantics, fed by 2c's measurements. [step 2c] +- Audit rejection flows back automatically: the human's "changes requested" + returns to the lane as an artifact (like a red smoke), and a PR once + rejected by the human is always re-audited by the human, never re-drawn. + [step 6] +- Environment preflight before every tick (`doctor`: required env, host + services) — a clock session on a broken environment only burns tokens. + [step 5] +- Phase 0 on an existing codebase also *reports* agent-hostile patterns + (codegen magic, implicit behaviour) — report only, never fixes. [Phase 0 / + `workflow-init`] +- Wave close renders an as-built view (what was actually built, grounded in + code) — a generated view like the roadmap, never maintained; the standing + defence against docs drift. [step 4/5] +- From the video: a hard round cap per Bau-Loop and a token budget per + story as preventive limits — reaching one is a mandatory stop, never a + silent kill; the caps apply to build loops, not to review loops, which + keep their floor and tells. [step 3/5] Acceptance criteria with IDs in + given/when/then form and a per-AC test report as a handoff artifact (the + machine-checkable goal condition; also what decision 9's fingerprint + protects). [step 1/4] Reverse traceability (code with no spec behind it) + as drift-audit content, report only. [step 5] Every clock loop starts + report-only — meaning it changes no product code and no pool *status*; + filing a new pool item is the one write it does make, because a report + nobody can act on is not a report (that is how the E2E loop files its + failure, §9). "Fix permission" is a maturity knob per loop, and it means + exactly one thing: the loop may **create and advance a normally classified + pool story**. It never means editing code directly. Every fix a loop wants + travels through classification, Freigabe and a wave like any other story — + decision 3's one process for everything covers the loops too. [step 5] + Deployment is out of scope (§8). + +Redirected: their merge coordinator is our merge-queue station (§9); their +`report` (executive summary over the analytics window) belongs to the +dashboard topic (§11); their harness-as-router (CLAUDE.md compressed to a +20-line signpost) becomes a kit story once 2c measures harness tokens per +session — with fresh context per station, every story pays the harness +size times its stations. + +Not adopted: the rollup branch (issue PRs into a wave branch, one rollup PR +to main). It is a part of their stop-the-world milestone model, already +rejected. What replaces it is weaker, and saying so is the point: incremental +smoke on each composed candidate *before* it lands, plus an E2E run over a +wave that is already on main. There is no atomic whole-wave candidate and no +pre-main whole-wave checkpoint, so some integration failures surface only +after a partial wave has shipped. The as-built view gives the wave-level +reading, and +a wave branch would break the kit's merge-base-with-main semantics and make +hotfixes a two-way merge — the opposite of flexible shortcuts (§11). A +project that needs "main = whole waves only" does not get it here: the queue +lands individual candidates throughout a wave, so main is incremental by +construction. Release tags in its own CI (§8) give that project release +*boundaries*, which is a different thing and the most this design offers. Also not adopted, noted as a lesson only: hard byte +caps on verify output and PR diffs in prompts (unbounded diffs blow context +and cost). Irrelevant here: per-module monorepo batteries, LangGraph as a +framework, knowledge-graph disambiguation. + +## 11. Open questions + +Most carry the leaf or step that will answer them. Some do not: the one +parked topic below — hidden verification scenarios — and +the stations near the end of this section that §1 draws but no +leaf yet owns. Those are marked as such rather than counted as decomposed. + +- Sample percentage and drawing rule for the Sample-Gate (step 6). +- Storage form of the pool (files in-repo vs. external board) — step 4. +- Hidden verification scenarios (checks written before build, unseen by the + builder) as a Gate-B supplement — candidate small story, unscheduled. +- How the clock's platform mechanics (loop/schedule) are configured per + project — step 5. +- Process dashboard / console status — decided 2026-08-31 and recorded as + the live-status view in §4 (snapshot per tick, rendered by code into + status.html / status line / optional menu bar, decision queue first, + staleness visible); still open: the snapshot schema and the owning leaf, + fed by 2c's traces and analytics. [2c / dashboard] +- Hooks and shortcuts — decided 2026-08-31 and recorded in §9 (shortcuts + are shorter lanes, never side doors; hooks are subscribers to + station-boundary events, passive or story-creating). Still open: the + event schema, with 2c. [2c / step 5] +- Test layers are decided (§9); still open: whether a smoke failure that + returns to a lane is surfaced to the human (a dashboard question), and the + E2E cadence / flaky-handling rules — step 5. + +**Recorded by Gate A pass 1 (2026-08-31).** Gaps this document does not close, +each with the step that owns it. They are named rather than specified: a +decomposition document that invented them would be taking design decisions +nobody took, and a gap named here cannot be silently invented later by whoever +writes the story. + +- *Pool and state* — cycles, self-dependencies and dangling targets; what a + dependency becomes when its target is split or rejected; the atomic + transition when the last dependency clears; which terminal statuses count as + resolved for wave closure and where carried-over items go; Phase 0's + empty-pool outcome; and whether an external board can satisfy the committed, + history-bearing, compare-and-set assumptions the `lanes` knob makes. [4a] +- *Intake concurrency* — stable item IDs and idempotent classification writes, + so a re-delivered or overlapping debounce batch cannot produce a second card + or a second meta-story. [4b] +- *The classification transaction* — the complete set of writes one + classification may make (card, status, meta-story, dependency link) and its + atomicity. Decision 6's "attaches cards only" and decision 3's meta-story + creation are the same act, so the seam guard needs the full list. [4b] +- *Naming* — the verdict `freigeben` and the status `freigegeben` are one + letter apart, and the first must never produce the second. The classification + outcome needs a non-authorizing name. [4b] +- *Architecture staleness* — an architecture merge makes verdicts stale, but + nothing yet revokes an already-*freigegeben* card or stops a running lane + whose verdict aged out mid-build. Binding cards and lanes to a tree version + is the candidate. [4c] +- *Projection freshness* — what the generated projection does on a stale + digest, a parse failure, or a detected cycle. [4c] +- *Approval binding* — the wave plan the human approves is recomputed every + tick from mutable inputs, so the clock can execute a materially different + plan than the one approved. Binding an approval to an input digest and + re-rendering when it moves is the candidate. [4d] +- *The decision-queue throttle's `N`* — a committed knob with a default, a + range and an exact comparison, distinct from `lanes`. [5a] +- *Branch-claim semantics* — "disjoint branches" is undefined for + ancestor/descendant overlap, shared roots, multi-branch stories, and a lane + whose touched set grows during the build. [5a] +- *Orchestrator exclusion* — stale-lock cleanup is not exclusion: a live owner + paused in a usage-limit hold is exactly what a staleness heuristic + misreads. A lease with a heartbeat and a fencing token that every mutating + write checks is the candidate. [5a] +- *Merge-queue terminal paths* — rebase conflict, unclean worktree, a branch + deleted while queued, a retry invalidated by newer main: each needs its + artifact, its return transition and a retry cap. [5b] +- *E2E attribution* — a nightly or wave-close run covers many merges, so "the + trace ID of the suspect merge" is not derivable from it. Carrying the tested + merge range and marking attribution unknown unless deterministically + isolated is the candidate. [5c] +- *Telemetry gaps* — analytics writes are non-fatal, yet review cost and + duration decide whether a pass counts. A missing or unattributable gate + measurement must read as INCOMPLETE, never as a cheap pass. [2c] +- *Live cost visibility* — post-run analytics detect overspend after it + happened, which is not the control §10 keeps from their P0 gap. Live + counters, alerts and stop thresholds are a different mechanism from P8's + passive record. [2c / dashboard] +- *Retention* — run artifacts need a retention, compaction and deletion stance + from the first story that writes them; their P0 gap was exactly this. [2c] +- *Planning-artifact home* — where plans and specs live so they never + contaminate an implementation branch, and which of them are tracked. [step 3] +- *Model strength order* — "the reviewer is never weaker than the builder" + needs a project-owned order across families and a fail-closed path for an + unknown or newly released model. [step 1/3] +- *The triviality gap in decision 5* — "no merge skips both gates" does not + hold today: the shipped Gate-B triviality skip plus an undrawn Sample-Gate + leaves a change with neither. Whether the end state removes that skip, or + makes every Gate-B-skipped change a mandatory draw, is open. [step 6] +- *Audit binding* — a draw and an audit verdict are not bound to the bytes + reviewed, so a later fix, rebase or Gate-B round can inherit an audit of + something else. Which mutations force resampling and re-audit is open. [step 6] +- *Autonomy downgrade* — every knob lowers human presence on good evidence and + nothing raises it back on bad (audit rejection, repeated smoke failure, + deteriorating metrics). Triggers, authority, hysteresis and the immediate + safe state belong to 6d, which owns the promotion path in both directions; + until 6d defines them "a dial, never a ratchet" is only half true. [6d] +- *Meta-story batching versus the split rule* — one architecture meta-story per + wave can combine independent subsystems and mixed profiles, which §5 says + must split. Batching by compatible branch and profile group is the candidate, + and the O(waves) bound then counts batches rather than waves. [4c / step 6] +- *The hardening ledger covers only one of three finding sources* — the + fingerprinted ledger and `harden-finding` are one of the kit's three + defining mechanisms (AGENTS.md), and `process-pr-review` item 5 already + routes accepted actionable **bot** findings into it. Two sources have no + station: accepted **Gate A and Gate B** findings, and accepted findings from + a **sampled human audit** — the latter being the factory's highest-value + signal, since an audit finding is something the gates that ran did not + catch, or that no gate saw at all where Gate B was legitimately skipped and + Gate A reviewed only the spec and the plan. Repairing those instances without recording fingerprints loses the + escalation that turns a recurring finding into a rule. Gate findings belong + to the loop-rule owner rather than to the classifier — 4b is pre-production + and never sees a review loop. [gate findings 2a, audit findings 6b] +- *Gate B and the rebase* — the queue rebases onto newer main after Gate B ran + and then runs only smoke, so what lands is not byte-for-byte what the + adversarial gate reviewed. Whether Gate B re-runs on the composed candidate, + or the claim is narrowed, is open — and until it is settled, "the adversarial + gate checks every merge" (decision 5) is a claim about the pre-rebase diff. + [5b] +- *PR readiness before the queue* — nothing sequences PR opening, the routed + review bots (`docs/pr-review-bots.md`) and required CI against Sample-Gate + entry and queue entry. [5e] + +**Stations drawn in §1 that no leaf yet owns.** Each needs a leaf assigned +before step 4 or step 5 planning begins; naming them here is what stops a +roadmap from reading complete while a drawn station is unbuilt. + +- *The vet preflight* — §10 assigns it to step 4 as a whole, but 4a, 4b and 4c + divide storage, classification and architecture between them and none owns a + runner that fires before **every** model-operated node, including nodes that + do not exist yet. Candidate: its own leaf under the shared contract, with + its pool, split-threshold and projection checks mapped to 4a, 4b and 4c. +- *The judge / watchdog* — §10 assigns it to step 5 as a whole, and it appears + in no leaf. It supervises the model loops **and** the merge queue, which is + not a clock loop, so it does not fall out of 5a either. Candidate: its own + leaf covering every supervised node plus the push notifications. +- *The working orchestrator dry-run* — step 3 owns the plan interface and + says the runnable dry-run lands after 4d and 5a, but no later leaf owns it + and §10 still points at step 3. Candidate: a leaf after 5a, with §10 + repointed to it. +- *The as-built view at wave close* — §10 assigns it to steps 4 and 5, it is + in no leaf, and it is not drawn in §1. Candidate: 5d generates it and 4d + requires it in the wave-close transition. +- *Punchlist generation* — §10 promises it as the artifact a sampled audit + reads, assigned to step 6 as a whole; 6b owns the draw and the audit state, + 6c owns the merge thresholds, and neither owns producing the punchlist. + Candidate: 6b, since it is what the audit consumes. +- *Model strength per role* — §10 assigns it to step 1/3, but the tracked + review-economics story contains no model-strength knob and step 3's + definition omits it, so both cited owners can finish without it. Candidate: + 2a with the other loop rules, with §10 repointed. + +**Unresolved at close (Gate A pass 5, 2026-08-31).** The gate was closed on a +scope disposition rather than a clean pass: leaf-level mechanics are outside +this decomposition document, and each item below is owned by the named leaf +story and its own Gate A. Pass 5 returned 15 findings and **zero Blockers** +(the series ran 52, 34, 32, 26, 15). These went unfixed by decision, not by +oversight, and a later reader should treat them as known: + +- *Labeled examples have no owner* — §1 turns a human override into a labeled + example, and decision 7 does the same for a Freigabe that repeatedly needs + deep thought, but no leaf captures, stores, routes or consumes them. + Candidate: 2a with the loop rules. [unowned] +- *The orchestrator product is wider than step 3* — §6 gap 4 names question + routing, artifact handoff, a prediction ledger and batched human decisions; + step 3 owns the role, the handoff and the plan interface only. Three + capabilities have no leaf. [3, needs widening] +- *"This closes the parallelism blind spot"* (§9) overstates what smoke + proves: it shows the configured smoke command passed on the composed + candidate, not that independently green lanes compose correctly outside + smoke coverage. The E2E layer is what reaches the rest, later. [5b / 5c] +- *"Promotion evidence" names one owner for several knobs* — 6d owns the + automatic-merge threshold, but the Freigabe and wave-opening knobs belong to + 4d and the sample rate to 6b, so the entry's single [6d] owner is too + narrow. [4d, 6b, 6d] +- *Coarse owners remain on four top-level questions* — the Sample-Gate rule, + pool storage, clock platform configuration and E2E cadence still cite step + 6, step 4 and step 5 rather than leaves, although this document makes the + leaf the unit of ownership. [6b, 4a, 5a, 5c] +These are the whole remainder. Six further pass-5 findings named wordings that +pass 4 had reported as fixed but had not written to disk — the §7 heading, step +5's title, decision 8's "every mandatory touchpoint", the autonomy-downgrade +owner, the §11 opening count, the dashboard's P8 attribution and the mini-wave +"no stage is skipped". Those were applied at close rather than recorded, and +the miss is noted here because a reader comparing the pass-4 record against the +document would otherwise find them inconsistent. +- *AC canonicalization* — decision 9's fingerprint needs a canonical + serialization of the AC block, a baseline captured at lane opening, a + durable record of the authorizing pool round-trip, and the exact evidence + Gate B compares. Without them the check cannot separate an authorized + correction from reward hacking. [4e] +- *The stopped lane* — when a lane stops for an AC change, whether its partial + work, plan, evidence and review counters are discarded, quarantined or + resumed after reclassification is undefined, and each choice changes the + Gate-B baseline. [4e] +- *Given/when/then and the per-AC test report* — §10 assigns these to steps 1 + and 4, but step 1's tracked story does not contain them. 4e now owns them + explicitly (see §7), so what remains is repointing §10 away from step 1. [4e] +- *Promotion evidence* — "measured P8 evidence" is the condition on every + autonomy knob, but P8 measures ledger recurrence and review-severity mixes, + not Freigabe accuracy, audit escapes or merge safety. Each knob needs its own + outcome metric, adverse-event measure, window and threshold. [6d] +- *Wave-close ordering* — the order of the wave-close E2E run, the as-built + view, wave closure and any standing auto-open rule is undefined, so a wave can + open before the previous one's integration result exists, and a late E2E + failure has no defined wave. [4d] diff --git a/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md b/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md index 07b4638..f3d6fe7 100644 --- a/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md +++ b/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md @@ -31,6 +31,36 @@ From `todos.md`, "**P8 — passive metrics, read-only over the ledger and git.** | It answers questions the ledger already contains the data for — which fingerprints recur, how often a rung holds | **kept** as the scope statement | | Trigger: 10 stories or 20 ledger rows, below which the sample says more about the last week than about the workflow | **moved** — the 20-row arm fired at 22 rows in the 2026-08-04 round, and the story arm reaches 10 with this story; recorded here | +### Second question routed here, added 2026-08-28 (updated 2026-08-29: the forms it reads are now pinned) + +`docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md` defers to this story, +rather than to a fresh backlog row, a **comparison** — not a measurement of its rule's effect. The +question it hands over: **after roughly three profiled cycles run under the new floor and severity +rules, do their recorded severity mixes differ from the `fic2` baseline curve of +14 · 24 · 12 · 3 · 6 · 6 · 2** (`docs/field-reports/2026-08-26-fic2-cycle-evidence.md`)? That story +cannot answer it inside its own cycle, and a single cycle would not answer it in any case. **Nor +can any number of cycles yield a demotion figure**: that needs one finding classified under both +rules, which nothing records — see acceptance criterion 5 for the two confounds any reported figure +must carry. + +Two things this hand-off does not silently assume, because the row's "the data already exists" +condition is doing real work: + +- **The per-pass curve is only sometimes in git.** Closing commit bodies carry it by habit, not + by rule: `3cdd075` records "Findings 14, 24, 12, 3, 6, 6, 2. Blockers 3, 4, 0, 0, 0, 0, 0" and + `baa75c1` records per-pass counts, while `7bbdb14` gives the pass total and no distribution. + The findings files those numbers come from live under `.context/`, which is gitignored and + per-clone. So this question is answerable from git only for cycles whose author wrote the + curve down. +- ~~**Whether closing bodies should be *required* to carry the curve is not decided here.**~~ + **Decided 2026-08-29 by the parent story**, which makes the curve and the provenance line + required and pins both forms. The subset problem this bullet raised is therefore closed for + cycles run under those rules and remains for every cycle before them — an analysis spanning both + says which side of that line each cycle falls on. + +Neither point changes this story's scope: still read-only, still no instrumentation, still +nothing written back. + ## 2. Desired outcome A reader can ask the ledger which fingerprints recur and how a rung has held, and get the answer @@ -47,6 +77,24 @@ from the file rather than from recall — without the analysis writing anything trust it. - [ ] What the analysis cannot answer from the ledger alone is stated, rather than left for a reader to infer from what it does answer. +- [ ] **The review-loop question routed here is answerable, or its gap is named.** The + review-loop-economics story defers to this one a **comparison, not a measurement of its + rule's effect**: after roughly three profiled cycles under the new rules, compare their + recorded severity mixes with the `fic2` baseline — which carries complete per-pass totals and + Blockers and **Majors for only some passes**. **Two confounds must be named wherever a figure + is reported**: the curves are self-reported and unvalidated, and the cycles being compared + reviewed **different artifacts**, so a difference is evidence about the population as much as + about the rule. **No demotion figure is derivable** — that would need one finding classified + under both rules, which nothing records. **An unknown count is excluded per series, not per + pass**: a curve may record one series as unknown while the others are intact, and whatever is + reported states how many values were excluded from each series rather than how many passes + were dropped. That analysis reads **two pinned commit-body forms** — the provenance + line and the per-pass curve, both specified in + `docs/superpowers/specs/2026-08-28-review-loop-economics-design.md` — and its **first + checkpoint** is the first post-merge cycle whose cited set licenses floor 1, which must carry + the floor-1 provenance line. Whatever this analysis reports from those forms says plainly that + **the curves are author-written and unchecked**: nothing compares them against the validated + pass files, so they are self-reported and not measurement. ## 4. Affected AGENTS.md invariants diff --git a/docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md b/docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md new file mode 100644 index 0000000..43acb8e --- /dev/null +++ b/docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md @@ -0,0 +1,390 @@ +# Review-loop economics: pass floor and severity semantics — Story + +**Date:** 2026-08-28 · **Size:** story +**Risk:** high · **Security:** none · **Validation:** battery+check+verification + +## 1. Problem statement + +Development drags. Review loops are opaque for minutes to hours at a stretch, and +Blocker/Major assessment is mis-calibrated, so a loop burns passes on trivia and on its +own test instruments before any product behaviour has been examined. (Daniel, 2026-08-26.) + +Four measurements, each citable: + +- **Loops do not converge on count.** In the kit's heaviest consumer, one design spec past + 2800 lines ran nineteen measured Gate-A passes; findings fell from 43 into a 2–19 range + after pass 6 and never reached zero, while Blockers fell from 11 to 0–1 from pass 7 on + (`CLAUDE.md:157-159`). **What that shows is that the count did not converge — and not that the + substance did.** Reading a falling Blocker curve as convergence is an inference + `docs/hardening-log.md` retracted at entries 78, 84, 85 and 87, and `CLAUDE.md` says in the same + paragraph that neither curve measures coverage, so a low count can sit beside an unreviewed + subsystem. The measurement is the numbers; the convergence reading is not part of it. +- **Severity lands on the instrument, not the product.** PR #23's closing commit records + that "of 27 Blocker/Major findings, 16 were in the never-committed scratch harness, 10 in + the design spec's narration, 1 in the plan" (`7bbdb14`, quoted at `todos.md:268-270`) — + 16 of 27 on the instrument, in that row's own summary of it. +- **The most recent cycle reproduced both.** Gate-B cycle `fic2` ran findings 14 · 24 · 12 · + 3 · 6 · 6 · 2 across seven passes. The pass-2 spike was caused by mid-cycle scope + expansion; pass 5 returned **zero** product-behaviour findings and still cost a full pass + each on two branches. Two mandatory stop-and-surfaces, one revert, and two false + counterfactuals that each survived a full clean pass before the next one caught them. + (`docs/field-reports/2026-08-26-fic2-cycle-evidence.md`.) +- **Individual passes are slow.** Five Gate-A passes of the 2026-08-03 round ran 458 s, + 550 s, 757 s, 663 s and 780 s (`todos.md:463-469`). That row states its own limit and this + story carries it unchanged: **no control run was made**, so this is a correlation observed + under one setting, not a demonstration of what caused those durations. + +A three-pass floor is charged to every cycle regardless of what the change is worth +reviewing, so a story with no behavioural surface pays the same toll as one that rewrites a +gate. + +**A worked example of the same class, found while writing this story.** The brief that +commissioned it cited "34 Gate-A passes on one 2848-line spec" against the 2026-08-17 ledger +row. That row says exactly that, and it has been superseded five times +(`docs/hardening-log.md:78,81,84,85,87`) — the line count is expressly excluded from the +consumer's evidence, and the pass total was taken from counting artifacts and then +attributed to a record that does not carry it. Supersession deliberately never alters a row, +so the row is still present, still matching its own grep, still counting, and still handing a +reader two retracted figures. Nothing was broken; the convention worked as designed. It is +recorded here as narrative evidence of what an opaque review record costs, not as a defect +this story repairs. + +## 2. Desired outcome + +Two outcomes, each observable in the shipped prompt text. + +1. **A cycle's mandatory pass count reflects what the story is worth reviewing**, instead of + charging every cycle the same three passes per gate. A story the profile puts at level 0 + stops paying a toll that buys nothing; a standard or high story keeps the floor it has + today. +2. **Severity means product behaviour.** A reader of either §5 copy can decide, **by a stated + test rather than by taste**, whether a finding about narration, about prose describing a + mechanism, or about a test instrument's internals is allowed to hold the loop open — the + test being whether an in-system reader takes a different decision if the text is wrong — and + knows that an instrument finding still holds the loop open whenever it shows the instrument + changes what the gate concludes about product behaviour, **in either direction**: a false + green, and equally a false red or a check that blocks a valid change. Coverage-first is + unchanged: the reviewer still reports everything, and the filter stays ours. + *(Revised 2026-08-28 after Gate-A pass 3. Two corrections: this outcome still described the + false-green-only carve-out that criterion 5 had already moved past; and "without judgement + calls" promised more than any prose rule can deliver — a stated test removes arbitrariness, + not judgement, and the spec says so plainly, so the story must not promise otherwise.)* +**Scope narrowed 2026-08-29 — the loop-rule consolidation moved to a successor story.** A third +outcome once sat here: answering the six open §5 contract questions in one coherent pass. +**Those answers are all decided and recorded**; what did not converge was writing them down. Gate-A +attribution over three consecutive passes measured it — parts 1 and 2 produced 4/1, 8/1 and 7/1 +Blocker/Major while the loop-rule sections produced 10, 11 and 14 and rising, each repair creating +an interaction the next pass found. See +`docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md`, which carries every +recorded decision as a settled input rather than reopening any of them. + +**Why parts 1 and 2 still ship together.** They are not merely adjacent: part 2's reachability test +is what decides a part-1 question — a path-derived `docs-only` arm would be wrong here because +`docs/hardening-log.md` is a `docs/**.md` path that drives rung escalation. Splitting *those two* +would separate a rule from the argument that settles it. + +**Named out of scope**, so no criterion below absorbs them: + +- **Hook code.** No change to any file under `plugins/dev-workflow/hooks/`. Part 1 is + prompt-only: the floor is derived from the profile and **stated in the text**, and the + already-shipped `.context/codex-gate.floor` knob is neither read for that derivation nor written + (Daniel, 2026-08-27; refined as the design settled). If design concludes prompt-only cannot hold, that is a stop-and-ask, not a + silent expansion. +- **Gate-call observability** — upstream in `mcp-codex-dev`, a different repo. +- **The pass-counter anomaly.** During `fic2`, seven validated passes were reported by the + hook as one. The cause is undiagnosed and diagnosing it needs hook-state inspection; it is + its own finding. +- **The CodeRabbit plan-metadata contradiction** — a docs follow-up, and only after + diagnosis. +- **The fixture-per-predicate question** — how much instrument a one-paragraph prose rule is + worth. The evidence doc carries it as an open question, not a commitment. +- **Any remedy to the supersession convention** the worked example above illustrates. Parked + as a `todos.md` candidate. +- **Whether loops actually got cheaper.** The honest form of that question is narrower than + "measure the rule's effect", and the design says why: no finding is ever classified under both + the old and new rules, so **no demotion figure is derivable** and what a later reader can compare + is *recorded severity mixes across cycles that reviewed different artifacts* — evidence about the + population as much as about the rule. It certainly cannot be observed inside this cycle, and + claiming otherwise from one cycle would be the fabricated-evidence failure §5 names. It is + **deferred to a named vehicle rather than to a new backlog row** — + `docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md` (the P8 + passive-metrics story, `todos.md:546`), annotated with this trigger: after roughly three + profiled cycles under the new rules, compare their recorded severity mixes against the `fic2` + baseline curve of 14 · 24 · 12 · 3 · 6 · 6 · 2, with both confounds named + (`docs/field-reports/2026-08-26-fic2-cycle-evidence.md`). A named vehicle rather than a + fresh row because loose deferred items rot here — the pass-counter anomaly, the + fixture-per-predicate question and the durations row's missing control run are all still + open, and all three are named above or in §1. + +## 3. Acceptance criteria + +- [ ] **The floor scales by profile, on one predicate, and both copies say which gates it + moves.** `CLAUDE.md` §5 and the `/workflow-init` inline template mirror each state a + mandatory pass floor of **1** only where the cited set is non-empty and every member is + profiled, resolvable and at level 0; **3** where no story is cited or any member is + unprofiled; and — stated, not left to a fallback — **a member whose profile is present but + unresolvable stops and surfaces**, per §5's existing rule, rather than yielding 3. A copy + that reads an unresolvable profile as 3 fails this criterion, because it converts a stop + condition into a silent default. Two levels, not three: `high` gets no + extra mandatory passes, taking its added rigor from lens sets and evidence mode instead. + **A cycle citing several stories reaches the reduced floor only unanimously** — every + cited story profiled, every one at level 0 — so a single higher-profile or unprofiled + member returns the cycle to 3. Stated in both copies, and falsifiable: a copy silent on + the multi-story case leaves the cheapest wrong reading available. + **Every pass report states the floor it derived, the risk and security axes it read, and + which cited stories it read them from**, so the value is + visible while passes are still being spent rather than only in the closing commit. + Each copy also states that **one derived value governs all three cycles** — the Gate-A spec + cycle, the Gate-A plan cycle and the Gate-B cycle, which §5 defines as **three separate + cycles** — and says *why*: they derive from **the same cited-story set**, not from being one + cycle. A copy that states the floor for one gate and leaves the others to inference fails + this criterion, and so does one that gives the shared-cycle reason, which is false. + Both copies also state that — **for a cycle §5 says is still running a gate**, the scope the + spec sets and which §5 alone decides — **a change to the cited set *or to any cited profile* + requires the final clean pass to run against the current set and under the current profile, + even when the floor number does not move** — a new member brings its + lenses and evidence duties with it — and that **removing a citation never discharges an + already accepted in-set Blocker or Major**, since acceptance and not the citation put it in + the fix set. + **One predicate, not two** (Daniel, 2026-08-28). An earlier draft added a `docs-only` + arm; it is dropped because it does no work and, read path-wise, does the wrong work. + A diff-derived reading cannot serve Gate A at all — Gate A runs on a spec, before any + diff exists. A story-declared reading is already subsumed: intake defines `trivial` as + "no behavioural effect in the artifact's own execution context", which a documentation + change has none of. And a path-derived arm would be **wrong in this repo specifically** — + `docs/hardening-log.md` is a `docs/**.md` path that drives rung escalation, so "docs" + does not imply "changes nothing". That is the design's severity test (part 2) deciding a floor + question, which is why the two parts belong in one change. +- [ ] **Every cycle's closing commit body carries its per-pass shape — all three cycle types.** + Both copies require the **Gate-A spec loop** (in the spec's commit body), the **Gate-A + plan loop** (in the plan's commit body) and the **Gate-B cycle** (in the closing amend) to + record that loop's per-pass **Findings, Blockers and Majors**, in one pinned greppable form, + following the `3cdd075` precedent + (`Findings 14, 24, 12, 3, 6, 6, 2. Blockers 3, 4, 0, 0, 0, 0, 0.`). The form is complete + enough that a reader can tell **which pass each number belongs to** — incomplete passes are + excluded and they consume pass numbers — and **which model each pass ran under**, which an + existing convention already requires beside a finding count. A **legitimately skipped** loop + records the skip rather than leaving a silent gap. **A count that cannot be recovered is + recorded as unknown rather than as zero, per series** — a pass may have one series lost and + the others intact — and a reader excludes an unknown value only from the comparisons that + read it. Both copies also state when separate calls + are **one logical pass**: only when they were **issued against the same tracked revision**, + which is what the retained request values establish and is not a claim that either branch + read it — the reviewer reports no reviewed revision. A mismatch **ends the first as an + incomplete pass** before the later one starts a new pass, rather than merging two + revisions into a single entry. Checkable: the requirement is + stated in both copies, and this story's own commits carry it for each cycle that ran — + **every field of the pinned form except the identifier, which no cycle on this branch can + supply.** Those records carry the reserved pre-rule value, which by construction **does not + name a cycle**; what the branch demonstrates is the field's presence and grammar, not + attribution (see the identifier criterion). + **All three, not Gate B alone** (revised 2026-08-28 after Gate-A pass 2). A Gate-B-only + requirement would leave the *dominant* cost unrecorded: the loops this story cites as + evidence are Gate-A loops — nineteen measured Gate-A passes on one spec, and this story's + own Gate-A run — so P8 without Gate-A curves would be comparing the smaller half of what the + problem statement is about. + **Why it is in scope** (approved as a scope addition, Daniel, 2026-08-28): the deferred + economics comparison routed to P8 is otherwise answerable only for cycles whose author + happened to write the curve down — `3cdd075` and `baa75c1` did, `7bbdb14` recorded the + pass total and no distribution — because the findings files behind those numbers live + under gitignored `.context/`. It is also the durable half of Q6: a curve in a commit body + survives a fresh checkout, a cleared `.context/` and a different machine. +- [ ] **Every cycle's floor is reconstructible from history alone, default or not.** From a + cycle's closing commit body, without consulting the spec or any per-clone state, a reader + can determine: **which floor §5 obliged**, **which stories derived it**, and — where a + user's workspace knob was set — **that its number is the hook's reminder threshold and not + the obligation**. Every cycle, so an absent record is never ambiguous between "the default + applied" and "someone forgot". The record is **machine-extractable**, because the deferred + P8 measurement reads it; both copies pin one form, and the spec states which. +- [ ] **The gate-off residual is disclosed in the shipped text, not merely avoided.** A reader of + either copy learns, without inferring it: **that the floor a cycle owes is produced by the + agent rather than established by any mechanism**; **that nothing checks it against the + cited profiles**; and **by what specific routes it can therefore be wrong** — the routes + enumerated, and the enumeration stated as a floor rather than as a complete list. The text + claims **no guard**: a reader must not be able to come away believing the residual is + mitigated by anything the design ships. Checkable by reading either copy. + **And both copies oblige the agent to leave the user's floor knob alone** — never written, + never removed by any rule this change ships; where one exists a cycle discloses it rather + than acting on it. Observable on this branch: a knob present before a cycle is byte-identical + after it. +- [ ] **Severity is decided by one stated test in both copies, and the test is keyed on + consequence.** + Five properties, each checkable by reading either copy. + **(a)** Exactly **one** procedure decides severity. A copy that states a categorical + demotion by subject *alongside* the test fails, because two procedures can disagree on one + finding; subject-based cases may appear only as worked examples of the test. + **(b)** The test requires **both** halves to be nameable: **what in the system consumes the + text**, and **the decision it takes differently** if the text is wrong. Failing to name + either makes the finding Minor-or-below. It does not turn on what kind of file the text + lives in, nor on a human reader, whose cost §5's prose exemption already prices as + non-gating. A copy demoting only when *both* are absent inverts the rule. + **(c)** The instrument carve-out is **symmetric**: an instrument finding keeps its severity + whenever it shows the instrument changes what a gate concludes about product behaviour, + in **either** direction. A copy naming only a false green fails, because it would demote a + check that fails for wiring reasons and costs a correct change. + **(d)** Coverage-first survives: the reviewer still reports every finding with severity and + confidence, and the filter is ours. A copy that drops this fails even if (a)–(c) hold. + **(e)** **The pass raising a finding is not an in-system reader of the text it is + reviewing.** Without this the test demotes nothing — any review finding could name the + review itself as the reader — so a copy stating (a)–(d) and omitting (e) fails. Gates must + remain legitimate readers of rule text they will later apply; what is excluded is the + reviewing pass, not gates. + +- [ ] **A cycle's records can be told apart from another cycle's.** Both copies require **each + cycle started after these rules ship** to hold an identifier created at its start, unique + among cycles open when it was generated, and present in **a named set of records** — the + provenance line, the per-pass curve (or the skip record standing in for one), the cycle's + findings slots, and its advisory working record — **and not required in records this change + neither introduces nor keys to a cycle**, so a reader can tell whether a given record is in + scope — **one per cycle, so a run of all three holds three**. Both copies also state the **bounded, self-terminating exception**: + a cycle already running when the rules land has no identifier, cannot acquire one, and writes + a reserved value that says so — and no later cycle can enter that state. They + state how it is recovered by a cycle resumed after an interruption, what happens when it + cannot be recovered unambiguously, that a cycle does not start without one, and that a slot + another cycle owns is refused rather than overwritten. Checkable by reading either copy, and + **What this branch can and cannot demonstrate, stated rather than assumed:** its three cycles + all began before these rules ship, so each closing body carries the reserved pre-rule cycle + field and **none of them demonstrates a real identifier**. The branch demonstrates the field's + presence and grammar; **a future cycle demonstrates a real one and the attribution it buys** — + the obligation is recorded at implementation and discharged by **any** cycle that both starts + under these rules and closes — not assigned in advance to a cycle that concurrent siblings and + a possible revert make impossible to name, and not to "the first", which concurrent closers + cannot agree on. A duplicate discharge is harmless and allowed. +- [ ] **The shipped text says when it starts binding, and what an adopter gets when it does not + fully arrive.** Both copies state: that a loop already running finishes under the rules it + started with; what a loop does when its starting rules cannot be established, covering + **every part this change touches and not the floor alone**; and that a downstream project + adopts by re-running the scaffolder, which may write nothing, be declined, or be merged in + part — with the consequence of a partial adoption stated rather than assumed away. + Checkable by reading either copy. +- [ ] **The change leaves no shipped sentence contradicting it, and the package it ships in is + valid.** Every user-facing statement this change falsifies is corrected in the same change — + a reviewer can check each corrected line against the site list the plan carries — and the plugin + manifest's version and `CHANGELOG.md` are updated. Both are checkable after the fact: no + corrected sentence still asserts a fixed three-pass floor, and the manifest version differs + from its value on the base ref. **Only the version bump is CI-enforced** — the changelog + entry is this repo's convention and nothing checks it, so this criterion is what carries it. + **Prompt conformance is judged item by item, and this change records exactly one n/a**: + item 1 for the scaffolded `CLAUDE.md`, because that artifact is model-agnostic by design and + a target-model line would be false in every repo it lands in. **Every other item binds it, + and every item binds the outer command prompt.** An n/a is admissible only where the reason + **establishes that the item cannot truthfully be met** — the answer `AGENTS.md`'s commands + table already gives; "we would rather not" is not such a reason. What fails this criterion is + an item left unanswered, answered falsely, or answered n/a on a reason that does not hold. +- [ ] **Every condition of the replaced prose is accounted for.** The change lists what each + replaced §5 passage required and marks each requirement kept, moved, or deliberately + dropped, per the AGENTS.md Don't quoted in §4. Checkable: the accounting exists and + covers every rule the diff rewrites. The failure this guards against has occurred: the + profiles cycle lost conditions ten times, once making an eligible profile *sufficient* + for a Gate-B skip — the gate-off path that change existed to close. +- [ ] **The two copies stay in parity.** Every rule this story changes reads the same in + `CLAUDE.md` §5 and in the `/workflow-init` template, except where a wording difference is + deliberate and stated as such. Checkable by diffing the two regions. +- [ ] **The provenance path is demonstrated on this branch as far as this branch can demonstrate + it, at the floor it actually licenses.** Not "end to end": two fields are out of reach here + and the criterion names both rather than claiming coverage it lacks — the **cycle identifier** + (every cycle here predates the rule) and the **user-knob clause's non-absent form** (this + workspace has no knob, so the design's conditional verification records not-applicable rather + than satisfied). Every other field is shown. This story is risk `high`, so every cycle citing it owes + floor **3** — and its closing commit body carries a provenance line **in the one pinned form**, + recording that floor and this story at its level, so the derivation and the line are shown + working at a real value. **The cycle field is the one it cannot demonstrate**: every cycle on + this branch predates the identifier rule, so each carries the reserved pre-rule value, which + by construction attributes nothing. This branch shows the line's derivation, its grammar and its + remaining fields; **attribution is demonstrated at the checkpoint named in the identifier + criterion**, not here. + **The floor-1 demonstration is not on this branch, deliberately.** It was in an earlier + draft and was unsatisfiable: a criterion demanding a floor-1 cycle here contradicts this + story's own profile, and the only way to satisfy it as written would have been to mint a + level-0 micro-story for the purpose — a fixture built to make a criterion pass, which is + the fabricated-evidence class §5 names. Instead the floor-1 case becomes the **first + checkpoint of the P8 comparison**: the first post-merge cycle whose cited-story set licenses + floor 1 must carry the floor-1 provenance line, and P8 reads it. + **What is demonstrated here stays bounded, and the wording forecloses reading it wider:** + the derivation and the provenance path work. It is not evidence that review + loops became cheaper. Reading a working mechanism as an improved outcome is the overclaim + class AGENTS.md names as this repo's most persistent defect, and the economics are + compared afterwards by the follow-up named in §2 — compared, because §2 explains why no + measurement of the rule's effect is derivable from what the record carries. + *(Revised 2026-08-28 after Gate-A pass 1 found the original unsatisfiable — sparring + session, under Daniel's 2026-08-28 delegation; flagged to Daniel for final-version review + because the provenance-demonstration criterion was his explicit choice. The falsifiability he chose is preserved by + the design's §8 differential verification, the provenance line now, and the P8 checkpoint later.)* + +## 4. Affected AGENTS.md invariants + +- `## Hook` — "**The hook always exits 0.** It is advisory; a reminder that can fail closed + would make the workflow unusable whenever Codex is down or the environment is odd." The + prompt-only decision for part 1 rests on this: the hook's floor was only ever a reminder + threshold, and §5's text is what obliges an agent — so deriving the floor in the text adds no + new enforcement class and removes none. +- `## Hook` — "**Loose in the firing direction.** On uncertainty, fire. A missed commit + (false ✓) is the dangerous direction; a redundant warning is the accepted price." This is what + licenses the accepted cost: a level-0 cycle draws a hook reminder its derived floor does not + owe, and a redundant warning is the price named here. Teaching the hook to fall silent would + be the false ✓ the same sentence calls dangerous. +- `## Prompts and scaffolding` — "**`/workflow-init` never overwrites silently.** Idempotent: + missing → write; identical → report unchanged; present and different → show the diff and ask." + This is why a downstream project adopts by re-running the scaffolder and can sit on a partial + adoption — the spec's rollout section rests on it. +- `## Prompts and scaffolding` — "**The base taxonomy stays stack-neutral.** Project vocabulary… + goes only in that project's `docs/hardening-taxonomy.md`… Otherwise one project leaks into every + other." The severity test ships into projects whose reader kinds we have never seen, which is + why its list of readers is illustrative rather than closed. +- `## Prompts and scaffolding` — "**`/workflow-init`'s templates stay inline** in the command + body." The mirror edit lands in the command body, never in a file read from disk. +- `## Prompts and scaffolding` — "**Prompt changes pass `docs/prompt-standards.md`** — all 12 + checklist items… **no comprehensive mechanical checker exists for them**: review is the + gate." Named because one of the three narrow checks that *do* exist is "the finding-severity + vocabulary stated as a closed set in both prompt copies", which part 2 edits directly. +- `## Packaging` — "**A plugin change requires a version bump.** A pull request that changes + any path under a `plugins//` directory **that still exists at HEAD**… must also change + that plugin manifest's `version`, or CI fails." The template mirror is under + `plugins/dev-workflow/`, so this fires. +- `## Don'ts` — "**Never replace a decision procedure without accounting for its old + conditions.** List what the previous prose required, then mark each one kept, moved, or + deliberately dropped." This is the governing constraint on the accounting and the basis of its + criterion. +- `## Don'ts` — "**Never describe what a gate proves without checking what it actually + compares.**… for every sentence about a gate, name the exact comparison the code performs, + and delete any part of the sentence that outruns it." Every sentence this story writes about + what a floor or a severity class *does* is subject to it. + +## 5. Open questions + +- ~~**Which floor governs a cycle citing several stories with different profiles?**~~ + **Answered 2026-08-28: unanimity.** Floor 1 only if the cited set is non-empty and every member + is profiled, resolvable and at level 0. A set with no story or any unprofiled member yields 3; + **a set containing a present-but-unresolvable profile stops and surfaces** rather than yielding + anything. It follows §5's own precedent for the analogous + relaxation — "skip-eligible only if **every** cited story is" — and it is the only reading + consistent with invariant 2's firing direction, since a lowest-cited-floor rule would + under-review a cycle that also touches a high-risk story. +- ~~**Does a profile change mid-cycle move the floor for passes already run?**~~ + **Answered 2026-08-28**, and it needed no new rule — three existing ones compose. The floor + derives from the current profile at each pass; passes already run keep counting; closing + requires the floor as currently derived. The consequence worth stating is that **any profile + change costs at least one further clean pass, in either direction and whether or not the floor + number moves**, because §5 already requires the final clean pass to run under the current + profile — so no already-banked pass can be it. +- ~~**What floor does an unprofiled cycle get?**~~ **Answered 2026-08-28** by the + single-predicate decision in criterion 1: no profile means `max(risk, security)` is not 0, + so the default of 3 stands. Kept rather than deleted, because the answer is only obvious + once the predicate is one thing. + +## 6. Suggested size + +`story` — two coupled parts, one change to one subsystem's rules in two mirrored copies. + +**This note said the opposite until 2026-08-29, and the correction is worth keeping.** It read: +"Part 3's value *is* being done once; splitting it would reproduce the clause-by-clause churn the +story exists to end." **That reasoning was about the contract questions**, which were genuinely +entangled — answering one moved the others — and it was right about them. They are now all settled +and recorded, so the entanglement it described has been paid for. + +**What did not converge was a different problem with a different remedy.** Eleven Gate-A passes +never brought Blocker/Major below 22, and the last three attributed 10, 11 and 14 of them to the +loop-rule sections while parts 1 and 2 held at 4/1, 8/1 and 7/1. Each repair to one loop rule +created an interaction the next pass found — density among shipped rules, not entanglement among +open questions. Splitting is the standard remedy for the first and the failure mode for the second, +which is why the same story can correctly refuse a split and then correctly take one. diff --git a/docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md b/docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md new file mode 100644 index 0000000..b094ec5 --- /dev/null +++ b/docs/superpowers/stories/2026-08-29-loop-rule-consolidation-story.md @@ -0,0 +1,161 @@ +# §5 loop-rule consolidation: exits, duties, and the decline — Story + +**Date:** 2026-08-29 · **Size:** story +**Risk:** *(proposed)* high · **Security:** *(proposed)* none · **Validation:** *(proposed)* battery+check+verification + +> **DRAFT — the profile above is proposed, not confirmed.** Per §5 a profile is confirmed by the +> human, and until it is this story is not executable. Nothing depends on it yet: the work it +> describes is split out of a cycle that is still running, and the successor starts when someone +> picks it up. The proposal's reasons are in §5. + +## 1. Problem statement + +**§5's loop has four exits and four standing duties, and nowhere says which wins when two apply at +once.** Clean completion, the scope stop, the clearly-stuck exit and the two-tell stop; the floor, +the Blocker/Major-resolve duty, the rule that a surfaced finding stays open, and the rule that no +pass carrying one counts as clean. Every one is stated in its own paragraph, each qualifying the +ones before it, and the ordering exists only in a reader's head. + +**That gap has already cost a cycle.** The `fic2` cycle could not ship two small clauses without +qualifying three rules nobody had proposed changing, and reverted — correctly. The revert is the +evidence: two clauses met an unordered lattice and the lattice pushed back. +(`docs/field-reports/2026-08-26-fic2-cycle-evidence.md`.) + +**Why this is a story rather than a paragraph.** The answers are not the hard part — they are all +settled and listed in §4 below. **Writing them down is.** Eleven Gate-A passes on the parent +story's spec never brought Blocker/Major below 22, and the last three attributed **10, 11 and 14** +of them to exactly these rules while the floor and severity parts held at 4/1, 8/1 and 7/1. Each +repair to one loop rule created an interaction the next pass found. **The subject here is +interaction density among rules that all bear on one decision — may this cycle close — and it needs +its own artifact and its own review budget.** + +**Parent:** `docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md`, which +ships the pass floor and severity semantics and whose §6 records why the split was refused first +and then taken. + +## 2. Desired outcome + +**One stated ordering, written once, that a reader can apply without inferring it** — after which +the individual rules reference the ordering instead of qualifying each other. Specifically, a reader +of either §5 copy can answer, without judgement: which exits *close* a cycle and which merely +*suspend* it; what happens when two apply at once; which duties participate in that ordering and +which are preconditions; and what a user's answer on a surfaced finding does in **both** directions. + +**Out of scope**, named so nothing absorbs them: +- **The pass floor and severity semantics** — the parent story ships those, and this story treats + them as given rather than adjusting them. +- **Reopening any decision in §4.** They are settled and paid for; the design starts from them. +- **Hook code** (anything under `plugins/dev-workflow/hooks/`), unchanged from the parent. +- **The pass-counter anomaly**, the CodeRabbit plan-metadata contradiction, and the + fixture-per-predicate question — all still parked. + +## 3. Acceptance criteria + +- [ ] **The ordering is stated once, in both copies, and covers every reachable conflict.** A + reader can determine which exits close and which suspend, and what happens when two apply + together, without inferring it from paragraphs that qualify one another. Conflicts that + **cannot** co-occur are named as such with the reason, rather than given invented rules — an + ordering that legislates for unreachable states rebuilds the broken instrument the `fic2` + record already documents. +- [ ] **Every duty is classified, and each classification is visible.** For each of the four + standing duties, both copies say whether it participates in the ordering or is a + precondition on closure — and a duty that is a precondition says what it gates and what + discharges it. +- [ ] **A user's answer on a surfaced finding has one rule covering both directions**, and the + rules it modifies carry the qualification **at each rule it modifies and at no rule it does + not**. Both halves are falsifiable by reading: a modified rule that does not mention it + leaves two instructions disagreeing; an unmodified rule that mentions it implies an exception + that does not exist. +- [ ] **No path leaves a cycle unable to close and unable to stop.** Every terminal condition the + change introduces has an answer that changes the cycle's state. Checkable by walking each + stop the shipped text names and asking what the next state is — the parent cycle shipped a + stop whose only answer resumed a cycle that immediately stopped again, and that is the + failure this criterion exists to catch. +- [ ] **Every condition of the replaced prose is accounted for** — for each rewritten passage, what + it required, each requirement marked kept, moved or deliberately dropped, per the AGENTS.md + Don't. A requirement neither kept nor explicitly dropped is a dropped condition. +- [ ] **The two copies stay in parity** on every rule this story changes, deliberate wording + differences stated as such. + +## 4. Settled inputs — decided, paid for, and not to be reopened + +Each was confirmed by Daniel during the parent cycle. **The durable record is that cycle's commit +bodies**, which carry every one of them. `docs/field-reports/2026-08-26-fic2-cycle-evidence.md` is +where the *questions* were parked, not where they were answered — it states Q3–Q5 as unanswered and +gives Q6 only as candidate answers, because it was written before the answers existed. Citing it as +the source of the decisions would send a reader to a document that predates them. **The design +begins from the table below.** + +| # | Decision | +|---|---| +| 1 | **Only clean completion closes a cycle.** The scope stop, the clearly-stuck exit and the two-tell stop **suspend** — they surface and the loop resumes. Two suspensions at once compose; the report carries both reasons. | +| 2 | **Clean completion outranks the two-tell stop** (Q1). | +| 3 | **Clean completion outranks the clearly-stuck exit** — §5 already says so and the sentence is preserved verbatim. | +| 4 | **A surfaced finding holds closure while it awaits the user's answer; any answer ends the hold, in either direction** (C3/Q5, and the accept branch). After the answer the ordinary rules govern. | +| 5 | **A decline releases the hold and never qualifies the Blocker/Major-resolve duty**, which applies to in-set findings a decline never reaches. | +| 6 | **A decline is available only for a finding surfaced by a scope stop.** An in-set Blocker or Major cannot be declined; treating it as declinable would make this a general waiver. | +| 7 | **A decline binds for the remainder of its cycle and has no effect in any later one** (Q4). | +| 8 | **"Explicitly declined" is a recorded user decision on that specific finding**, attributable and unambiguous — never silence, never a general remark about scope, never inferred. | +| 9 | **The decline is recorded in the commit body**, reusing the human-exception transport as a **distinct record type** — the two differ in force, since the human-exception form authorizes nothing. | +| 9b | **The record stores exactly what the sameness test reads: location, defect, severity, consequence and suggested fix.** A test reading fields the record lacks is a wiring failure. Sameness requires all five to match; **any difference — including severity, since a Minor re-raised as a Blocker is not the thing that was declined — makes it a new finding and the hold applies**, as does any genuine uncertainty. | +| 9c | **The record reads as an unverified assertion**, like the human-exception record beside it: nothing checks that the handle belongs to whoever decided. **Narrowness bounds what a false record can do — one fully-identified finding, one cycle — and that is not the same as making it safe**; a fabricated decline still releases a real hold and nothing detects it. | +| 10 | **Q6:** when prior-pass history is unavailable, the pass report states what is computable, names what is not and why, and discloses the reduced sensitivity. Not a new stop condition, not a mandatory resume note. | + +**Two implementation facts the parent cycle established, carried so they are not rediscovered:** a +pass's cleanliness is a fact about what that pass found and is **never rewritten** — what a later +answer changes is whether the *cycle* may close; and the findings files establish the **inventory** +of in-set findings, not their resolutions, which they do not contain. + +### Three passages this story shares with the parent + +Both changes rewrite these, and **this story must extend rather than replace what the parent added** +— the parent's accounting already covers its own half: + +- **The squash-carry rule.** The parent adds the provenance line, the per-pass curves and a skipped + loop's skip record to what a squash must carry. **This story adds the decline record**, and a + rewrite that drops the parent's three would silently unship them. +- **The unknown-start fallback** (what a loop does when its starting rules cannot be established). + The parent covers five parts at their strictest — floor, severity, the provenance line, the curve, + and the nonce duties. **This story extends + the same list to the loop rules it ships** — suspensions binding, decline records + treated as absent so no hold is released. Extending is safe; replacing is not. + +A third, for the same reason: the **clearly-stuck paragraph** is rewritten by both — by the parent +because its "pass 1 carrying a Minor" sentence is false under a floor of 1, and by this story +because the exit becomes a suspension in the ordering. **Both accountings owe it.** + +**A fourth, handed over rather than shared — the demotion/loop-health interaction.** The parent's +severity test demotes a finding when nothing in the system takes a different decision from it. +That bears directly on the loop-health measures this story owns: what a demoted finding does to +the per-pass counts, to the finding clusters, and to the stop thresholds. The parent **does not +settle it** — three of its revisions tried, each reaching past its own §9 exclusion of "the §5 +loop-rule consolidation and everything its successor story owns", and each removal found another +layer underneath. What it ships is one sentence saying the interaction is **not settled +there**, together with the conservative action for a pass that would turn on it — report the +question and stop rather than deciding it. Only this repository's `CLAUDE.md` also names this +story as the owner; the scaffolded template deliberately does not, because it writes into +projects where this path does not exist. **This paragraph is the reciprocal**: the obligation is named in both documents so it +cannot fall between them, which is the failure the passage list demonstrated in that same cycle. +The design owes an answer covering at least: whether a demoted finding still counts toward the +finding total and the clusters, and whether the Blocker curve reads severity before or after the +ceiling. + +## 5. Open questions + +- **The profile.** Proposed `high / none / battery+check+verification`, on the same reasoning the + parent used: the surface is the review gate itself, and a wrong rule mis-steers every future + cycle. **No named `high` trigger matches literally**, so this is a judgement call under intake's + "surfaces, not words", and the human decides it. The parent's experience is evidence for rather + than against: eleven passes and three mandatory stops on this material. +- **How much of the ordering is new text versus reference.** §5 already contains the two sentences + from which "only clean completion closes" follows; whether the ordering is stated fresh or + assembled from what is there changes the old-conditions accounting and the parity surface. +*(Resolved 2026-08-29: the cycle nonce stays with the parent, which ships it because both of the +records parts 1+2 produce carry it. **This story consumes it and does not define it** — it uses the +nonce to bind a decline to one cycle, per settled decision 7.)* + +## 6. Suggested size + +`story` — one coherent change to one subsystem's rules in two mirrored copies. Not `chore`: it +answers a structural question and rewrites standing duties. Not `epic-needs-splitting`: the +decisions are already made, which is what made the parent's version expensive. diff --git a/plugins/dev-workflow/.claude-plugin/plugin.json b/plugins/dev-workflow/.claude-plugin/plugin.json index c7b63d7..5599cbd 100644 --- a/plugins/dev-workflow/.claude-plugin/plugin.json +++ b/plugins/dev-workflow/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "dev-workflow", "displayName": "Cross-Model Review Workflow", - "version": "0.10.0", + "version": "0.11.0", "description": "Spec-driven workflow with two independent cross-model review gates, an append-only hardening ledger with an escalation ladder, and repo-enforced quality. Requires the superpowers plugin.", "author": { "name": "Daniel Sänger", diff --git a/plugins/dev-workflow/CHANGELOG.md b/plugins/dev-workflow/CHANGELOG.md index 02eac0f..526f194 100644 --- a/plugins/dev-workflow/CHANGELOG.md +++ b/plugins/dev-workflow/CHANGELOG.md @@ -22,6 +22,47 @@ unambiguously, still fails. Deleting only a plugin's *manifest* while the direct keeps shipping fails too. AGENTS.md invariant 12 carries the complete list. +## 0.11.0 + +- **The mandatory pass floor is now a function of the cited story's profile**, not the constant 3. + `max(risk, security) == 0` gives a floor of **1**; every resolvable profile above that, and an + artifact citing no story, gives **3**. A cited story whose profile is present but unresolvable + **stops and surfaces** rather than defaulting. Across a cited set the floor is 1 only if the set + is non-empty and every member is profiled, resolvable and at level 0. +- **The hook's ratio is a reminder threshold and controls nothing.** It always did; the text now + says so, and the `codex-gate.floor` knob is described as moving that threshold rather than the + obligation. `README.md` and `docs/getting-started.md` carried the old description and are + corrected. +- **Finding severity is decided by whether something in the system takes a different decision.** + Name what consumes the text and the decision that changes if it is wrong; if you cannot name + both, the finding is Minor or below. The review pass raising a finding is not an in-system + reader of the text it reviews, gates remain readers of rule text they will later apply, and a + human reader never satisfies the test. It sets a ceiling, never a floor, and never chooses + between Blocker and Major. +- **Two commit-body records are pinned**, so that a program can parse them — P8's deferred + measurement is the intended consumer, and **no parser for either form ships today**; the + evidence for this release matched constructed strings against the grammars instead: a + **provenance line** + carrying the cycle field, the derived floor and the cited set that produced it, and a + **per-pass curve** carrying Findings, Blockers and Majors per pass. One of each per cycle — a + change running five cycles records five. +- **A cycle nonce** attributes those records. Eight to sixteen characters from `[a-z0-9]`, from a + source of randomness, never derived from a name, timestamp or commit. It is + collision-**resistant**, not collision-proof, and the shipped text says where that bound bites + rather than implying a guarantee. +- **Findings slots take a per-cycle infix**, and the deletion step §5 already requires deletes + only paths carrying the cycle's own infix. A cycle that holds a **nonce** uses it; a cycle with + **no** nonce keeps the **bare** names. That rule exists because a bare slot was overwritten + during this change's own development, destroying a previous cycle's findings file. + **What this release does not ship** is a rule for the remaining case — a nonce-less cycle in a + workspace that already holds bare-slot files, where the bare names would collide with somebody + else's. That case is real: this release's own Gate-B cycle is one, and it used a recorded + plan-local naming exception rather than a shipped rule. A general slot production for it is + deferred to the loop-rule consolidation story. +- **What this change does not settle** is how demotion bears on the loop-health measures — the + per-pass counts, the clusters and the stop thresholds. That is the loop-rule consolidation + story's, and both documents say so, so the obligation cannot fall between them. + ## 0.10.0 - §5's gate loop gained two rules it was missing, both mirrored into `workflow-init`'s scaffolded diff --git a/plugins/dev-workflow/commands/process-pr-review.md b/plugins/dev-workflow/commands/process-pr-review.md index ced1a7b..46bdf72 100644 --- a/plugins/dev-workflow/commands/process-pr-review.md +++ b/plugins/dev-workflow/commands/process-pr-review.md @@ -148,18 +148,30 @@ be ambiguous, which is not an instruction. - **every cited story is eligible.** Resolve the cited story path(s) first, looking in **both** the PR body and the **commit bodies in the range** — the workflow puts the story path in the closing commit message, so a PR that cites nothing in its - description may still be profiled. Only when neither carries a citation is this §5's - "no story cited" case, which takes the unprofiled judgement call. A profiled story is + description may still be profiled. **This reads a record, and is not §5's + governing-set derivation**, which takes the cited set from a reviewed artifact's own + `Story:` header and forbids grepping an artifact body for story paths. A closing + commit body carries the path deliberately, as an evidence entry; an artifact body + mentioning one may be a disclaimer. Different question, different source — and this one + never relaxes the other: where no record carries a citation, that is an absence **in the + PR record**, and it takes the unprofiled judgement call for *this command's* skip + decision only. It is not §5's "no story cited" case, it cannot set a floor, and it can + neither override nor substitute for a governing set derived from a reviewed artifact's + `Story:` header. Where neither carries a citation, take the unprofiled judgement call + for this decision — without calling it §5's "no story cited" case, which is about a + reviewed artifact's header and not about this record. A profiled story is eligible only at effective level 0 (risk `trivial` *and* security `none`); an unprofiled story is eligible on the old judgement call. With several cited stories, each must be eligible on its own; one eligible story does not carry the rest. A cited profile that - is **present but unresolvable** (§5's third case) stops the run — surface the cause; + is **present but unresolvable** (§5's fourth case) stops the run — surface the cause; it is never treated as unprofiled. A skip removes the review and never the evidence. Every skipped cycle runs the battery - and records, in the commit body, **the skip reason and the battery result**. On top of - that: one mode-derived evidence entry per cited **profiled** story, and none for an - unprofiled one — which owes the reason and battery result and nothing further. + and records, in the commit body, **the skip reason and the battery result** — and, like + every other cycle, **its provenance line and, in place of a curve, its skip record**. On + top of that: one mode-derived evidence entry per cited **profiled** story, and none for an + unprofiled one — which owes the reason, the battery result, the provenance line and the + skip record, and nothing further. 4. Stop and ask the user for: every `escalate-to-user` verdict, and every accepted finding that contradicts a settled decision. Do not implement these. A finding already recorded as out of scope by item 2 does **not** come here — it is terminal diff --git a/plugins/dev-workflow/commands/workflow-init.md b/plugins/dev-workflow/commands/workflow-init.md index 3895394..d72087e 100644 --- a/plugins/dev-workflow/commands/workflow-init.md +++ b/plugins/dev-workflow/commands/workflow-init.md @@ -189,6 +189,13 @@ If a `CLAUDE.md` already exists with unrelated project content, do not overwrite offer to **append** sections §1–§5 (renumbering only if the file already uses those numbers) and say so in the report. +> **Prompt-standards item 1 for the scaffolded `CLAUDE.md`: n/a, and why.** The file this +> template writes is model-agnostic by design — its executing model is whatever the reader of +> that project runs — so a `Target model:` line inside it would be false in every repo it lands +> in. Recorded as a reasoned n/a rather than skipped: the item is answered. **This note sits +> outside the fence** so it never scaffolds, and is deliberately **not** a `Target model:` line, +> which would make this file's declaration count 2 and fail `scripts/check-invariants.sh`. + ````markdown # @@ -269,16 +276,129 @@ counters and Gate B reports "not run" forever. A mapped name must itself start w to the hook or, for `Bash`/`Skill`, hijacks a lifecycle event. Register the server as `codex` to place its tools there. -**Both gates are a LOOP with a HARD FLOOR: min 3 passes per run (Blocker/Major -only), counted by the hook.** The hook counts passes but can't read findings or -tell the spec run from the plan run (it resets at `writing-plans`), so Gate A — -the spec run especially — is instruction-backed: a satisfied count is not a clean -review. Open a TodoWrite "Codex pass N" per pass; fix Blocker/Major after each. Your -final pass must be clean — if pass 3 still finds Blocker/Major, keep going until +**Both gates are a LOOP with a HARD FLOOR: a minimum number of passes per run +(Blocker/Major only), derived from the cited story's profile.** +The derivation is max(risk, security): a value of 0 gives a floor of 1; every +resolvable profile above that, and an artifact citing no story, gives 3. Two levels, +not three — `high` takes its rigor from lens sets and evidence mode, not from extra +passes. A cited story whose profile is present but unresolvable stops and surfaces +under the existing rule; it does not fall through to 3, because reading it as 3 would +turn a stop condition into a silent default. Across a cited set the floor is 1 if and +only if the set is non-empty and every member is profiled, resolvable and at level 0 +— all four conditions, since "every cited story" is vacuously true of an empty set; +no story cited, or any cited story unprofiled, gives 3. One derived value governs all +three cycle *kinds*: the Gate-A spec loop, the Gate-A plan loop and the Gate-B cycle. Not +because they are one cycle — they are separate cycles, and a change carrying several plans runs +a Gate-A plan cycle per plan — but because they derive from the same +cited-story set. That value is a function of the current confirmed profiles of that set, +read fresh wherever this section already requires them to be read, so wherever a value can be +derived at all there is exactly one, because there is one source. Some states derive **no** value +rather than a second one, and each stops rather than defaulting: governing headers that +disagree; a cited profile that is present but unresolvable; and a `Story:` header that cannot +be read. A change to a profile or to the set +therefore binds every open and future cycle — a raise costs an affected open cycle a +further pass under the current profile, as the profile-change rule below requires — while a +cycle that has already closed +stands, its close having been valid under the profile current when it closed, which is the +cycle-level form of passes already run keeping their count. **The set has one authority: +the artifact's `Story:` header, which carries the path of every cited story.** Nothing else +is a citation. A story path appearing anywhere else in an artifact's body — including a +sentence placing a story *outside* this change's scope — **contributes nothing to the cited +set and nothing to the floor derivation**, which is the only claim made about it; it may still +be a perfectly good cross-reference for any other purpose. And **an agent +deriving the set reads that header and does not grep the body for story paths**, because a +grep finds mentions and cannot tell a citation from a disclaimer. **Each cycle's governing header is the +header of the artifact it reviews**: the spec's for the Gate-A spec loop, the plan's for the +Gate-A plan loop, and — since a Gate-B cycle reviews a diff and has no header of its own — +**the union of the `Story:` headers of every plan contributing to that diff, which the Gate-B +call must carry in full**, as this section already requires of every cited path. **Every expected artifact contributes a set — a spec, and every plan contributing to the +reviewed diff — and an expected artifact whose `Story:` header is absent contributes the empty +set rather than dropping out of the comparison.** **One path per entry**, and "entry" is +decidable against the form these artifacts actually carry: a single line beginning `**Story:**`, +then one or more paths, **each wrapped in backticks**, separated by `, `. Trailing prose after +the last path is allowed and contributes nothing — several headers carry a reminder to read the +profile fresh, and a reminder is not a citation. So a header citing several stories carries several entries, +one path each. Exact duplicate paths are one member; entries naming different stories are +different members; and a header that does not parse as that line is malformed and stops, +reporting that as the cause rather than as a disagreement. **Before each pass the deriving agent +compares every such set, and again before a clean pass is accepted as the cycle's final pass.** +A header or profile that changed during that pass means the pass is not final — the same +answer a change gets at every other read point. Where they name different sets the premise of a single value has +failed: **stop and surface the disagreement** rather than deriving from either, exactly as an +unresolvable profile stops rather than defaulting. + +**The derived floor is the pass count a cycle owes, and the hook's ratio is a reminder +threshold that controls nothing.** The hook still counts passes, and it still can't read +findings or tell the spec run from the plan run (it resets at `writing-plans`), so +Gate A — the spec run especially — is instruction-backed: a satisfied count is not a +clean review, and a below-threshold reminder is noted in the pass report and disregarded +where the cycle's own closure rules are satisfied. This replaces the pass-count number +and nothing else. Every other rule stated here about how a cycle closes stands as +written, and none of them is restated — a summary is where their conditions would get +dropped. Nothing here writes the floor knob: it stays the user's, never written, never +removed, never read for this derivation. Open a TodoWrite "Codex pass N" per pass; fix Blocker/Major after each. Your +final pass must be clean — if the pass at the floor still finds Blocker/Major, keep going until clean or clearly stuck → then STOP and surface to the user. The only early exit -below 3 is a pass with **zero** findings; don't manufacture findings to pad. Codex is +below the floor is a pass with **zero** findings; don't manufacture findings to pad. Codex is advisory — validate before applying; dismissed finding → one-line why. +**Named residual:** the hook's messages state its own threshold as an obligation, so at a +floor of 1 they report a shortfall the cycle does not owe. Hook text is out of scope here +by decision; what makes that tolerable is the precedence rule above plus the hook exiting +0 on every branch, not the reminder being harmless. + +**The gate-off surface — routes known today, not a complete list**, since an enumeration +read as complete guarantees the routes it omits. One route is created here: a stated floor +the cited set does not license, which could not exist before there was a derived floor to +state. Pre-existing and unchanged: omitting a higher-risk cited story; minting or editing a +profile to level 0; presenting an incomplete cited set; falsifying evidence entries; +silencing reminders; or not running a pass and reporting that it ran. A user-set floor is +not the lever — it moves what the hook says, not what the cycle owes. +None of this is a guard: the floor is produced by the agent and nothing checks it against +the cited profiles. + +**When these rules bind.** From the commit that ships them, and a cycle already running +finishes under the rules it started with. Where a cycle's starting rules cannot be +established it takes the stricter reading of every part this change touches — at minimum +floor 3, severity classified without the demotion, the provenance-line duty owed, the curve +duty owed, and the nonce duties at their strictest — the cycle is treated as post-rule, so it +owes a nonce, owes its provenance line and its curve or skip record, and uses that nonce in every +cycle record it does write — which changes what a record is named, never whether one is owed, so +the working record stays optional and a skipped cycle still writes no findings slots. Where it +cannot recover a nonce it starts a new cycle rather than claiming `none (pre-rule)`, that reserved +field being unavailable to a cycle whose start cannot be established. Each further rule this change ships adds its own strict +reading to this list. Not a re-derivation, which could hand a level-0 +cycle a floor of 1 and skip passes on the strength of not knowing when it started. A user +knob set above 3 is not lowered by this fallback. A revert is itself a shipping commit for +the old rules, and the activation rule wins wherever the start is determinable; the +fallback covers only where it is not. + +**Downstream has no shipping commit.** Adoption binds from the `/workflow-init` run that +actually writes the text — which may write nothing, be declined, or be merged in part — +so these rules bind only over the text a project's `CLAUDE.md` actually contains, and a +partial adoption can persist undetected. A project taking the floor rule without the +severity test gets a floor whose docs-only question the severity test is what settles. +**A partial adoption can leave a project's floor undefined or self-contradictory.** The rule +is a coherence requirement, stated semantically rather than as a list of spellings, and it +runs in **both** directions: **exactly one definition of the floor must be present, and every +statement that defines or constrains the floor, or makes closing depend on it, must resolve to +that one definition.** **The unknown-start fallback is not a second definition**: it is +explicitly conditional on a cycle's starting rules being undeterminable and governs only that +state, so it coexists with the predicate rather than competing with it. Everything else +likewise keeps its own footing and is **not** required to derive from the floor: **the other closure and stop predicates** — assigned-fix-set membership, a new +structural question, an accepted Blocker or Major, the tell thresholds; **independent reporting +and diagnostic ordinals**, such as a duty owed from a given pass onward; and **the hook's +reminder threshold together with any descriptive or historical pass number**, which say what a +tool reports or what once happened rather than what a cycle owes. Four states break it, and the list is **not exhaustive**: a fixed-number or +specific-pass obligation surviving beside the derived predicate; a claim or dependency on a +derived floor with no predicate to define it; **no definition at all**; and **two definitions +at once**. A +merge can produce any of them: the Gate-A loop description, the pass-1 closure rule and the +re-review rationale each carry a fixed-three claim and can be taken or left independently of +the predicate itself. In any such state nothing here resolves which rule governs: **stop, and +have a human complete or revert the adoption, before running a gate under it.** What prompt text can do about downstream +adoption is limited, and that limit is what this paragraph states. + **What a loop absorbs, and what stops it — a question of scope, not of action.** A finding that corrects the correction you just made **and stays inside the assigned fix set** is **inside this loop's scope**: keep it here rather than handing it back, then act on it by its @@ -319,7 +439,8 @@ finish, and it is why **a clean completion takes precedence over this exit**: a Blocker/Major-free pass **at or above the floor** has satisfied the clean-final-pass rule — collect the Minors and Nits and close — and reporting "will not converge" on a converged loop is a false report. **Below the floor nothing closes**, and a zero-finding pass remains -the only exception, exactly as above; a Blocker/Major-free pass 1 carrying a Minor keeps +the only exception, exactly as above; a Blocker/Major-free pass below the floor +carrying a Minor keeps looping. **Surfacing does not close the cycle, and that is what makes this reachable.** You surface *with the finding still open* — the resolve rule is not waived, no pass is credited as @@ -327,6 +448,14 @@ clean, and the loop resumes on whatever the user decides. Reading it as "stop in fixing" would put the exit in competition with the rule that every Blocker and Major resolves, and then nothing could satisfy both. +**Every pass report states three things about the floor**, from pass 1 onward: the +derived floor, the risk and security values read, and the cited stories they were read +from. A report giving the number alone leaves a reader unable to check the derivation +while passes are still being spent — which is the only time checking it is cheap. Where +no story is cited, or a cited story is unprofiled, the report says so in place of axis +values; a multi-story set names each story and its values. This is owed by every pass; +the three lines below are owed from pass 4 and are a different obligation. + **From pass 4 onward every pass report carries three lines.** The carrier is **your own status report to the user** — never the Codex reply, which stays exactly one line per branch, and never the findings file, which admits no line that is not a finding or the terminator. @@ -364,7 +493,26 @@ because the response stops carrying the findings at all. Append to the gate prom > `.context/codex-reviews/.md` (create the directory if needed; the path is > relative to that root — Codex resolves writes against its working directory, so > without this a valid file can land in a different checkout). `` is -> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

`. +> `gate-a-spec-pass-

`, `gate-a-plan-pass-

`, or `gate-b--pass-

` for a +> cycle with no nonce; a cycle that has one writes `gate-a-spec--pass-

`, +> `gate-a-plan--pass-

` or `gate-b---pass-

` instead, and uses +> the nonce in every slot more than one cycle could write. The bare names are reserved for the +> legacy single-cycle case they already serve. **Distinct-nonce paths coexist by construction and +> are never in conflict** — a sibling cycle's slot is simply a different file. +> +> **The rule binds the deletion step, which is where the damage is done.** This section already +> requires every target to be deleted and confirmed gone before a call. A cycle holding a nonce +> **deletes only paths carrying its own nonce**; it never deletes a bare path or one carrying a +> different nonce, and an attempt to do either **stops and names the path** instead of removing +> it. That is reachable and observable: the step operates on a path it computed, and the check is +> whether that path is the cycle's own. **The case it exists for is a nonce-holding cycle +> computing a bare path** — the legacy spelling — **and deleting a file that belongs to somebody +> else**, which is exactly what happened once. **Two cycles that drew the same nonce compute the +> same paths and are indistinguishable to this rule**; what makes that unlikely is the width of +> the draw, not this rule — this section already stops on a +> target that survives deletion, and this extends that to a target that must not be deleted at +> all. That rule exists because a bare slot was in fact overwritten once, destroying a previous +> cycle's findings file. > > One finding per line in the format above; escape a literal pipe inside a field as > `\|`. @@ -410,6 +558,114 @@ terminator remain the only hard requirement, and a zero-finding pass needs no co Whoever runs the cycle writes it when useful, replaces it as the cycle moves, and deletes it once the cycle closes. Nothing depends on it existing. +**The cycle nonce.** Both shipped records below carry a **cycle field**, because a record that +cannot be attributed to a cycle cannot be told apart from another cycle's when several are read +together. That is a limitation rather than a disqualification — a human reading one cycle's +records knows which cycle they came from; what attribution buys is that a *later* reader +**usually** does not have to. Usually, not always: the guarantee is probabilistic, for the two +reasons stated at the end of this block. This section defines three **kinds** of cycle — the Gate-A spec loop, the Gate-A +plan loop and the Gate-B cycle — and **one cycle field is produced per cycle run, not per +kind**: a change carrying several plans runs a Gate-A plan cycle for each, and each of those is +its own cycle with its own nonce. + +Generated once at cycle start, immutable, and collision-resistant operationally: **8 to 16 +characters drawn uniformly from `[a-z0-9]`, from a source of randomness** — 8 being where +collision resistance starts and 16 where the field stops being a usable infix. **Never derived +from a name, a timestamp or a commit**, each of which collides exactly where sibling cycles do, +which is the one thing the nonce exists to prevent. The character set keeps it safe as a slot +infix and a path component. + +**It appears in every record the cycle writes** — which keeps records apart **as far as distinct +nonces allow**, and no further — **and that set is named rather than left open**: +the provenance line, the per-pass curve (including a skip record standing in for one), the +cycle's findings slots, and its advisory working record. **The working record is a cycle record +too**: a cycle holding a nonce names it `gate-a-spec--resume.md`, +`gate-a-plan--resume.md` or `gate-b--resume.md`, and the bare names above stay +reserved for the legacy single-cycle case, exactly as the findings slots do. **Because recovery +scopes candidates by artifact as well as by kind, the record's contents name that artifact**, and +what counts as the artifact depends on the cycle kind: for a Gate-A cycle it is the reviewed +document's path, quoted by the same rule the provenance line uses where quoting is needed; for a +Gate-B cycle, which reviews a diff rather than a file, it is the **base commit's full +40-character hex object name**, the same value the cycle's reviews are run against. The filename +carries kind and nonce; the artifact key lives inside, where neither a path nor a hex name has to +survive a filename. The nonce is not +required in records this change neither introduces nor keys to a cycle — the evidence entry and +a human-exception record among them. + +**A nonce is a candidate for recovery only if** it is keyed to this cycle's kind — Gate-A spec, +Gate-A plan, or Gate B — **and** this cycle's artifact, **and** that cycle is still open. **Those +three are necessary and not sufficient, and the difference matters**: two Gate-A cycles can review +the same document and two Gate-B cycles commonly share a base commit, so a sole match on kind and +artifact is **not** identity. **A candidate is adopted only if it is positively linked to this +run** — the working record this run itself wrote. A match that is merely consistent is treated as +no identity, and the cycle starts fresh; adopting a sibling on a shared key would merge two +cycles under one nonce, which is the failure this rule exists to prevent. +History normally holds many closed cycles' nonces and they are not candidates; a working record +left by a closed cycle is not one either, which is why that record is **retired at closure** +rather than left to be found later. **Recovery has two sources, and they answer different questions.** The **working record** is the +source while the cycle runs, and it is the one the candidate rules above apply to — several files +may be present and the run must decide which, if any, is its own. **History is the source once +the cycle's own commit exists**, and there is no search there: the cycle is reading **its own +commit body**, so kind and artifact are settled by which commit is being read, and the nonce is +taken from the provenance line and the curve, which must agree. A Gate-A cycle mid-run has no +such commit and therefore has only the working record. Recovering a single candidate from +**either** keeps identity **as far as the field can distinguish cycles** — two cycles sharing a +nonce are one cycle to it. **No candidate, +disagreeing sources, or more than one candidate → no identity: start a new cycle**, which costs +passes rather than letting one cycle's records read as another's — again, as far as distinct +nonces allow. **Starting a new cycle does +not close, adopt or retire the cycles those candidates belong to** — they stay open, keep their +own nonces, and are a human's to resolve; the new cycle simply does not claim them. + +**A cycle does not start without a valid nonce, unique among the cycles open when it was +generated.** That is the requirement. **What the check can establish is narrower** — it compares +against the cycles it can observe — and the gap between the two is the residual set out below. +Where generation fails, make **at most three attempts in total**, then stop and +surface, **naming which of the three causes occurred**; each has its own check and its own fix, +and one token would name a symptom rather than a cause: + +The three are distinguished by **where** the attempt stopped, so they cannot both apply: the +source failed to produce bytes; or it produced bytes that are not a well-formed nonce; or it +produced a well-formed nonce that is already in use. An empty result is the first, never the +second. + +- **randomness unavailable** — the source errors or produces no bytes. *Fix:* retry, since the + condition can be transient; if it persists across the attempts, make a source available or run + where one is, which is a change to the environment rather than another draw. +- **an invalid value** — the drawn value is not 8 to 16 characters from `[a-z0-9]`. *Fix:* + redraw. Repeated invalid output points at the generator rather than at luck, and the report + says which. +- **a collision with a known-open cycle** — the value equals a nonce on a cycle still open. + *Fix:* redraw. A second collision at this width is possible but unlikely enough to be worth + reporting as a possible source defect, which the report states as a suspicion rather than a + finding. + +**Report every distinct cause observed across the attempts, in the order they occurred** — the +attempts can fail for different reasons, and naming only the last would describe the tail of the +sequence rather than what happened. + +**No deterministic fallback.** + +**Residuals, disclosed rather than guarded, and this list is not exhaustive.** The check compares +against cycles *known to be open*, so a nonce can repeat one belonging to a cycle nobody can see; +two cycles starting at the same moment can each check before either has published, so neither +observes the other; and the check deliberately ignores **closed** cycles, so a new cycle can +redraw a closed one's value and then write to its surviving findings slots and working record. +**What makes both unlikely is the width of the draw, not the check** — and unlikely is the +honest word. Neither is a guard. + +**What follows from that, said here rather than left to be discovered.** The nonce is +collision-**resistant**, not collision-**proof**, so everything built on it inherits that bound: +two cycles sharing a nonce write to the same slots and are not refused, their records read as +one cycle's, and a later reader cannot separate them. Attribution is therefore a strong default +rather than a guarantee, and any reading of these records that would be wrong if two cycles +shared a field should say so rather than assume they did not. + +**A cycle that began before these rules shipped has no nonce and cannot acquire one.** Its +records carry the reserved `cycle none (pre-rule)` field and are, by construction, not +cycle-attributable. That exception is bounded and self-terminating: it reaches only cycles +already running when the rules land, and no later cycle can enter the state. + **Accept a pass only when** the file exists and is readable; its last line is exactly `END OF FINDINGS ( total)`; it contains exactly `` finding lines *and nothing @@ -418,7 +674,7 @@ the file" would accept a truncated file padded with fragments); and, for a `full pass, both branch files satisfy all of that. Anything else — missing, unreadable or empty file, wrong path, malformed terminator, count mismatch, extra lines, one branch file, an `INCOMPLETE` reply — is an **INCOMPLETE pass**, which is not a review: don't -act on the partial list, don't count it toward the 3-pass floor, and don't read "no +act on the partial list, don't count it toward the floor, and don't read "no Blocker/Major visible" as clean. **Reader:** the severity field is taken by splitting the line on **unescaped** pipes and @@ -482,7 +738,7 @@ fingerprint components, so review artifacts cannot invalidate the review they do Add `/.context/codex-reviews/` to `.gitignore` — that entry specifically, not all of `.context/`, which would strip the committed `codex-gate.on` adoption marker. -- **Gate A — Spec, then plan (TWO runs, each its own 3-pass loop).** Run on the +- **Gate A — Spec, then plan (TWO runs, each its own loop at the derived floor).** Run on the **spec** right after brainstorming (before `writing-plans`), then on the **plan** before `executing-plans`/`subagent-driven-development` — catching a spec flaw before it's baked into the plan. Tool: `mcp__codex__exec` (raw; @@ -515,8 +771,8 @@ Add `/.context/codex-reviews/` to `.gitignore` — that entry specifically, not - **Gate B — Code.** Tests green, before `git commit`. Tool: `mcp__codex__review` (args `instruction`, `whatWasImplemented`, `baseSha`; `reviewType: full` runs spec + quality in parallel). Skip ONLY trivial changes. Check against - @AGENTS.md. Re-review after every fix — a fix changes the diff and the hook - invalidates the prior pass, which is where the 3 come from. + @AGENTS.md. Re-review after every fix — a fix changes the artifact, so the prior + review no longer covers it. The hook merely notices, at commit time. **A fix that changes specified behaviour updates the spec in the same commit.** If a Gate-B fix alters something the approved spec pins down — an ordering, a terminal @@ -579,16 +835,31 @@ the reviewer, the other obliges the author. it; risk's *abuse* and security's *abuse paths* are **one lens carrying both labels**, not two questions. -Lenses are **different questions, not more passes.** The 3-pass floor, the Blocker/Major -filter, the file-first findings protocol and the clean-final-pass rule are unchanged. +Lenses are **different questions, not more passes** — they change what a pass asks, never +how many a cycle owes. The Blocker/Major filter, the file-first findings protocol and the +clean-final-pass rule are unchanged. The floor is not among them: it is no longer a fixed +number but derives from the profile and the cited set. -**Reading the profile — three cases, three answers:** +**Reading the profile — five cases, five answers:** +0. **The ordinary case**: every cited story is readable and its profile resolves → derive the + floor from it and run. Stated first because a partition of failures alone is not a + partition, and an earlier revision of this list omitted it. 1. The artifact **cites no story** → run unprofiled and **say so** in the pass. Artifacts predating this rule are the common case; stopping on them would halt in-flight work. 2. The cited story has **no profile line** → same: today's behaviour. -3. A profile is **present but unresolvable** → **stop and surface the cause**. That covers - the syntactic failures — unparseable line, a value outside the enums, two profile - blocks, a citation resolving to nothing — **and the semantic ones**: a `**Validation:**` +3. The cited path **does not yield a readable story file** → **stop and surface which of + these it was**, because each has a different fix: the path does not exist (a typo, or a + file moved or deleted); it exists but is not a regular file, a directory being the common + case; it is a symlink that does not resolve; or it exists and is a regular file but cannot + be read for permissions. **Report what you observed; no test order is prescribed here**, + because the obvious one is wrong — an ordinary existence or regular-file test follows a + symlink, so a dangling link reads as absent rather than as a broken link. + This case exists because none of the answers above is available to an agent that never + obtained the file: it can establish neither that a profile is absent nor that one is + present but unresolvable. +4. The story **is readable** and a profile is **present but unresolvable** → **stop and + surface the cause**. That covers the syntactic failures — unparseable line, a value + outside the enums, two profile blocks — **and the semantic ones**: a `**Validation:**` value disagreeing with `max(risk, security)`, or `+abuse-path` present without security `high` or absent with it. Only the **latest `mode override`** in the log, moving in a direction compatible with the current value, can explain such a mismatch — and if the @@ -608,8 +879,9 @@ trivial** (the pre-existing judgement, unchanged by profiles), **and** for a pro profile *changes*, and a skip changes no profile value. **A skip removes the review, never the evidence**, and what is owed follows the profile: a skipped **profiled** story runs the battery and lands its evidence entry beside the reason; a skipped **unprofiled** story -records the reason and the battery result and nothing more, because it owes no mode-derived -entry and keeps exactly today's judgement-based skip. +records the reason and the battery result, because it owes no mode-derived entry and keeps +exactly today's judgement-based skip. **Neither is excused the records every cycle owes** — +the provenance line, and a skip record in place of the curve. **A cycle citing several stories** aggregates along separate dimensions, never through one winning mode: the **battery runs once** for the cycle; **each cited _profiled_ story @@ -666,6 +938,29 @@ the current profile. Inside an active Gate-B cycle, fold the edit into the activ snapshot by amend — a non-`WIP` commit reads to the hook as the cycle closing and would discard the accumulated passes. +**While a gate is running, the floor derives from the current profile at each pass.** +Passes already run keep counting; closing requires the floor as currently derived. These +are pass-count rules, so they apply while a gate is running and are silent otherwise — +what governs when a gate runs is unchanged and deliberately not summarised here. + +**Any profile change costs at least one further pass**, in either direction and whether or +not the floor number moves, because the final clean pass must run under the current +profile — so no already-banked pass can be it. That further pass must itself be clean and +every other closure duty must be satisfied; it is one more pass, not a licence to close on +the next one. What a lowering drops is whatever the changed values drop, not a fixed pair: +a mode-only override changes the evidence obligations while leaving the axis-derived lens +sets alone, and security `high` → `standard` keeps the security lens set while changing +what evidence is owed. Every derived obligation is recomputed from the current profile. + +**The cited set is re-read at each pass, and the final clean pass runs against the current +set** — whenever its membership changes, not only when the floor number moves. Adding a +high-risk story to a set already at floor 3 leaves the number alone while adding that +story's lens set, its evidence obligations and its review scope; a pass run before it +joined did not cover them. Removing a story recomputes obligations from the current set +and so does remove that story's lenses and evidence duty — but it never discharges an +accepted in-set Blocker or Major: the acceptance put that finding in the fix set, not the +citation. + **What this does not do:** nothing checks which file a model actually read, whether the header changed mid-call, or whether the lens sets were appended. This is instruction-backed like the rest of §5; the detection is a reader comparing the pass against the story. @@ -673,10 +968,41 @@ like the rest of §5; the detection is a reader comparing the pass against the s ### Mechanics (reference) - **Severity:** Blocker (wrong/unsafe/breaks invariant) · Major (design flaw → rework) → both must resolve. Minor · Nit → collect, never iterate. + + **Deciding severity — one procedure. The subject list is illustration, not a second + rule.** Name what in the system consumes this text — whatever *acts* on it — and the + decision that act takes differently if the text is wrong. Both are required. If you + cannot name both, the finding is Minor or below: collect, never iterate. + + The exclusions are contract, not commentary. The reader must consume the text in the + system's *operation*, not in reviewing it — the review pass raising the finding is not + an in-system reader of the text it reviews; without this the test demotes nothing. + Gates remain legitimate readers of rule text they will later apply. A human reader never + satisfies the test — the prose exemption already prices that cost as non-gating. The + list of reader kinds is illustrative, not closed, because this ships into projects whose + readers we have never seen. The test sets a ceiling, not a floor, and never chooses + between Blocker and Major — the four definitions above still decide that. The instrument + carve-out is symmetric: an instrument finding keeps its severity whenever it shows the + instrument changes what a gate concludes about product behaviour — a false green, and + equally a false red or a check blocking a valid change. Rationale prose is Minor only + when no rule's application depends on it, not categorically: `docs/prompt-standards.md` + requires rules to carry their why, so rationale a reader must consult to apply a rule + passes the test. This removes arbitrariness, not judgement. Coverage-first is unchanged + — the reviewer reports every finding with severity and confidence; the filter is ours. + + This is the finding-level analog of the path-level prose exemption: one principle at two + granularities — text that *describes* the product versus text that *is* the product. + + **How this demotion bears on the loop-health measures — the per-pass counts, the finding + clusters and the stop thresholds — is not settled here, and this change does not settle it. + Until it is, a pass whose outcome would turn on that question reports the question and + stops rather than deciding it** — the same answer any unresolved gate question gets. - **Tool routing:** docs (spec/plan, incl. code snippets) → `mcp__codex__exec`; implemented diff → `mcp__codex__review`. Never `review` a doc — it reads the git range, not the text. -- **`baseSha`:** against main = merge-base with main (`headSha` = HEAD); +- **`baseSha`:** against main = merge-base with main (`headSha` = the full 40-character + object name `HEAD` resolves to at that moment, never the symbolic `HEAD` — see the + branch-agreement rule below for why); pre-commit, `baseSha` = HEAD is an empty range (HEAD..HEAD) — make a WIP commit and set `baseSha` to its parent. **Name that commit `WIP: …`** — the hook treats a `wip`-prefixed commit message as cycle-internal, so it neither fires a Gate-B STOP @@ -695,7 +1021,142 @@ like the rest of §5; the detection is a reader comparing the pass against the s destroyed exactly when the cycle closes. The final commit body is the durable record; a PR shows commit messages, so there is no second home to keep in sync. - **On squash-merge, copy every evidence entry and every human-exception record in the squash range into the squash body — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** + **Every cycle records one provenance line in its closing commit body** — default floor or + not, so an absent line is never ambiguous between "the default applied" and "someone forgot". + **One line per cycle**, so a change running five cycles records five. There is no informal + variant; anything quoting this form elsewhere quotes an instance of it, because the deferred + metrics work is intended to parse it — that consumer does not exist yet, and the form is pinned + now so that it can. + + ; floor per ; hook reminder threshold + + := "cycle " | "cycle none (pre-rule)" + := [a-z0-9]{8,16} + := [1-9][0-9]* + := "none" | "{" ("," )* "}" + each appears at most once; a repeated path, + with or without conflicting levels, is malformed + := " (level " ("0"|"1"|"2") ")" | " (unprofiled)" + := | + := [A-Za-z0-9._/-]+ contains no delimiter, quote or whitespace + := a double-quoted string, non-empty, whose only escapes are \" and \\ ; + a path containing a newline or other control + character is NOT representable — the cycle stops + and surfaces rather than emitting one + := "absent" | [1-9][0-9]* | "unusable" + + A filled instance, so the form is shown and not only described: + + cycle none (pre-rule); floor 3 per {docs/superpowers/stories/2026-08-28-review-loop-economics-pass-floor-story.md (level 2)}; hook reminder threshold absent + + It carries that cycle's **cycle field** — the nonce for any cycle started after these rules + ship, `none (pre-rule)` only for one that began before them — the **derived floor**, and **the + cited set that produced it**, each member with its level as a numeral. One floor and one set, + not an entry per story, since unanimity makes the floor a property of the set. It + distinguishes **a cited story with no profile** from **no story cited**. It records the + **workspace knob whenever the file exists**: the value if the observer read one, otherwise + `unusable`. **Nothing here describes what the hook does with that file, and the record does not + say why a value was unusable** — four successive attempts to state either were each wrong in a + different way, the last of them demonstrably so, and the rule for a claim needing a fourth + correction is to delete it. Whoever needs to know why reads the file and the hook. + + **These records are one contract, and a partial adoption breaks it.** The nonce, the slot + naming, the provenance line, the curve, this carry rule **and the unknown-start activation + semantics that say what a cycle owes when its starting rules cannot be established** depend on + one another, and the requirement is that the adopted definitions **agree**, not merely that all + of them are present: a curve + without a cycle field cannot be attributed, a slot rule without a nonce has nothing to key on, + and a carry rule naming records a project does not produce is inert. **A project whose text + carries some of them and not others, or carries all of them in versions that disagree, stops + and has a human complete, revert or reconcile the adoption before running a gate under it** — + disagreement is the harder case and gets the same stop, because a project holding two + definitions of a record has no single answer to what it owes — the same answer, and for the same reason, as a partial + adoption of the floor rule. + + **On squash-merge, copy every evidence entry, every human-exception record, the provenance lines, the curves and any skipped cycle's skip record TOGETHER WITH THE SKIP REASON IT POINTS AT in the squash range into the squash body — a skip record carried without its reason is a pointer into a body the squash has made unreachable — the squash commit is the only body the merge carries into `main`'s history, so anything left behind is unreachable from it.** + + **Every cycle records its own per-pass curve in its own commit body.** Gate B alone would + leave the dominant cost unrecorded — the loops this rule was built from are Gate-A loops. + + ; (passes , ): Findings . Blockers . Majors . + + := "Gate-A spec" | "Gate-A plan" | "Gate B" + := ("," )* strictly ascending, non-overlapping + :=

|

"-"

+

:= [1-9][0-9]* + := ("," )* exactly as many entries as enumerates + := 0 | [1-9][0-9]* | "?" "?" = the count is unrecoverable for that pass + := | ("; " )* + := "pass "

" " ("+" )* + := | | "undetermined" + "undetermined" means the model could not be determined; + a real model so named is written as + := [!-~]{1,} minus ; : , ( ) + " and space, and not the + literal "undetermined", which is reserved + printable ASCII only; a control character makes the + identifier unrepresentable, handled below + := a non-empty double-quoted string, same two escapes as ; + an identifier that cannot be determined, or cannot be + represented, is written `undetermined`, and the raw + value is NOT reproduced anywhere in the body, since a + commit message cannot safely carry one (NUL cannot + appear at all). The record does not say why a pass + reached `undetermined`, and nothing here describes how + a model identifier fails — same rule, same reason as + the knob above + + Two filled instances, one ordinary and one with a split logical pass: + + cycle none (pre-rule); Gate B (passes 1-3, codex): Findings 16,29,4. Blockers 4,15,0. Majors 5,2,1. + cycle 7b2q9xk4; Gate-A spec (passes 1,2, pass 1 codex+claude; pass 2 codex): Findings 5,0. Blockers 1,0. Majors 2,0. + + A skipped cycle writes `; : skipped (see skip reason)` and no counts. **The skip + reason it points at is the text immediately following it in the same commit body** — + adjacency is the link. The cycle field is not: every pre-rule cycle writes + `cycle none (pre-rule)`, so it identifies nothing when a body carries more than one. + **`` keys must be exactly the passes `` expands to, each once, ascending** — a + list that omits or repeats a pass is malformed, not partially informative — and **every model + contributing to a split logical pass is listed**, joined by `+`, since recording one of two is + the same loss as recording none. + + **Majors are recorded as well as Findings and Blockers**, because the severity rule moves the + Blocker/Major line rather than the total, so totals and Blockers alone could not show even a + change in the mix. **Subject categories are deliberately not recorded** — they are a judgement + per finding rather than a count, and the findings files carry the material. + + **One entry per valid pass**, and since incomplete passes are excluded while still consuming + pass numbers, the record **states which pass numbers it covers**. A valid zero-finding pass is + recorded as zero, never omitted. **A count that cannot be recovered is written `?`, never + guessed and never written as `0`** — a cycle keeps its identity through the nonce rather than + through its pass files, as far as distinct nonces allow, so a resumed cycle may know a pass happened and not what it found, and + zero and unknown are different facts. **`?` is per series**: a pass whose Findings are unknown + may still have usable Blocker and Major counts, and a reader excludes the unknown value from + the comparisons that read that series while keeping the pass's other series. + + A `full` Gate-B pass, separate `spec`/`quality` calls, and a single-branch recovery are + **branches of one logical pass** contributing one summed entry — **the curve counts logical + passes; the hook counts calls**, and where they differ the body says so **as prose beside the + curve**: neither grammar has a field for a call count, deliberately, since the count is a + property of how the pass was invoked rather than of what it found. **Both branches must be + issued against the same commit**, and that — not what they read — is what this rule + establishes. The result reports no reviewed revision, so there is nothing to read back and no + way to confirm from the reply what either branch actually looked at. What is available is the + request: **resolve `HEAD` to its full 40-character object name before each call and pass that + explicit value as `headSha`**, never the symbolic `HEAD`, which two calls can resolve + differently if a `WIP:` amend lands between them. Keep the value you passed **with that + branch's result**, and require the two kept values — **`baseSha` and `headSha` both**, since a + range is selected by both ends and two calls can share a head over different bases — to be + **exactly equal** before summing the branches. Equal values mean the two calls were aimed at one commit; they are not evidence that + either branch reviewed it, and nothing available here would be. Record it as the **full 40-character hex object name**, since abbreviations are + ambiguous across repositories and across time; if it changed between them they are not one + pass, the completed branch is recorded as incomplete and excluded, and the later branch begins + a new one. Ending the pass is the conservative direction; merging two revisions would produce + one entry describing two different artifacts. + + **What the curve is worth, stated rather than implied.** Durable **across** cycles; **not + within** a running one, since the commit does not exist until the cycle closes. And + **author-written and unchecked** — nothing compares it against the validated pass files, so + whatever reads it reads a self-reported curve and must not present it as measurement. **Recording a human exception.** Where a human decides that something **no applicable rule required** was nonetheless worth skipping — an optional check this environment cannot run, a diff --git a/todos.md b/todos.md index d7b8a33..1133bb9 100644 --- a/todos.md +++ b/todos.md @@ -430,6 +430,32 @@ That belongs in each product project's own `todos.md` once `/workflow-init` has there, not here: this repo ships the workflow, it does not hold another project's backlog. +- [ ] **`git commit --amend --no-edit` silently resets a Gate-B cycle, and nothing warns.** + `plugins/dev-workflow/hooks/codex-gate.sh:763` is + `is_wip_commit() { printf '%s' "$1" | grep -Eiq -- "-m[[:space:]]*['\"]?[[:space:]]*wip"; }` + — it matches the **Bash command string**, not git state and not the commit message. + So an amend written the natural way carries no `-m`, is not recognized as a WIP + commit, and at `:886` `is_commit "$cmd" && ! is_wip_commit "$cmd"` is true: the hook + resets and the accumulated Gate-B passes are discarded. §5 tells everyone to snapshot + with `WIP: …` and close with `--amend -m ""` but never says the + recognition is a grep over what you type, so following §5 as written and amending + with `--no-edit` mid-cycle destroys the cycle with no signal. + **Loose in both directions**, which is the same root cause: a `cat` heredoc merely + *containing* the text `WIP:` fires the WIP notice, and one containing `git commit` + fires a Gate-B STOP. Both observed in the 2026-08-29 session. That direction is + harmless per invariant 2; the reset direction is not. + Found while writing the review-loop-economics plans, whose whole commit protocol had + to be built around it — see + `docs/superpowers/plans/2026-08-29-review-loop-economics-plan-a-rules.md` + § "The `--no-edit` trap" and `.context/codex-reviews/gate-a-plan-rle-pass-1.md` B15. + **Not fixed there**: those plans are prompt-only by their own constraint, and the fix + touches hook code. Two candidate rungs, and the choice is the point — prose (§5 warns + about `--no-edit` explicitly, cheap, does not stop the next person who forgets) or a + real fix in the hook (recognize an amend that preserves a `WIP:` subject, which means + reading git state rather than the command string). Prefer the latter if the class + recurs; log it through `harden-finding` when it does. + *Trigger: the next change that touches the hook, or a second observed cycle reset.* + - [x] **P2 — risk/security profiles, and the derived validation mode.** Shipped: two human-confirmed axes in the story header, a mode derived as `max(risk, security)`, lens sets appended to the §5 gate prompts, and the Gate-B triviality skip narrowed @@ -741,3 +767,37 @@ backlog. fixtures. Part of that story: `ci.yml`'s `koalaman/shellcheck:v0.11.0` is tag-pinned by luck, not by the gate — a tag can be repointed, so digest-pinning it belongs to whoever takes the Docker surface on. + +## From PR #26 — backlog only, nothing implemented here + +- **Both-branches-misread-each-other.** `mcp__codex__review` with `reviewType: full` runs two + reviewers in parallel from one call. §5's file protocol keeps them from racing on a single + path, but never tells either that the other exists — so on PR #26's Gate-B pass 2 each read + its counterpart's legitimate findings file as a foreign write and reported the other branch + `INCOMPLETE`. Both files were structurally valid; the pass was discounted anyway, because an + `INCOMPLETE` reply is an incomplete pass by rule. One line of `additionalContext` fixed it + and it did not recur across three further passes. **Backlog:** ship that line as standing + prompt text in §5's Gate-B section and the scaffolded template, so it is not rediscovered per + cycle. Record: `docs/field-reports/2026-08-30-gate-a-rle-plan-cycles.md`. + +- **The self-consuming deletion — `prompt-standards.md` item 11 amendment.** Item 11 says to + delete a claim about a mechanism after a fourth correction rather than refine it a fifth + time. It does not say what happens to the *rationale* for the deletion — and on PR #26 that + rationale was itself a description of the mechanism (the tested NUL counter-example), so it + fell to the same rule. There is no version of that paragraph that survives its own rule. The + explanation was moved to a field report, where describing the hook is the point rather than + a claim the product makes. **Backlog:** amend item 11 to say the deletion takes its rationale + with it, and name the field report as the rationale's home. + +- **Valid findings from PR #26 recorded as out of scope** (per `process-pr-review` item 2 — + pre-existing and larger than the code this PR touches, so terminal there, not hardened): + - `docs/superpowers/plans/2026-08-29-review-loop-economics.md` is the **superseded** + single-plan artifact. CodeRabbit found two real defects in it — an impossible cycle + classification across Tasks 1–9, and `git commit --amend --no-edit` in Tasks 2–5, which + the hook does not recognize as a WIP amend and which therefore resets the cycle. Both are + true. The file is recorded as history and is not executed; marking superseded artifacts in + place is a convention gap this repo already owns and defers. + - `docs/superpowers/plans/2026-08-30-review-loop-economics-plan-c1-user-docs.md` — commands + whose output is tested do not check status, so a failed `git diff` with empty output can + select "ALREADY APPLIED AND COMMITTED". True, and C1 was dissolved into the rollout on + 2026-09-01 without being executed. Fix it if C1 is ever revived.