feat(fabrika): the /adr skill and its derived CLI contract — wave-0 pilot (#4704) - #4724
Conversation
The fabrika wave-0 pilot, authored via /skill-creator against brief #4704. v1 is reference only: no script is ported and no clause defers to one. SKILL.md is 119 lines, inside the brief's 80-120 target, against v1's 182. It carries only judgment; every deterministic step is a specified verb. contract.md specifies seven verbs and claims the seed package (packages/fabrika-cli/), which #4648 Resolved question 2 defers to the first derived contract. adr sweep and adr classify delegate to pipeline-cli and normalise two exit contracts a caller reliably misreads: adr-sweep exits 1 whenever it HAS a shortlist, and guard-content-probe exits 0 on the CP hold and 3 on proven-ordinary. Delegating rather than reimplementing keeps one adjacency ranker in the repo; wrapping rather than calling directly is forced by cli-invocation-guard, whose own remedy uses the variable expansion the fabrika conventions ban. adr resolve is the derivation the brief did not pre-specify: it collapses #4296, #4338 and #4163 (ADR state resolved against a stale tree) plus v1's never-guess-a-slug rule into one verb, and its live/landed split separates presence from authority - 36 of 234 ADRs on main are landed but proposed, superseded or retired. evals/ carries five graded cases with fixtures. Iteration 1 measured 95% with the skill against 40% without, over 18 assertions. Fixes #4704
No preview deploy
|
… settled Two corrections, both surfaced by the skill's own eval runs. The corpus count was 234; 233 records match NNNN-slug.md. The 234th file is 0034a-live-fan-out-options-considered.md, which the four-digit pattern does not match. The 36 landed-but-not-live figure is unchanged. The skill cited ADR 0164 as settled law while telling authors to cite only live ADRs - 0164 is status: proposed. The behaviour it describes is live regardless, because cp-classify and guard-content-probe enforce it and stamp (§CP, ADR 0164) into their own output, so both files now cite the enforcement and name 0164's status rather than leaning on it. The live/landed rule binds the artifact that states it.
The previous fix traded one drift for another. It hardcoded "still proposed in ADR 0164" into the skill, which is a claim about a mutable field in a record this artifact does not own - stale the moment #4388's pending ruling lands, and the same rot the live/landed rule exists to prevent. The skill now names only what binds: cp-classify enforces §CP-by-content on the merge path. The contract's grounding carries the fuller picture - the enforcement is live while 0164's status field has read proposed throughout, tracked as #4388 - and states that a caller learns a record's current status from adr resolve rather than from a claim written down elsewhere.
… two assertions Iteration 2 caught a side effect of the title fix. Dropping the 12-word cap and asking for "long enough to carry the discriminating half" invited an em-dash aside - the run produced "Sozluk pages stay client-rendered, never server-rendered - the live SSE view already owns the data path", a decision clause plus a second clause with its own subject and verb, which fails the skill's own one-clause rule. The rule now says where the contrast belongs rather than only how long the title may be. Two eval assertions also could not discriminate. Eval 1 rested three of four assertions on report.md, which the harness blocks subagents from writing; that one now also binds to the artifact. Eval 4's glossary-routing assertion passed equally for saying and doing under a read-only run; it now requires the row text and its insertion point.
Full isolation, ruled in-session. A fabrika that calls pipeline-cli can never be the thing that replaces it - every call is a tether that keeps the old tree alive. Duplication is the accepted cost. For /adr this drops the two wrapper verbs. adr sweep becomes a native implementation: the ranking is fabrika's own, and v1's adr-sweep is now cited as a list of mistakes already made (it exits non-zero on its own informative case; its --json goes to stderr, #4723) rather than as an implementation to call. adr classify is dropped entirely rather than duplicated. cp-classify decides control-plane membership at the merge gate and that gate is the authority; a fabrika copy of the guard vocabulary could tell an author "ordinary" while the gate says "control-plane" - two answers to a merge-gating question, worse than either a tether or a drifted ranking. #4386 and #3416 were the gate misclassifying, so an author-side predictor would not have caught them. The skill now states the expectation and never predicts the verdict. Encoded for the sessions that follow: CLI interface convention rule 6, the README's absent-list, and the authoring-brief contract's field 4, which becomes prior-art-to-read rather than verbs-to-call. The 18 unfired briefs (#4705-#4722) are amended to match - runbook step 7 anticipated exactly this.
|
review-skill: FAIL @ 5f38c5d — changes-requested Reviewed-head: @ 5f38c5d Verified PR #4724 against #4704's acceptance criteria + the skill-rigor checklist. Class: non-blocking ( Required namespaces for this diff ( Acceptance criteria
Skill rigor
Factual spot-checks — all four asserted numbers verify
PR-body accuracy — not artifact defects, but the body is what a merger reads The body describes an earlier design, and its arithmetic has drifted from the head:
Worth refreshing the body alongside the Assessment. This is a strong pilot. The fence discipline is exact, every load-bearing number verifies against the repo, both fail-closed statements and both forced-outcome gates survive as one-liners, no invariant is restated, and Failing items above must be addressed before this PR can merge. The PR stays open and unmerged; #4704 stays open and assigned. Re-request review once they're satisfied. Verdict-written: 2026-08-02T01:39:11Z |
|
review-code: FAIL @ 5f38c5d — changes-requested Reviewed-head: @ 5f38c5d Verified PR #4724 against #4704's acceptance criteria. Class: non-blocking ( Why this namespace is required. The three files in this namespace — all clean
Acceptance criteria (shared checklist — the same boxes both gates verify) Nine of the ten boxes PASS; the full per-box evidence is in the
Note. The failing artifact belongs to the skill class, not to this namespace's three files, which are clean on their own terms. This namespace fails because the acceptance-criteria checklist is shared and conjunctive — not because of a defect in Failing item above must be addressed before this PR can merge. The PR stays open and unmerged; #4704 stays open and assigned. Re-request review once it is satisfied. Verdict-written: 2026-08-02T01:40:06Z |
review-skill FAIL on #4724. The isolation rule (ADR 0238) was adopted after the adr sweep block was written, and nobody re-read the block against it, so its normative half still described a wrapper: the outcome token was "upstream's, verbatim", exit 3 meant "the underlying sweep could not run", and its error message named a component the spec says does not exist. A coder building from that paragraph alone would have written a pipeline-cli wrapper - re-tethering fabrika to v1 through the document meant to prevent it. The outcome is now stated as this verb's own verdict, with v1's exit-1-on-a- shortlist named as the mistake it avoids rather than a status it relays. Exit 3 becomes "the corpus could not be read". adr sweep --json was also the only --json in the contract with no declared payload shape, failing completeness test 2. Its keys are now named.
|
review-skill: PASS @ a1a8ab4 — merge-ready Reviewed-head: @ a1a8ab4 Re-review of the repair round (prior FAIL at Required namespaces ( Prior blocking finding — verified repairedOne root cause, three surfaces, all in
Residual-language sweep. Load-bearing claims re-measured at the new head
Acceptance criteria
Skill rigor
PR body — rewritten and now matching the artifact: 113 lines (measured 113), six fences (measured 6), six verbs ( Non-blocking observations — recorded, none gating, none a regression from the prior head:
Assessment. The repair is exactly what was asked for and it is complete. Re-measured from scratch, every load-bearing claim holds. The contract spec is buildable cold for all six verbs. The skill class is merge-ready. review-skill does not merge — Verdict-written: 2026-08-02T02:02:49Z |
|
review-code: FAIL @ a1a8ab4 — changes-requested Reviewed-head: @ a1a8ab4 Re-review of the repair round (prior FAIL at Why this namespace is required. The prior round's finding — repairedThe New finding in this namespace —
|
…lls it review-code FAIL on #4724. The previous commit deleted the "No skills yet" bullet but left three other statements that this same PR falsifies: the only-door paragraph's "the whole reason skills/ is empty right now", the layout tree's "empty today", and the .gitkeep line's "while it is empty". That file declares itself fabrika's front door and is the orientation doc for the 18 sibling briefs, all of which are cold boots - so a false claim there is read by every one of them. Also folded in three non-blocking review notes while the branch was open: adr resolve --json over several ids is now pinned as a JSON array rather than left array-vs-JSON-lines underdetermined; adr new's invocation line names its --json flag; and eval 3's fourth assertion no longer encodes v1's exit-1-on-a-shortlist as expected behaviour, which the repaired contract designs out.
|
review-code: PASS @ e3efe5f — merge-ready Reviewed-head: @ e3efe5f Third round. Prior: FAIL at Required namespaces ( The blocking finding — repairedThe
Whole-file re-read — every remaining claim tested against the tree at this headRead
The other two files — unchanged since the PASSed round
Acceptance criteriaAll nine boxes on #4704 PASS at this head. Per-box evidence is in the Non-blocking — recorded, not gating
Assessment. The one blocking finding is fully repaired, and the whole-file re-read found no second claim this PR falsifies — including the two claims that only became testable once a real skill landed, both of which hold. This namespace is merge-ready. review-code does not merge. Verdict-written: 2026-08-02T02:17:12Z |
|
review-skill: PASS @ e3efe5f — merge-ready Reviewed-head: @ e3efe5f Third round; re-binding a PASS to a moved head. Prior: FAIL at Required namespaces ( A PASS binds to content, not to a prior verdict, so every load-bearing claim below was re-measured at What moved in this file set
Standing bars — re-measured at this head
|
#4520, #4730) Two ends of one too-small required-review set, on the merge-authorization path. `verdict gate --require` was a single-valued `Flag.string`, so a repeated flag kept only the first occurrence and dropped the rest without a word: `--require review-code --require review-doc` cleared PR #4724 as enqueueable while `--require review-code,review-doc` refused on an absent `review-doc` — the same required set, opposite answers, decided by spelling. The `parseRequired` guard one layer below already refuses to shrink the conjunction; it never saw occurrence 2..n, so it was born dead rather than drifted. The flag is now `Flag.atLeast(1)` and every occurrence unions in, which also routes a later occurrence's bogus token back into that guard. Beside it, a coverage assertion: an affirmative gate answer must cover as many distinct namespaces as argv asked about. That is the general form — it refuses any future path that answers about fewer things than it was asked, which is the shape of this whole class (a check that runs, exits clean, and returns a plausible value that is simply smaller). Its operands come from two origins on purpose: the CLI's parse of argv against the decision returned over the service boundary. ship-it Step 0 re-derived the artifact-class set instead of delegating to `class-probe`, and its copy could only ever answer with a SUBSET: a file matching none of the three predicates produced silence, and silence subtracts a class. On PR #4724's list it printed `has-skills` alone where the probe printed `has-code, has-skills` — the plugin-docs-beside-skills shape any plugin home reproduces. The #2765 no-class rule lived there as five comment lines and zero executable ones. Step 0 now prints `class-probe`'s answer, which is what #2765's own commit said it was always meant to do, and the no-class rule lands as an executable fail-closed branch keyed on the §CPREAD file count against the probe's stdout. Scope note: the executed gate was not weakened by the Step-0 half alone — Step 2 re-derives the set from the CLI and no executable consumer of `step0-classify` exists. The exposure was the prose contract telling an agent to carry Step 0's set forward, and turning it live took a second deviation, which is the `--require` defect. Neither half demonstrates an un-gated merge on its own; the pair is the urgency. In-place edit to the frozen v1 `claude-plugins/kampus-pipeline/` corpus under the founder's #4650 No-go exception, scoped to this defect only (recorded on #4730). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes #4704
The fabrika wave-0 pilot:
/adr, authored through/skill-creatoragainst the brief, plus the CLI contract its verbs are specified by, plus a graded eval set.What landed
claude-plugins/fabrika/skills/adr/SKILL.md— 113 lines, inside the brief's 80–120 target, against v1's 182. Six steps, six fences, each exactly one plain-literal invocation.claude-plugins/fabrika/skills/adr/contract.md— six verbs, fully specified. It also claims the seed package (packages/fabrika-cli/), which #4648 Resolved question 2 defers to the first derived contract.claude-plugins/fabrika/skills/adr/evals/— five graded cases with fixtures.No verbs are implemented here — building them is #4725. No v1 script is ported and no clause defers to one (#4638):
grepforkampus-pipeline,.shorscripts/in either file returns nothing.fabrika calls
pipeline-clinowhereFounder ruling in-session, recorded as ADR 0238 in companion PR #4728. The deletion test decides it: a fabrika that calls v1 can never be the thing that replaces it, because every call keeps the old tree alive.
For this skill that means six verbs, not seven.
adr sweepis a native implementation — v1'sadr-sweepis cited as a list of mistakes already made (it exits1on its own informative case; its--jsongoes to stderr, #4723), never as something to call.adr classifywas dropped rather than duplicated:cp-classifydecides control-plane membership at the merge gate and that gate is the authority, so a fabrika copy of the guard vocabulary could contradict it on a merge-gating question. #4386 and #3416 were the gate misclassifying, so an author-side predictor would not have caught them anyway.Encoded for the sessions that follow: rule 6 of the CLI interface convention, the README's absent-list, and field 4 of the authoring-brief contract — which becomes prior-art-to-read rather than verbs-to-call. The 18 unfired briefs (#4705–#4722) were amended to match.
The derivation the brief did not pre-specify
adr resolve. The brief grouped #4296, #4338 and #4163 as one class — ADR state resolved against a tree that was not current, with the wrong answer indistinguishable from a right one. One verb answers all three, and absorbs v1's "never guess a slug from a title" rule as a fourth.Its
live/landedsplit separates presence from authority. 36 of the 233 ADRs onmainare landed butproposed,supersededorretired— ADR 0164 is landed andproposed. "Cite onlylanded" would not have closed #4338; "cite onlylive" does.Gates run before this PR
skill-reviewer(runbook step 5.5) ran pre-PR and returned Needs Major Revision with 28 findings; all blocking ones are fixed. Three of its claims I verified myself before acting, because they changed the architecture — all three held. One claim of mine did not: I asserted the template's**Banned.**labels trip the §CP probe. Tested in isolation they do not — the trigger isgate/guardin ordinary prose.review-skill+review-codethen FAILed the first submission for real cause: theadr sweepblock still carried delegation language after the isolation rule was adopted, so a coder building from that paragraph alone would have written apipeline-cliwrapper. Fixed ina1a8ab4a, along with the missing--jsonpayload shape.Deviation from the pre-derived carry-list
The brief's pre-derived material said to keep a trimmed ADR template in the wrapper. I cut it entirely — it is now the specified output of
adr new, so it has one home. The brief instructs flagging rather than following when it pre-decides the split, so: flagged.skill-revieweragreed on the principle (~90%) while catching three seam defects the removal created; all fixed.Evals
Iteration 1: 95% with the skill, 40% without, 18 assertions over 4 cases. The sharpest result is eval 2 — the baseline shipped an ADR stamped
0237, a number open PR #4703 already holds; the skill's arm refused to claim one at all.Iteration 2 re-ran the three cases still valid after the rewrite: 12/13. The one regression was mine — dropping the 12-word title cap invited an em-dash aside, fixed so the contrast sits inside the clause.
Caveats stated rather than buried: n=1 per cell, so a one-assertion swing is noise. Both graders found defects in the eval design itself (one vacuous assertion, one unreachable branch); both are fixed and a fifth case now covers the
indeterminatebranch. Evals 3 and 5 cannot execute untilfabrika-cliexists.Filed along the way
#4723 · #4725 · #4726 · #4727, plus folded findings on #3779 (the live allocator collision) and #2617 (the §CP probe's false-positive rate, measured for the first time at 84%).
🤖 Generated with Claude Code