Skip to content

Graph capability repairs — the 0.7.0 seams, simulation-verified#224

Closed
qwerfunch wants to merge 11 commits into
developfrom
fix/graph-seams
Closed

Graph capability repairs — the 0.7.0 seams, simulation-verified#224
qwerfunch wants to merge 11 commits into
developfrom
fix/graph-seams

Conversation

@qwerfunch

Copy link
Copy Markdown
Owner

2026-07-02 · fix/graph-seams → develop

Repairs the seams a deep multi-agent review found in the 0.7.0 graph capability. Every fix was verified by simulation against the real repo before it was coded; the full strict gate is green.

Heads-up: two behavior changes for tool callers. Asking for the graph without a focus now returns a small stats summary instead of the whole graph (the full dump measured ~70k tokens in a single reply). And the measurement report now attributes its "N× smaller" numbers to the token budget rather than presenting them as structural savings. Everything else is bug-fix level.

Fixed

The after-edit impact card never fired in real use. Editors send absolute file paths; the index is repo-relative. Hit rate 0/361 → 99.2%, now pinned by an end-to-end test.

Docs referencing older features were invisible to the doc graph. The scanner only recognized the new id format — 10 real references restored, zero new warnings, one shared id lexer so the two scan sites can't diverge again.

Shared files under-reported their blast radius. Asking about a file owned by several features counted only the first owner (a hub file reported 0 impacted instead of the real 83). All owners count now — and the payload still honors its token budget: deeper dependents clip first, the direct set is never dropped, every clip is reported.

The live graph could show a stale picture. Changes that didn't alter the node count never redrew; the page now compares actual content. A broken spec answers 503 with a JSON error instead of 200-with-prose; a busy port fails with one clean line instead of a stack trace; foreign Host headers are refused; the health endpoint parses the spec once instead of ~10× per request (611ms → 21ms for the detector pass).

Three source files were invisible to code review. Raw NUL bytes made git treat them as binary — the graph core shipped with an unreviewable diff. Replaced with escapes (byte-identical at runtime) and guarded by a hygiene test.

One file, one focus. A file that is a module and a test at once got disconnected graph nodes; focus queries and health badges now cover all of a path's nodes.

▸ Assorted robustness: typo'd export options fail loudly, diagram labels escape their special characters, a corrupt log line no longer crashes the events tool, the mobile sidebar button works, and the bundled 3D library keeps its MIT notice.

Changed

Documentation tells the truth about what shipped. The design doc records the "no custom viewer" reversal in a dated addendum; the 0.7.0 changelog section is corrected in place (it described an earlier prototype that never shipped); the efficiency case study carries the budget-attribution correction; deprecation notices say 0.8 (0.7 still shipped every alias).

Dependency inference sees more of what's real. Re-export ("barrel") files and literal dynamic imports now produce edges; non-literal dynamic imports flag the file honestly; the JS/TS extraction path has fixtures for the first time.

Verification

▸ 10 fix designs simulated deterministically against the live repo before implementation (before/after measured; two designs were adjusted by what the simulations found).
▸ Full suite 1681/1681 green · clad check --tier=pre-push --strict green · live smoke on the built binary (endpoints, 403 host guard, health timing, format validation).

🤖 Generated with Claude Code

qwerfunch and others added 11 commits July 2, 2026 12:19
…e binary to git

src/graph/model.ts (edge-dedup key), src/optimizer/infer-depends-on.ts
(edge-map key), src/spec/attestation.ts (sha256 separators) carried literal
0x00 chars inside string/template literals. Git's binary heuristic then hid
those files from every diff/blame/review — the 0.7.0 graph core shipped as
"Bin 0 -> 9363 bytes" with a review-invisible diff. The escape spelling is
byte-identical at runtime (attestation digests unchanged; verified by
simulation before the change).

Adds a self-consistency test banning raw NUL bytes across the source tree
so a file can never silently become review-invisible again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…id lexer

doc-references.ts matched only 6-8 hex ids, so every legacy sequential id
(F-001…F-083 — 80 live shards) referenced in docs/ produced no doc→feature
edge and no DOC_LINK_INTEGRITY validation, while graph-health.ts already
carried the correct alternation. src/spec/feature-id.ts is now the single
prose-scanning lexer for both sites (fresh RegExp per call — no shared /g
state). Simulated on the live repo before implementing: +10 refs gained,
all 10 resolve to real shards (0 new warns), hash-id extraction
byte-identical, references edges 8→18.

spec/_doc-links.yaml regeneration happens via clad sync at branch close.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hosts send tool_input.file_path ABSOLUTE while moduleOwners keys are the
spec's repo-relative posix paths, so buildImpactSlice never resolved and
the impact card (F-d6b93648) never rendered in real usage. Measured on
cladding-self before the fix: 0/361 module paths hit; after relativizing:
358/361 (99.2% — the 3 misses are trailing-slash directory keys unreachable
via the hook). Outside-repo absolute paths degrade to not_found, relative
inputs are untouched (idempotent). Card now also displays the relative path.

Adds the end-to-end wiring test (runHookEvent with an absolute file_path
against a real on-disk spec) that was missing when the bug shipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…caps breaks_if_changed

Two seams in one module, landed together deliberately — simulation showed
the fan-out alone doubles budget-breach paths (17→33/144 on cladding-self,
worst 2.9x the cap), so the clip must ship with it.

Fan-out: a module-path query now passes its original form to the iterative
impact slice, seeding ALL owners (reverse-slice already supported this) —
before, only the alphabetically-first owner's dependents/tests were
reported (src/cli/clad.ts: impacted 0 vs 83). Co-owners are seeds, so they
surface in co_owners, not impacted; feature-id queries are byte-identical.

Clip: breaks_if_changed now participates in the token budget, LAST in the
clipping order (needs → code → breaks; the clip-before-code variant
simulated strictly worse). Deeper dependents drop from the far end first,
then tests outside the depth-1 floor; the direct set is never dropped
(must-edit precedent). Fit checks measure WITH the pending
'breaks: omitted …' marker, closing the +3..10-token overshoot the old
loops carried. In-budget payloads return byte-identical (pure no-op —
existing 6 tests unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…through both sides

Two validity fixes to clad measure, both simulation-verified before coding:

Attribution: medianShrinkFactor was bounded by the 3000-token default budget
— on cladding-self the '~4x smaller' headline was mostly the CAP's
arithmetic, not graph value (uncapped, the structural slice is ~1.16x of
naive: code + structured metadata). The report now splits
fitsCount/truncatedCount with medianShrinkFit/medianShrinkTruncated and a
medianStructuralRatio, and the CLI headline attributes the reduction to the
budget ('budget enforces 3.9x on 163 capped') instead of selling it as
shrink. What the working set actually sells: a guaranteed token budget +
wired needs/breaks/verify context.

One universe: the injected ModuleReader now reaches buildWorkingSet →
codeExcerpt (same safety gates), so the slice and the naive baseline read
the SAME universe — before, a virtual reader fed only the baseline,
silently inflating the shrink factor ('Pure given (spec, reader)' is now
true). The old test asserted the inflated number; rewritten to assert the
honest split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atcher + Host hardening

Body-before-writeHead on every non-SSE route: a mid-write or unparseable
spec throws inside liveGraph(), and committing the 200+application/json
headers first turned that into an HTTP 200 whose body was a prose YAML
error — precisely the state the fs.watch auto-reload refetches into.
Parse failures now answer 503 with a JSON {error} payload (the viewer can
show a retry state); the headersSent guard remains for mid-stream (SSE)
failures. Also covers schema-invalid specs (simulation).

EADDRINUSE: server.on('error') now rejects the boot promise, so
runGraphServeCommand prints one pulse line and exits 1 — before, the
'error' event was unhandled (raw 20-line stack) and the promise never
settled. The listener stays attached so later runtime errors can't crash
the process either. FSWatcher 'error' events degrade to manual refresh
instead of crashing. A foreign Host header is refused (DNS-rebinding
guard; the bind was already 127.0.0.1).

Tests: the old 'watched-file change' test called broadcast() by hand —
renamed to what it tests, and a REAL fs.watch→debounce→SSE chain test
added (writes under spec/, capability-probe skip on platforms without
recursive watch), plus 503, busy-port, and Host-guard cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…; health cache + all-twin badges; live viewer fixes

Node identity: the same file materialises as up to three nodes
(module:/test:/doc: — 95 paths on cladding-self), and every focus surface
picked only the FIRST twin, silently dropping the others' edges.
resolveNodeIds returns all twins, subgraph accepts a seed set, and the CLI
--focus + clad_get_graph query union them (resolveNodeId keeps the old
single-id contract). graph-health now badges ALL twins of a finding's path
(first-twin-only left siblings looking healthy) and the viewer's drift
pill counts distinct paths so twins can't double the headline number.

clad_get_graph: the no-query form returned the whole graph pretty-printed
— measured 285KB (~70k tokens) in one MCP result, contradicting the
working-set budget discipline. It now answers a graphStats summary
(counts by kind + top hubs + a hint at the CLI export, 2.1KB); a focused
query still returns the real subgraph. The vacuous clad_get_working_set
test (asserted a hand-maintained constant against itself; the handler was
never invoked) is replaced with a real MCP round-trip: on-disk module code
in must_edit.code, dependent in breaks, budget echo, isError miss.

nodeHealth wraps its detector loop in primeSpecCache (the drift.ts
run-scope pattern): one shard-tree parse instead of ~10 per /health.json
request — measured 611ms → 21ms for the loop, results byte-identical
(incl. mtime-sensitive STALE_TESTS on a drifted fixture).

Viewer: SSE change detection compares the server's exact graph.json text
(baseline = first fetch; never re-serialized client-side) — the old
nodes.length proxy missed edge-only and same-count node changes and
rendered stale. Wires the dead mobile burger button. CLI: a typo'd
--format/--depth now fails loudly instead of silently rendering mermaid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ort(); TS fixtures added

The JS/TS branch missed two statically-extractable forms cladding's own
source uses: 'export … from' re-exports (barrel files are dependencies)
and literal dynamic import('…'). Both now produce edges. A NON-literal
import(expr) flags the file in dynamicImportFiles (kept apart from the
shared DYNAMIC_IMPORT regex — 'import (' would false-flag Python's
parenthesized from-import). The whole JS/TS extraction branch shipped with
zero fixtures (all Python) — a TS fixture set now pins import…from,
side-effect import, require(), re-exports, dynamic import, and the
single-segment ambiguity rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ee.js license notice

Mermaid ids are deduped (safeId collapsed src/a.ts vs src/a_ts into one
node silently); labels flatten quotes/newlines that broke the quoted-label
syntax. DOT escapes backslashes before quotes. Obsidian wikilink aliases
strip the |[[]] metacharacters that corrupt links. clad_get_events
tolerates a corrupt/partial JSONL line (mid-write tail read) as an
{unparseable} entry instead of crashing the tool call. The viewer bundle
keeps three.js's MIT notice (legalComments 'eof' — 'none' stripped it from
a distributed artifact).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…attribution note

design.md gains a dated post-ship addendum (§8): the 'no bespoke web UI'
decision was reversed (WebGL viewer + live serve shipped, and why the
premise no longer applied), clad_get_dependents shipped as clad_get_impact,
scope grew (working-set/iterative/infer-deps/measure), and _doc-links.yaml
is a grep/human index, not the export source. The 0.7.0 CHANGELOG described
the archived 2D prototype (force sliders, Live/Calm, 'no third-party
library') — corrected in place with a note, plus an [Unreleased] section
for this branch. Glossary's graph row now names html/serve. README no
longer claims serve 'opens' the browser. case-efficiency-measurement.md
carries the cap-attribution correction (the '4.1×' headline is
budget-enforced, not structural). Deprecation notices say 0.8 (0.7.0
shipped every alias while claiming removal in 0.7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… artifacts

Updated ACs/test_refs where behavior contracts changed (clad_get_graph
no-query summary, working-set fan-out + breaks clipping, measure honest
attribution + one-reader universe, hook impact-card absolute-path wiring
now pinned end-to-end), each with a Correction note recording why.
src/spec/feature-id.ts claimed by the doc-graph feature. clad sync
regenerated spec/_doc-links.yaml — the restored legacy F-NNN doc edges
(multi-provider-roadmap, ssot-model) plus design.md's addendum references
now materialize. npm run build refreshed dist + plugin mirrors + the
viewer bundle (SSE text-compare + pill dedupe + burger + three.js MIT
notice at EOF). Persona alias deprecation wording aligned to 0.8.

Gate: clad check --tier=pre-push --strict GREEN (exit 0), full suite
1681/1681.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qwerfunch

Copy link
Copy Markdown
Owner Author

Superseded by #225 — identical commits, branch renamed fix/graph-seams → feature/graph-seams to match the feature/* → develop convention.

@qwerfunch qwerfunch closed this Jul 2, 2026
@qwerfunch qwerfunch deleted the fix/graph-seams branch July 2, 2026 06:52
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