feat: ceremony proportional to kind and size, and add check for the tally the gate reads - #206
Open
TinDang97 wants to merge 8 commits into
Open
feat: ceremony proportional to kind and size, and add check for the tally the gate reads#206TinDang97 wants to merge 8 commits into
add check for the tally the gate reads#206TinDang97 wants to merge 8 commits into
Conversation
…ver does ADD funnelled every request toward the full specification bundle. A one-line typo fix and a new subsystem read the same instructions, so the method paid node-shaped ceremony for trivia — and the only escape hatch, the Quick lane, admitted nothing but mechanical edits and carried no discipline at all beyond "make the edit". Replace the size threshold with a LADDER keyed on kind x size. Each rung states three things in one row: the route, the effort and review it owes, and what persists after the session. The closed floor is read FIRST and outranks size — security, data, architecture, a consumed `gives:` surface or frozen scope takes a node however small it is; security stays a HARD-STOP. The direct rung now admits small NEW behavior, not just mechanical edits, and in exchange carries a five-step checklist: route line, an inline card before the first edit, red->green, `invariants:` under the bare runtime, then a receipt of the commit plus exactly one `add learn` line. That learn line is its only bundle write. Medium and large reuse today's vocabulary — `--depth quick`, `standard|deep`, Milestone — no new lane, tier, verb or stamp. The rule ships to every reader, not just to Claude: `intake.md` and the SKILL.md bullet in all three live skill trees, this repo's CLAUDE.md block, and one byte-identical sizing sentence in both installer pointer twins, so a Cursor or Codex agent that never loads the skill still gets it in its own CLAUDE.md. Also repairs the CLAUDE.md block's retired verbs: `add.py` is the library and prints nothing — `cli.py` is the entrypoint — and `guide` no longer exists. 13 guards across two new files pin the text rather than the intent: the floor must precede the size limits in reading order, no rung may carry an empty review or persistence cell, and the SKILL.md line pin may not be raised to fund the new bullet. task: direct-lane-size-gate (gate PASS, authority plan, freshness fresh) milestone: right-sized-lane closed 3/3 tests: add-method/ 726 passed 7 skipped; add-method/tooling/ 8 passed author: Tin Dang
`milestone_done` gates a milestone closed on a `- [x]`/`- [ ]` tally it parses out of `## EXIT`, but the engine shipped no verb that could write one. Every tick was a hand edit to markdown the engine reads — the milestone closed earlier in this session was ticked by a throwaway Python script, which is the same defect class as editing a frozen contract by hand: the state the gate turns on was reachable only from outside the engine. Add `add check REF N... [--all] [--off] [--section S] [--by WHO]`. Indices are 1-based in document order; `--section` narrows to one heading and re-indexes within it; boxes inside fenced code blocks are skipped, so the number a human counts off the rendered file is the number the verb writes to. Every index is validated before any line is rewritten and the write is one atomic replace, so a refusal or a crash leaves the node fully old or fully new. A box already in the requested state reports `unchanged` and writes nothing, making re-runs safe. The verb deliberately does NOT defend the goal-gate. Offered the narrower notary design — tick only boxes whose `(<- task)` referent is done and gated PASS — the human chose reach: any box, any node, any section, any caller. What replaces the defence is attribution. Each invocation appends exactly one `verified:` stamp naming who moved which boxes in which section, an unattributed call stamps the literal `process:check` rather than a blank, and `milestone_done`'s close line now reads `(4/4 exit criteria met, checked by <who>)` — or `checked by hand` for a milestone ticked before this verb existed, inventing no name. A self-served goal-gate is now visible at the moment it closes instead of being indistinguishable from a human affirmation. `check` and `milestone_done` share ONE compiled box pattern, so the verb can never tick a box the goal-gate cannot count. The verb count moves 22 -> 23, which five registries enumerate: the CLI's WIRED set, both README kernel counts, the book command reference, and the phantom fixture in test_shipped_docs.py that used `add check` as its example of a verb the engine lacks (now `add sharpen`). loop.md's milestone-close step no longer tells the reader to hand-edit the tally. task: box-check-verb (gate PASS, authority plan, freshness fresh) milestone: checkbox-verb closed 4/4 — ticked with the verb it ships tests: 17 new engine guards; add-method/ 745 passed 7 skipped; tooling/ 8 passed author: Tin Dang
Hand-driving a real bundle surfaced `2 box marked` and `unchanged — box 2, 3`. The tests all passed: every assertion checked the count and the box text, none read the sentence as an operator would. Manual use is what caught it. Direct lane (mechanical, one string, zero unknowns): the assertion was extended and run red first, all four engine twins carry it, ENGINE_MD5 re-aimed. tests: 18 engine guards; add-method/ 746 passed 7 skipped; tooling/ 8 passed author: Tin Dang
Real exit criteria wrap across source lines, and the listing showed only the first one — every row on the live `affordance-truth` milestone trailed off mid-sentence. That listing is what an operator picks an index from, so a clipped preview is a correctness problem for the choice, not a cosmetic one. Join indented continuation lines into the preview text. Display only: the node keeps its own wrapping byte for byte, and writes still go by line index, so `check` remains read-only until it marks something. A continuation is an indented non-empty line that opens no new box, heading or fence. Direct lane (mechanical, zero unknowns): assertion written and run red first, all four engine twins carry it, ENGINE_MD5 re-aimed. tests: 19 engine guards; add-method/ 747 passed 7 skipped; tooling/ 8 passed author: Tin Dang
…heck` Both defects the manual drive found — `2 box marked`, and a criterion listing clipped at its first physical line — were green under 17 guards. The lesson in both directions is the same: assert the rendered line, and reassemble what the file wraps, because the preview is what the operator acts on. author: Tin Dang
A four-lens adversarial review (method integrity · trust boundary · adoption
cost · engine architecture) found ADD's mechanical enforcement sitting entirely
inside `freeze` and `gate`, and both reachable around. The unifying defect: every
guard fired on the PRESENCE of a malformed thing and never on the ABSENCE of a
required one, so the way past each refusal was to DELETE rather than to forge.
Four refusals added, one coercion, one sanitiser. No verb removed, no message
weakened:
* `gate` refuses a PASS on a node carrying no freeze/refreeze stamp, at every
Task depth (R:UNSEALED). Every post-freeze guard — drift, brief entry,
R:UNBRIEFED — was keyed off `if sealed:` with no else, so a node that skipped
the ONE approval did not FAIL those checks, it switched them off. The
pre-seal tolerance is unchanged: a missing DIGEST is still tolerated, only a
missing STAMP refuses.
* `run` claims `kind: test-ids` only for a junit report written during the run,
and the receipt SAYS when it downgrades to `command-exit`. A hand-written XML
beside `/usr/bin/true` earned the strongest evidence rung.
* `authority_for` matches `sensitive_paths:` through a directory scope, both
directions — `scope: src/` did not match `src/auth/*`, so declaring a
BROADER, honest scope LOWERED authority below one naming the file exactly.
* `gate` refuses a PASS when a changed file matching `sensitive_paths:` is
covered by no declared scope entry (R:UNDECLARED_SENSITIVE). Deliberately
narrow — an ordinary undeclared path stays freshness's business.
* `gate`'s `--reason` is flattened the way `replan` already flattened its note:
an unbalanced brace made the parser swallow the FOLLOWING stamp, so two
records were written and one read back from an append-only ledger whose
ordering IS the trust model.
* `_scope_list` coerces a single-entry `scope: src/ui.py` — it parses as a
STRING, and every reader iterated it per character, resolving `/` to the
filesystem root.
* `check` records the caller context (`via: tty | process`) beside the claimed
`--by` name, and `milestone-done` marks unattended credit. `--by` is free
text, so `loop.md`'s "a box the AI ticked never reads as a human's" was
false; the name stays a claim, the context is fact. loop.md re-cut across
all three skill trees.
Fixture honesty, not test weakening: eleven suites hand-wrote a junit beside the
run or gated a node that was never frozen. Each now drives the real path —
freeze → brief → run (the command writes its own report) → gate. `spike_cli`'s
`brief` records the entry stamp, without which no drive can reach a PASS.
Recorded as /tasks/sealed-gate-enforcement.md (gated PASS at authority `plan`,
receipt runs/2.md, 15 checks bound); two lessons filed to specs/method and
specs/quality. Both md5 pins re-aimed, four engine twins mirrored.
Suite: 762 passed, 7 skipped (both roots).
author: Tin Dang
…thon
`add check` wrote its file back with a PEP 701 f-string — `f"...{"".join(lines)}"`,
double quotes nested inside double quotes — which is a SyntaxError on every
Python before 3.12. `add.py` therefore failed to IMPORT on 3.10, so CI's py 3.10
job died at `cli.py init` before a single test ran, and the whole suite was
skipped rather than red. Local runs on 3.14 never saw it.
Split the join out of the f-string. Every tracked .py now parses under 3.10, and
the full suite is green on both 3.10 and 3.14 (762 passed, 7 skipped).
Also removes an `engine_pin.py` that a mirror loop leaked into
`src/add_method/_bundled/tooling/` — the pin is dev-only and the bundle parity
guards name it as such.
ENGINE_MD5 re-aimed; four engine twins mirrored.
author: Tin Dang
`test_four_engine_twins_and_both_pins` hard-required all four twins, including the two `.add/tooling/` copies. Both are gitignored — they are materialised by `add init`, not tracked — so a fresh checkout has neither and the guard turned a clean clone red. It passed locally only because this working tree dogfoods both. The SHIPPED twin (`src/add_method/_bundled/tooling/`) stays hard-required: it is tracked, and a consumer installs what it holds. The dogfood twins exists-skip individually, matching `test_tree_parity.py`'s established convention — but any twin that IS present is still byte-compared, and the guard asserts at least one twin was checked, so the skip can never be what makes a drifted twin pass. Verified the way CI runs it: fresh clone -> `cli.py init` -> `pytest -q` from add-method/ on Python 3.10. 762 passed, 7 skipped. author: Tin Dang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two milestones, driven through ADD itself:
right-sized-lane(3/3) andcheckbox-verb(4/4).1 — Ceremony proportional to kind and size
ADD funnelled every request toward the full specification bundle: a typo fix and a new subsystem read the same instructions. The one escape hatch, the Quick lane, admitted only mechanical edits and carried no discipline beyond "make the edit".
A size threshold alone tells an agent when to skip the node and nothing about what it still owes, so this ships a ladder keyed on kind × size. Each rung states three things in one row — the route, the effort and review it owes, and what persists:
invariants:hold · self-reviewadd learnline--depth quickorstandard--kind explore## FINDINGSThe floor is read first and outranks size: security · data · architecture, a consumed
gives:surface, or frozen scope takes a node however small; security stays a HARD-STOP. The direct rung now admits small new behavior (previously mechanical-only) and in exchange owes a five-step checklist ending in a commit plus exactly oneadd learnline — its only bundle write.Ships to every reader, not just Claude:
intake.md+ the SKILL.md bullet in all three skill trees, this repo's CLAUDE.md block, and one byte-identical sizing sentence in both installer pointer twins, so a Cursor/Codex user who never loads the skill still gets it in their own CLAUDE.md. Also repairs that block's retired verbs —add.pyis the library and prints nothing (cli.pyis the entrypoint), andguideno longer exists.2 —
add check: the engine writes the checklist it readsmilestone_donegates a milestone closed on a- [x]/- [ ]tally it parses out of## EXIT, but no verb could write one. Every tick was a hand edit to markdown the engine reads — the first milestone in this PR was closed with a throwaway Python script, which is the same defect class as editing a frozen contract by hand: the state the gate turns on was reachable only from outside the engine.add check REF N... [--all] [--off] [--section S] [--by WHO]. 1-based in document order;--sectionnarrows and re-indexes; boxes inside fenced blocks are skipped so the number you count off the file is the number it writes to; all indices validated before any line is rewritten, one atomic replace, and an already-marked box reportsunchangedand writes nothing.On the goal-gate. Offered the narrower notary design — tick only boxes whose
(← task)referent is done and gated PASS — the maintainer chose reach: any box, any node, any caller. So the gate is self-serve by choice, and attribution replaces the defence. Each invocation appends oneverified:stamp naming who moved which boxes in which section; an unattributed call stamps the literalprocess:check; and the close line now reads:An agent's self-affirmation is visible in the sentence that closes the milestone. A milestone ticked before this verb existed closes
checked by hand, inventing no name.checkandmilestone_doneshare one compiledBOXpattern, so the verb can never tick a box the gate cannot count.The verb count moves 22 → 23, which five registries enumerate — the CLI's
WIREDset, both README kernel counts, the book command reference, and the phantom fixture that usedadd checkprecisely because no such verb existed. The full suite found them; a grep would not have.Verification
checkat all.checkbox-verbwas closed with the verb it ships.2 box marked(no plural), and a listing clipped at each criterion's first physical line. Both fixed here on the direct rung of the ladder above, red-first, with theiradd learnlines. That listing is what an operator picks an index from, so a clipped row is a correctness problem for the choice.19 engine guards. 747 passed / 7 skipped in
add-method/, 8 passed inadd-method/tooling/(which owns the pins). Four engine twins synced; bothENGINE_MD5andENGINE_PKG_MD5re-aimed.Reviewer notes
write()'s existingos.replace.test_every_registry_learned_the_new_verbhas never run red; the red evidence for that claim came from the five incumbent guards.3 — The refusals the docs promised, made real
After the two milestones above, a four-lens adversarial review (method integrity · trust boundary · adoption cost · engine architecture) went at the whole method. It found ADD's mechanical enforcement sitting entirely inside
freezeandgate— and both reachable around.The unifying defect: every guard fired on the PRESENCE of a malformed thing and never on the ABSENCE of a required one.
_section_ofreturns""for a missing heading and every consumer reads empty as clean, so the way past each refusal was to delete rather than to forge.The maintainer's call was to enforce more rather than soften the docs. Four refusals added, one coercion, one sanitiser — no verb removed, no message weakened:
gatePASS on a node with no freeze/refreeze stamp →R:UNSEALED, all Task depthsif sealed:with no else, so skipping the ONE approval did not fail drift +R:UNBRIEFED— it switched them off, gating with less scrutiny than a node that went through the approvalrunclaimskind: test-idsonly for a junit written during the run, and the receipt says when it downgrades tocommand-exit/usr/bin/true, naming tests that do not exist, earned the strongest evidence rungsensitive_paths:matches through a directory scope, both directionsscope: src/did not matchsrc/auth/*— declaring a broader, honest scope lowered authority below one naming the file exactlygatePASS when a changed file matchingsensitive_paths:is covered by no scope entry →R:UNDECLARED_SENSITIVEscope:defeated the one floor that does not rest on self-declared sensitivitygate's--reasonflattened, asreplanalready flattened its note{made the parser swallow the following stamp: two records written, one read back, from an append-only ledger whose ordering is the trust model_scope_listcoerces a string scopescope: src/ui.pyparses as a STRING and every reader iterated it — one freshness entry per character, with/resolving to the filesystem rootcheckrecordsvia: tty | processbeside the claimed name;milestone-donemarks(unattended)--byis free text, soloop.md's "a box the AI ticked never reads as a human's" was false. The name stays a claim; the caller context is fact. Prose re-cut across all three skill treesThe scope-vs-diff refusal is deliberately narrow — an ordinary undeclared path stays freshness's business, because a scope diff that refuses everything is a scope diff everyone learns to widen past.
Fixture honesty, not test weakening. Eleven suites broke, and every one of them was cheating in a way the new rules named: hand-writing a junit beside the run, or gating a node that was never frozen. Each now drives the real path — freeze → brief → run (the command writes its own report) → gate.
spike_cli'sbriefhad to start recording the entry stamp; without it no drive can reach a PASS. Nothing was relaxed to make a test go green.test_an_unsealed_node_is_exemptsurvives untouched: it strips thedirection:digest but keeps the freeze stamp, which is exactly where the line falls — a missing digest is tolerated (pre-3.0 bundles,R:RETROBREAK), a missing stamp refuses.Recorded as
/tasks/sealed-gate-enforcement.md, gated PASS at authorityplan— the engine computed that floor fromsensitivity: architectureand ignored the--authority humanclaim on the command line, which is the behaviour under test. 15 checks bound, receiptruns/2.md. Two lessons filed tospecs/methodandspecs/quality.762 passed / 7 skipped across both roots. Both md5 pins re-aimed, four engine twins mirrored.
Reviewer notes on this section
_changed_pathsreturns[]wherever git cannot answer, so the new sensitive-path refusal can only ever add a refusal where git is present, never invent one where it is not (E2pins this)._scope_list, the receipt numbering, theisinstanceguard) — cheap, but they are not enforcement, and shouldn't be read as such.