The ticket blamed an unbounded grammar walk. Instrumenting the halves
separately: load_grammar() x1000 = 2.50s against a 15.7ms walk, so the walk
was 0.6% of the cost and improv_file() was re-parsing improv.pest once per
generated case. A OnceLock took improvise-io's lib from 1.42s to 0.21s with
the strategy's input space unchanged; constraining it would have moved 15.7ms
and shipped as the fix.
Re-measured here rather than taking the hand-back's figure, which differed:
1445 tests over 34 binaries, longest single binary 0.6-1.0s across two runs on
one machine. That spread, on one machine in one sitting, is its own argument
for reading your own run.
Also corrects the repo map's bug-fix workflow line, which said to document a
bug in the test's doc comment — the two-line comment cap forbids exactly that
and design-principles §7 says the opposite. A fact stated twice, changed by an
edit rather than a grep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Both blockers from the adversarial review of #83, both in the mode this PR
rewrote, both producing a pass while the gate did not do what its prose said.
1. cargo_test_names reduced every listed path to its last segment. Six leaf
names collide in this workspace and two are cited in evidence, so a heading was
satisfiable by a test in another module answering another question -- renaming
ui::project::tests::a_load_that_fails_is_not_a_rebase, the one its entry
describes, left the checker at exit 0 with the ui::host namesake answering for
it. Paths are kept whole; a bare leaf resolves only when unique, and an
ambiguous one is REFUSED with both candidates named. _RUST_NAME accepts `::`, so
a qualified heading is a claim rather than prose, and a citation is matched on
the leaf so qualifying a heading cannot orphan its own test. The two colliding
entries are qualified. Neither half suffices alone: rename one of a colliding
pair and the leaf becomes unique again, which is why qualification is the fix
and the refusal is what forces it.
2. --rust-tests called cargo_test_names with one argument, so the --ignored
subtraction never ran, and it printed a byte-identical success line. It now
requires --rust-tests-ignored (one alone exits 2) and the line names its source.
Also, from the same review: a missing context/test-evidence/ exited 0 while 170
citations pointed at nothing (early return, now gone); the doc-site "runs last"
check accepted a trailing `echo` that merely NAMED the generator, so it asks for
an invocation; bundle_freedom gains an arm for a transitive `import "../app.js"`,
which reaches the bundle while naming nothing forbidden; and two prose overclaims
are corrected -- the prose-heading count is a convenience rather than a guard
(git diff is the mechanism), and the ratio rule says "above the run MEDIAN",
which is defensible where "above the others" is false against second place.
scripts/check-ci-graph.py makes claim 6's rule a gate: pages-deploy must `needs`
every other job in ci.yml. The rule was stated in the last commit and a
zz-new-check: job added during review was still ungated with nothing noticing.
README now states the rule and lists no jobs, the second copy being what went
stale twice.
Every fault re-injected and observed; figures in context/test-evidence/tooling.md
rather than only here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Adversarial review injected the other fault -- Some(_existing) => {}, which
drops the duplicate section outright -- and the whole improvise-io suite stayed
green at 2000 proptest cases while the fixture's item and its group line
vanished from the document. A test that compares two printed forms cannot tell
unstable-but-lossless from stable-and-lossy, because both sides of the equality
lose the same thing. Three contains assertions now run before the equality, and
they fail under that injection.
Two more from the same review. push_category destructures PCategory, so a fourth
field has to be named rather than silently dropped on the duplicate path only.
And design-principles.md's formula-tokenizer section described a tokenizer that
no longer exists -- a split_where pre-parse step and Token::Ident/Token::Str,
none of which are in the tree -- and claimed bare identifiers allow spaces,
which bare_ident forbids. This branch changes that production, so correcting the
sibling copy is the grep gotcha 48 asks for.
improvise-349m improvise-r08u
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
84wp's prose applied to §7 verbatim as filed. It names the shape three PR #59/#60
findings shared -- a vacuous assertion, a vacuous repair of it, and a clean
auto-merge -- and the remedy each needs: name the question you actually mean,
then find an input that separates it from its neighbour. It generalises
doctrine(fold_tests_must_test_order_not_the_axiom) rather than replacing it.
8ec3's second half. The 2s-per-binary rule kept a figure measured on one machine
("longest single binary 1.42s") as though it were a property of the suite; the
same commit measured 3.70-5.24s elsewhere. §7 now says re-measure, name your
machine if you cite a number, and read the RATIO -- one binary an order of
magnitude above the others -- because that is what reproduces. Measured here
over three runs: improvise-io lib 1.40-1.45s against a ~0.06s median, the same
binary the slow machine named.
The first half (constrain the .improv grammar generator's strategy, never the
case count) is improvise-8shu.
doctrine(suite_speed) and context/repo-map.md carry the same pinned figure and
are orchestrator-only; the replacement text is in the hand-back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Four shapes let a heading name something that never runs: a test inside a raw
string literal, one behind an unenabled cfg feature, one behind
cfg(target_arch = "wasm32"), and one behind #[ignore]. All four cleared the
scrape; injecting them together made the previous checker print "543 entries,
all name a test that exists", exit 0.
--check-evidence now takes the Rust names from `cargo test --workspace -- --list`
minus its `--ignored` twin. All 539 live headings resolve against a real listing,
so the stricter source costs nothing today. rust_tests is rust_citations: the
scrape survives only for the reverse direction, where a false positive is loud.
Prose headings become a third kind, counted and printed, so retiring a claim by
rewriting it as prose moves a number instead of passing in silence
(improvise-9za9). No gate can read prose for truth; the reviewer is the
mechanism, and the diff is what to read.
context/code/tooling.md is new and is the file four docstrings in
check-comment-runs.py already pointed at. design-principles §7 stopped saying
the check runs in one direction, which improvise-lyyc had already made false.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
User doctrine, 2026-09-20: 'E2E and integration tests should be rare and focus
on boundary contracts. Unit tests should assume the boundary contracts tested
by E2E and integration tests and test between the boundaries.'
Recorded with the worked example that makes it actionable rather than
aspirational. Four browser widgets had never worked and all four were ONE
boundary failure — a RenderModel field with no element painting it. Per-widget
specs cost four slow tests and would still miss the fifth; one contract
assertion that every exposed field is painted catches all four and the next.
opfs.spec.js is the shape already in the tree: it runs the storage contract
rather than a feature.
The corollary that is easy to miss: a unit test is cheap precisely BECAUSE it
assumes the contract, which means deleting a contract test silently invalidates
every unit test leaning on it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both are the reviewers' framings rather than mine, and both are better arguments
than the ones I gave.
AGENTS.md: under merge-as-soon-as-green a review round can finish AFTER its own
merge, and GitHub has deleted the branch by then — so a fix push to the old name
silently succeeds and accomplishes nothing. It re-creates a branch main will
never see, CI runs on it and passes, and the commit is nowhere. The tell is in
git's own output: `* [new branch]` where an `old..new` range was expected.
Happened on #51, four minutes after the merge.
design-principles §6: an argument must not live only in a commit message. I had
justified the context/ rule as "context/ is what readers grep", which is weak.
The real reason is that a commit message is write-once and unauditable — it
cannot be corrected without rewriting merged history, and NO check in this repo
can read one, because the ID audit greps source and docs and a message is in
neither. A reason recorded only there is a reason nothing can ever find wrong.
PR #51's 7d25762 is the worked example: a declined-test reason that was
factually wrong, cited across two rounds, and correctable only by #56 saying so
in context/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four findings, all verified against the tree before acting.
F10 — this PR's own edit to design-principles §5 made a sibling note in
context/code/command.md false: it said §5 "still names Sessions::apply" and
recommended apply_checked. A PR whose whole subject is stale prose had left a
new stale copy behind, which is gotcha 48 exactly ("changing it is a grep, not
an edit"). Note deleted; the verified claim above it stands.
F11 — §5 named both apply and apply_checked as "the one door". apply has no
production caller anywhere in the workspace; Host calls apply_checked. Naming
two weakens the claim §5 makes, so it now names the one production uses.
F12 — gotcha 17 said "four call sites". There are six uses across the two
evaluators: a div/0 guard plus the Eq and Ne arms in each. In a file agents
follow literally, a reader who greps and finds six cannot tell whether the
prose or the code moved.
F13 — gotcha 17's sibling said the Dirty::of doc comment "keeps only the
one-line reason LayoutGeometry is spelled out rather than EVERYTHING". It says
no such thing: it points at context/code/command.md, and the EVERYTHING reason
is a two-line comment inside the match body. A correction to prose that
asserted the negation of the code should not itself misdescribe the code.
Two findings deliberately not fixed here and filed instead: improvise-ahdf
(repo-map gotchas 36 and 43 carry the same stale Sessions::apply claim —
pre-existing, and inside long paragraphs that need reading rather than a sed)
and improvise-rm4s (--check-evidence accepts headings naming tests that never
run).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Orchestrator doc pass over the comment wave. Each claim was verified against
the tree rather than taken from the queue.
Gotcha 17 recorded the *opposite* of the code: it said float equality was
"currently mixed", with `=`/`!=` on a 1e-10 epsilon and the div-by-zero guard
checking `rv == 0.0` exactly. Both now go through FLOAT_EQ_EPSILON, at four
call sites across eval_expr and its aggregated twin. Rewritten to say so and
to point at context/code/core.md §4.
Gotcha 35 called the doc comment on `Dirty::of` "the only record" of its
derivation. The comment wave moved that reasoning into
context/code/command.md and left the comment holding one line, so the
instruction to re-read on reclassification now names the prose.
design-principles §5 named `Sessions::apply` as dispatch; `apply_checked` is
what `Host` actually calls since improvise-955n answered a whole frame at
once. Both are real, so the row names both.
improvise-mbmf: the_ranking_is_ordered_shares_with_the_unused_features_dropped
asserted a length of 2 from three features — the zero-importance one dropped —
under the message "the zero-importance feature is listed". The message is read
only on failure, which is precisely when it would have described the wrong
behaviour as the requirement. The test name and doc comment were already right.
cargo test --workspace: 1311 passing, unchanged.
Closes improvise-mbmf.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings from the review on #33, and the second is the sharper one.
The checker skipped harness/opfs-contract/src/lib.rs — a shipped Rust source
tree with 14 runs over the limit, including a 22-line module doc — so it could
have reported success while the convention was violated. harness/ is now in the
default roots.
And design-principles claimed a renamed test was made detectable because 'the
checker looks for both halves'. It did not: it counted comment lines and listed
doctest fences, and nothing read context/test-evidence/ at all. So the cost the
convention says is controlled was uncontrolled, in the same commit that
introduced the convention — a reviewed paragraph is not evidence, and this one
was mine.
--check-evidence now requires every '### name' heading to name a test that
exists. One direction only: a test may have no evidence (most do not), but
evidence naming a test nobody can find is a dangling explanation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The user's instruction (2026-09-20): reasoning moves out of code comments into
context/ prose, and a comment run is at most two consecutive lines — `///` and
`//!` included, and test doc-comments too.
scripts/check-comment-runs.py makes the rule checkable rather than remembered,
which is the difference between a convention and a guard. It reports 1321 runs
over the limit today; the move itself is improvise-9c8c.
A ``` fence in a doc comment is exempt: a doctest is a running test rather than
prose, and capping around one would delete it. `--list-doctests` finds the six
blocks that need individual decisions.
design-principles §7 said to record a bug in the test's doc-comment, which the
cap contradicts — that evidence now goes to context/test-evidence/ keyed by test
name, with the orphaning cost stated rather than discovered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review findings on PR #30, all three applied.
1 [P1]. The claim that the handshake watchdog overwrites a genuine mid-session
disconnect 8s later is FALSE on any tree that has PR #21. `close` routes BOTH
arms through `setShellFailure` (app.js:248-250), which clears the timer
synchronously (app.js:165-169).
It was believed because it was measured honestly — against a tree predating
PR #21 — and then reported as "surviving" that PR on the strength of a
simulation that re-created only the `opened` flag and not the other half of the
fix, the routing. A simulated fix that is not the fix you named reproduces a
bug the real code does not have, and reads exactly like a confirmation.
Rather than delete the claim, execute the property it denied: the
open-then-dropped test now runs past HANDSHAKE_TIMEOUT_MS + 2s and asserts
"sent no document" never appears. Proved discriminating by removing
`setShellFailure`'s `clearTimeout` — the assertion then fails with exactly the
message xps8 described. gotcha 48 and the spec's comment block corrected; the
ticket is cited in neither. Recorded as
doctrine(a_simulated_fix_must_be_the_fix_you_named), the sibling of
an_injected_fault_must_be_the_fault_you_named that fails the other way: a wrong
fault proves a check that cannot fire, a wrong simulated fix proves a bug that
does not exist.
2 [P1]. The previous commit created the contradiction it was meant to remove:
design-principles recorded the measured ~2.2s while kb.pl still said "under 2
seconds". The ambiguity between per-binary and wall clock is what let it drift,
so both now state two numbers — no single binary over 2s, wall clock is the
sum, measured 1309 tests / 33 binaries / ~2.2s / longest 1.42s — in kb.pl,
design-principles and repo-map alike. Also 16 -> 17 in doctrine(binding_dump_diff).
3 [P2]. The new audit exclusion was unanchored, so `improvise-gp` was a prefix
match that would silently exclude a real future ticket. Anchored `^…$`. It
immediately caught the example id the explanatory paragraph itself spelled out
— a recipe is code, twice over.
CLAUDE.md: declined, with evidence. Both copies of the duplicated tail sit
inside `<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:ca08a54f -->`
markers written by `bd setup claude` and `bd setup codex` and refreshed against
that hash. A process change there is one tool invocation, not two hand edits,
and a pointer makes `bd setup <recipe> --check` report it modified. Recorded as
kb_issue_found(claude_md_tail_is_generated_not_duplicated) so it is not
re-proposed.
kb.pl: doctrine 54->55, kb_issue_found 11->12; invariant 51, decision 47,
location 34 unchanged. kb_consistent passes.
cargo test --workspace: 1309 passed, 0 failed, 2 ignored.
harness (Playwright): 12 passed, 3 skipped (webkit has no OPFS).
Dead-ID audit with the anchored recipe: 157 tokens, all resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An independent review checked every factual claim in the docs against the
code at 22d43b2, after eight PRs landed in one wave. This applies its
replacement text.
context/repo-map.md
- gotcha 44 rewritten: the cache DOES frame the grid now (improvise-anr9,
AxisFrame/FrameEntry/header_level_widths). The surviving half — the TUI
builds at offsets zero, so a cache position is absolute — is kept,
because four code comments cite gotcha 44 for exactly that.
- gotcha 45: ClientFrame::Save exists (improvise-368h); MarkDirty is gone
and dirtiness is read off EffectKind::Model(_); Rc<T>: Storage lives in
improvise-io (improvise-hmws); OpfsFs::list joins dir (improvise-vhcz);
OpfsFs is run by harness/ and bindings.rs is now the dark corner.
- gotcha 47: Host::commands answers the frame whole (improvise-955n); the
open hole is clause 4, not a per-command Checked. set_viewport "must"
-> "does".
- gotcha 43: the frame, not the command, is what is answered; two of three
tiers hoist.
- gotcha 21 widened: three effects abort, and tidy-up goes at the head.
- gotcha 48: web/app.js is executed by harness/tests/shell.spec.js;
improvise-m18f -> improvise-taxx (fixed); the open half is improvise-xps8.
- gotcha 42: the never-rendered / improvise-hwpi sentence pair deleted.
- gotcha 18: live_records and analyze_records are both decided now.
- gotcha 1: the records columns are [coords…, +cat, measures…, +].
- AppMode is 17 variants, ModeKey is 17, eight text-entry modes; the keymap
table moved to improvise-protocol/src/keymap.rs (six references).
- the Wasm section: the mirror is gone; the wasm gate covers eight crates.
- the File Inventory regenerated rather than patched (57/79 rows were
stale); the duplicate ws-server/host.rs collapsed to a pointer at the
23-line shim; harness/, protocol keymap.rs/viewmodel.rs, improvise-gp
fit.rs/dataset.rs, cmd/fit.rs, model/record.rs and the integration tests
added.
- the suite speed re-measured rather than re-asserted.
README.md — the four "no browser here" sites narrowed to what is still true;
17 modes; the browser section names the Playwright harness.
AGENTS.md — the dead-ID audit recipe was itself defective: two paths, a
character class blind to a commit SHA, and a two-name exclusion list. Widened,
made runnable under the worktree shell guard, and its own prose no longer
spells any id.
prolog-sessions/kb.pl — seven invariants replaced in place (they were false),
five location/3 facts re-sited plus three new, six doctrines and seven
invariants added. doctrine 48->54, invariant 44->51, decision 47 unchanged,
location 31->34. kb_consistent passes.
Comment-only Rust changes: a duplicated sentence in worker.rs, records_columns'
doc omitting NewCategory, improvise-709f2df -> commit 709f2df, and three more
copies of "OpfsFs has never been run".
cargo test --workspace: 1309 passed, 0 failed, 2 ignored — unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The check gotcha 44's divergence never had. Each front end is driven the way
it is really driven — the widget into a ratatui buffer, the browser client
through a `Snapshot` of that same cache's `seed()` — and they are compared on
what they *draw*, not on what they were handed; comparing the caches would be
vacuous, since it is one cache.
Injected both faults and watched them fail before keeping it:
`labels: vec![rows.item(r).join("/")]` in the client's `render_model`, and the
same join in the widget's row header. Each names the row and both spellings.
`improvise-wasm-client` becomes a dev-dependency of the root crate for this.
It builds natively — that is how its own tests already run — and being a dev
dependency the terminal binary does not grow a wasm client.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
User ruling 2026-09-20: minimise the amount of new code a new UI framework
needs. Centralise logic in a redux-like store so a second or third front end
is a function from state to pixels plus a wire for events, not a second
implementation of how the application behaves.
Names the pieces that already exist as what they are -- Command is the
action, reduce_view and WidgetCache::apply are the reducer, ViewState plus
the caches are the store, Sessions::apply is dispatch -- and gives the
question to ask of any front-end code: would a third front end have to write
this again?
Three things make it concrete: the measure is lines of framework-specific
code and should trend to 'paint and wire'; a derivation done twice is the
defect even when both copies are correct today (improvise-edp is the worked
example, gotcha 44 is the same defect still open); and sharing a table beats
testing two copies of it, which is why the keymap is shared rather than
mirrored. Also says what legitimately stays local: painting, event wiring,
and a semantic-value-to-framework-type lookup like AxisColor -> axis_fg.
Sections 5-7 renumbered; the §6 testing references in repo-map.md and the
code-review skill updated with them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Add a new design principle encouraging decomposition of functions instead
of relying on early returns, as early returns often signal mixed
responsibilities.
Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
- Document PEG grammar as single source of truth for .improv format
- Update file format section with v2025-04-09 syntax: version line,
Initial View, pipe quoting, Views→Formulas→Categories→Data order
- Add pipe quoting convention and grammar-driven testing principles
- Update file inventory (persistence: 124+2291 lines, 83 tests)
- Add pest/pest_meta to dependency table
- Update persistence testing guidance for grammar-walking generator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Executed-By: spot
Add VirtualMeasure to CategoryKind description, document _Measure as
third always-present virtual category, add section on fixed-point
formula evaluation strategy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add virtual category boundary rule: use regular_category_names() for
user-facing logic, never expose _Index/_Dim
- Document formula tokenizer keyword-aware identifier breaking
- Update repo-map test counts (356 → 510) and add regular_category_names
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add new context files to assist with repository navigation and design
consistency:
- context/repo-map.md: A roadmap for the repository.
- context/design-principles.md: Guidelines for maintaining repository
consistency.
Update CLAUDE.md to include instructions on using the new context files.
Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)