Skip to content

Two new commands, three fixes, and a benchmark that killed a feature - #2

Merged
lambiengcode merged 8 commits into
mainfrom
feat/doctor-install-and-verify-benchmark
Aug 25, 2026
Merged

Two new commands, three fixes, and a benchmark that killed a feature#2
lambiengcode merged 8 commits into
mainfrom
feat/doctor-install-and-verify-benchmark

Conversation

@lambiengcode

Copy link
Copy Markdown
Owner

Eight commits: two new commands, three correctness fixes, one new benchmark, and two documentation passes. The benchmark is the important one — it was built to decide whether a feature was worth writing, and it decided no.

A feature measured, then not built

reify verify — a post-flight check reading an agent's diff and reporting what the patch missed — looked like the strongest thing on the roadmap. reify-bench verify-eval measures whether the call graph can actually support it: withhold one hunk from a real merged commit, ask the graph what the patch missed, then run the complete commit through the same query, where every finding is a false positive by construction. That negative control is what stops the metric rewarding a checker that simply shouts.

The condition was written into metrics.rs before the first run: recall below 0.25, or false alarms above 0.1 per commit, means do not build.

Rust Python Go
finds the withheld hunk 0.50 0.10 0.40
false alarms per already-complete commit 23.5 6.9 4.4
verdict do not build do not build do not build

It fails on noise, not blindness. A CALLS edge says a caller exists; it does not say the caller needed changing, and nothing in the graph separates a changed signature from an edit inside a body. No rewrite around that edge removes it. Counting only findings the complete commit does not also produce, recall drops to 0.00 / 0.05 / 0.15 — most apparent hits were files the checker would have named anyway.

Model-free, deterministic, 116s for three repositories. AGENTS.md records the verdict so it is not rebuilt without beating those numbers first.

Two new commands

reify doctor — should this repository use Reify at all, answered before there is an index. Grounded in the four-repository benchmark's own measured predictor rather than invented heuristics: what separated the repositories where Reify helped from the one where it did not is whether commit subjects speak the vocabulary the code is named in. Candidate signals were validated against all four repositories, and commit focus is the only one that isolates Medusa; it reuses History::co_changes' existing 20-file threshold rather than inventing a second definition.

It is willing to say no. A 2-line repository gets TOO SMALL and nothing else is measured. This repository gets MARGINAL — at 53 commits the intervals straddle the thresholds, so a verdict either way would be an artefact of the sample. No suitability score: named signals, measured values, and what would change a no. Every run discloses that it is a heuristic fitted to four repositories and points at reify-bench for measuring a specific one.

reify install — detect the agents configured here and wire each one. It keeps docs/integration/'s existing position: level 0 by default, --mcp opting into the per-turn cost with a warning before it writes.

Detection requires repository evidence. An earlier build used home directories as triggers and created .cursorrules and AGENTS.md in a repository that had neither, purely because the machine has ~/.cursor; home evidence now corroborates and is reported, never acted on alone. Nothing outside the repository is written, which drops Codex/Windsurf MCP registration — their configs are machine-wide and a per-repo uninit could not safely undo one — so those get the instruction block with the reason stated.

Config merging is textual rather than a serde round-trip: adding to a hand-written .mcp.json changes exactly one line, an unrelated server's env block and the file's formatting survive byte-for-byte, and uninit restores the original byte-identically. Asserted in tests.

Three correctness fixes

  • reify impact <file> returned nothing while reify preflight on the same file reported 199 dependants. It seeded from the File node and walked inbound CALLS, but call edges attach to symbols. Two commands contradicting each other about one file is worse than either being silent, because an agent acts on the answer it was given.
  • A substring subject match mined an HTTP cache header as a business rule at 0.97 confidencevalidate sits inside revalidate, must supplied the polarity. The polarity test one line away was already word-boundaried. Django's rules go 180 → 152; all 30 losses were inspected individually and every one is a test name, a mangled doc snippet, or the same bug in French via premise/remise.
  • The benchmark prompt hardcoded (ERPNext), so three published tables told the model it was working on ERPNext while asking about Medusa, OFBiz or OpenMRS. Those reports carry a dated note rather than being regenerated from runs that were not redone.

Documentation

The README disagreed with itself: three different answers for its own index timings, a "six-line block" that showed four lines and matched neither the tool's output nor itself, completions documented twice, and the quickstart duplicating Install. Net 48 lines shorter, and the quickstart now leads with doctor and install — both blocks run as written against a scratch repository before being documented.

Every measurement section is untouched, including Medusa's no-win row and the end-to-end p-value section.

Test plan

  • cargo test --workspace — 426 passing
  • cargo clippy --workspace --all-targets clean
  • reify doctor on this repository, django, and a 2-line scratch repository: three distinct, defensible verdicts
  • reify install plan → apply → rerun → uninit, with an unrelated .mcp.json server surviving byte-identically
  • reify impact <file> reports 208 against preflight's 199, with a regression test that fails without the fix
  • reify-bench verify-eval reproduces the table above in 116s with no model
  • CI

`impact <file>` seeded its traversal from the File node and walked inbound
CALLS. Call edges attach to symbols, never to files, so a file argument could
only ever return "no dependants found" — while `preflight` on the same file,
using `symbols_in_file`, correctly reported 199. Two commands contradicting
each other about one file is worse than either being silent, because an agent
acts on the answer it was given.

Seed a file's symbols alongside the file itself, and follow inbound IMPORTS so
file-level importers are reported next to symbol-level callers. Symbol
arguments are untouched.

A file every module imports has hundreds of dependants, so the renderer leads
with the count and shows the nearest few as evidence rather than spending an
agent's budget to say "a lot".

The corpus-wide `impact >= preflight` invariant is satisfiable by IMPORTS edges
alone, so the test also pins what actually broke: a file's affected list must
contain a caller, which fails without this change.
…ready does

`classify_phrase` tested the subject with a raw substring match while the
polarity test on the next line used the word-boundaried `contains_word`. The
asymmetry read `must-revalidate` as the `validation` subject — `validate` sits
inside `revalidate` — with `must` supplying the polarity, so an HTTP cache
header was mined as a business rule at 0.97 confidence.

`contains_word` already falls back to substring for multi-word needles like
"credit limit" and for non-ASCII ones, so the multilingual corpus is unaffected.

Django's mined rules go 180 -> 152. All 30 losses were inspected: every one is a
test method name, a mangled documentation snippet, or the same bug in French
via premise/remise. Two genuine rules are newly gained.

Known and deliberately out of scope: the `validation` subject lacks the
inflections `approval` has, so prose like "the service validates the order" is
now missed where the loose match caught it incidentally. Restoring those
inflections would also restore the test-name noise this removes; that is a
tuning decision about the subject table, not part of this bug.
`prompt()` hardcoded "(ERPNext)" with no parameter, so every model-in-the-loop
run against Medusa, OFBiz and OpenMRS told the model it was working on ERPNext
while asking about a different codebase in a different language.

It does not obviously favour one arm — every condition shared the same wrong
name, and the prompt-differs-only-in-context test still holds — but it is a
validity defect in three published tables. The name now comes from
`TaskSet::repository`.

The three affected reports carry a dated note stating the defect and its scope
rather than being silently regenerated from runs that were not redone.
`reify verify` — a post-flight check that reads an agent's diff and reports
what the patch missed — is only worth building if the call graph can support
it. This measures that before the feature is written.

For each qualifying merged commit the parent tree is indexed, one file's only
hunk is withheld, and the truncated patch goes to the checker. The same commit
then goes through complete, where every finding is a false positive by
construction. That negative control is what stops the metric rewarding a
checker that simply shouts.

The checker is the shipped graph query reached through `query::impact`, not a
new one, so this measures the substrate the decision needs.

The pre-registered condition — recall below 0.25 or false alarms above 0.1 per
commit means do not build — was written into metrics.rs before the first run.

It fires on all three repositories. Recall is 0.50 on Rust, 0.10 on Python and
0.40 on Go, but false alarms run 4.4 to 23.5 per already-complete commit. It
fails on noise, not blindness: a CALLS edge says a caller exists, not that the
caller needed changing, and nothing in the graph separates a changed signature
from an edit inside a body. No rewrite around that edge removes it.

`reify verify` is therefore not built. 116s, no model, no network.

AGENTS.md records the verdict so it is not rebuilt without beating these
numbers first.
The README disagreed with itself in ways that undercut the measurement
discipline it is arguing for.

- Three different answers for its own index timings. The install section said
  4.6s/0.7s where the measured table says 4.2s/0.49s. Unified to the measured
  numbers.
- It said `init` appends "a six-line block", then showed a four-line block and
  called it "the same four lines". Neither matched what the tool writes. It now
  shows the actual constant, including the two lines the paraphrase dropped.
- Shell completions were documented twice, forty lines apart.
- The quickstart and Install repeated the same commands and the same uninstall
  explanation; Install now carries only what the quickstart does not.
- The optimisation war stories are compressed to a paragraph pointing at the
  changelog, keeping the `git log -L` lazy-fetch story because it is
  load-bearing for the privacy claim rather than for speed.

Adds the verify-benchmark section, and records that `impact` now takes a file.

Net 48 lines shorter. Every measurement section — Medusa's no-win row, "where
it doesn't work", the p-value section — is untouched.
Nothing told anyone whether Reify would help before they invested in it.
The README FAQ answered it informally and `benchmarks/REPORT-medusa.md`
already shows the advantage can be zero on a real repository. `reify doctor`
makes that measured instead of anecdotal, and runs before there is an index —
the whole point is deciding before committing to the tool.

The signals were fitted against the four repositories in `benchmarks/`, not
invented. Two hypotheses were tested and dropped: size inverts (OFBiz wins
biggest at 1,364 code files, Medusa ties at 11,821), and so does the obvious
reading of "history and file naming share vocabulary" — pooled corpus-wide,
Medusa scores 0.43 against OFBiz's 0.38.

Two signals do fit all four outcomes, each explaining a different failure:

  repo      grep margin   commit focus   subject->path
  OFBiz            +58           0.96            0.80
  ERPNext          +48           0.98            0.85
  OpenMRS           +9           0.98            0.48
  Medusa             0           0.84            0.79

Commit focus is the share of commits touching few enough files that their
subject says anything about them; Medusa is the only measured repository
where it falls away and the only one where Reify won nothing. The mechanism
is already load-bearing elsewhere: `History::co_changes` discards commits
touching more than 20 files for the same reason, so `doctor` uses the same
threshold rather than a second definition of a sweeping commit.

No suitability score. `docs/metrics.md` forbids printing a number that cannot
be defined, and a weighted blend of heuristics tuned on four repositories is
exactly that. Named signals, measured values, a plain verdict, and — where the
answer is no or marginal — what would change it.

The verdict floor is 200 commits, which is where a 95% Wilson interval around
Medusa's 0.84 lies wholly below the threshold; at 50 it straddles it, so a
shorter history is reported as short rather than condemned.

Verified on all four benchmarked repositories, which land where their reports
say they should, plus django (yes) and a two-commit scratch repository (too
small). No new dependency; the offline guarantee is untouched.
`reify init --write-agent-instructions` handled one file, chosen from a list
four filenames long. `reify install` detects which agents this repository is
actually configured for and wires each one in its own format and location,
shows its plan before writing, and is fully reversed by `reify uninit`.

It installs the shell-command instruction block, not MCP. That is the
existing reasoned position in docs/integration/claude-code.md — an MCP
server's tool schemas are re-sent on every turn of every session, and for a
tool whose purpose is reducing context, a per-turn tax to deliver it is
self-defeating — and nothing here overturns it. Every agent this can detect
runs shell commands, so level 0 is right for all of them. `--mcp` is the
deliberate opt-in and says what it costs before it writes anything.

Two rules do most of the work:

Detection requires evidence *in the repository*. `~/.cursor` says the user has
Cursor installed, not that this repository is worked on with it; creating a
`.cursorrules` on that basis is exactly the guess the command must not make.
Home directories are read, reported as corroboration, and never acted on
alone — an agent seen only there is listed so its absence from the plan is
explained. Every detection prints what it rests on.

Everything written stays inside the repository. A machine-wide MCP
registration cannot be undone by a per-repository `uninit` without breaking
every other repository relying on it, so the MCP entries written are the
repository-scoped ones and a client with no such config gets the block
instead, with the reason stated.

Config merging is textual, not a serde round-trip: re-serialising sorts a
user's keys and drops their formatting, and the file is theirs. Adding the
entry to a hand-written `.mcp.json` changes exactly one line, an unrelated
server's env block survives byte for byte, removal restores the original
exactly, and the result is parsed before it is written so a bad splice fails
loudly. A config that exists but does not parse is reported and skipped —
never overwritten.

`uninit` derives its removal targets from the same table `install` plans from,
so a new agent cannot be added to one without appearing in the other, and a
test asserts it. The MCP surface stays at six tools: these are operator
commands and belong to the CLI.
The quickstart still taught `init --write-agent-instructions`, the hand-wiring
path, and skipped the question `doctor` exists to answer. It now runs the two
commands in the order someone actually needs them: should you use this, then
wire it.

The Install section gains the same pair, with what `install` detects, that an
agent present on the machine but not configured in the repository is reported
rather than written to, and that nothing outside the repository is touched.

Both blocks were run as written against a scratch repository before being
documented.
@lambiengcode
lambiengcode merged commit de84695 into main Aug 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant