2026-06-23 — soul-doctor dangling-reference check
Drafted a new dangling & unlinked references deep-check into plugins/claude-code/commands/soul-doctor.md.
Why (the gotcha that motivated it)
Tracing how an agent reaches a referenced doc, hit a real failure: ADR 0006 in the demarkus-library project soul says "supersedes ADR 0005", but ADR 0005 isn't in the store — adr/ holds only 0006. Neither retrieval path reaches it:
- Graph traversal can't — the "supersedes ADR 0005" reference is prose, not a
[text](url)link.links.Extract(client/links/links.go) only walks*ast.Linknodes, so the ADR crawled withLinkCount: 0. Latent relationships written in prose / inline code are invisible tomark_graph/mark_backlinks. - Catalog lookup proves absence —
mark_lookup query="*"scoped to the project returns the complete subtree census (6 docs, only ADR 0006). The catalog is authoritative for absence:*is a full census, so "no match" means "not here," not "didn't crawl it." That certainty is the catalog's real value, distinct from finding things.
The existing soul-doctor broken-links check missed it because a broken link requires a link; this reference was never an edge.
The check (design decisions)
- Deep tier (per-doc fetch) — it's a body-text scan; core checks stay fetch-free. Inherits the cap/disclose discipline.
- Zero extra calls — resolves each prose mention against the inventory (
mark_list, existence) + edges (mark_graph, already-linked?) already gathered. - Two verdicts, different fixes: unlinked (target exists, no edge → convert prose to a link) vs dangling (resolves to nothing → restore or remove; confirm via
mark_lookupsince catalog is authoritative for absence). - Tight pattern set to bound false positives:
ADR[ -]?#?\d{3,4}+ bare/inlinemark://URLs not inside a link. Exclude self-references and code-fence mentions (same fence-skipfirstH1/the catalog use). Left out*.gomentions (point at repo, not soul) and title fuzzy-matching (too noisy). - Distinct from broken-links: that follows an edge to a missing target; this finds references that were never edges.
Broader frame (not yet acted on)
Two improvement axes identified. (1) prose-reference gap → this doctor check + optional publish-time link suggest-mode. (2) semantic-recall gap (catalog is tags/title only) → a client/broker-side embedding index, not a SEARCH verb — ADR 0006 D7 closed full-text search by design ("never will be"). Respecting that, semantic recall belongs in the gateway where mark_index already reads bodies, mirroring the ephemeral/bucket-store pattern of the graph store.
Not yet done: dry-run the new check against the demarkus-library project to confirm it flags the 0005 dangling ref + 0006 unlinked refs.
Update — dry-ran the check against demarkus-library
Ran the new deep-check over all 6 docs. Confirmed the predicted findings: ADR 0005 dangling (cited from 5/6 docs), ADR 0003 dangling (from the ADR itself); architecture.md → ADR 0006 unlinked (cited ~4× in prose, no in-body link). index.md self-annotates 0005's absence ("no md file exists") → report as known, not actionable.
The dry-run exposed two bugs in the v1 draft (now fixed in soul-doctor.md):
- already-linked? must parse the citing doc's OWN markdown links, not the crawl edge store. The crawl seeds from the hub, so orphan docs are never visited and their outbound links never enter the edge store.
journal/2026-06-19.mddoes link[ADR 0006](…)but is an orphan → the edge-store rule would falsely flag it unlinked. Fix: the deep tier already holds every body; resolve from parsed body links. - bare/inline
mark://pattern too noisy → deferred to v2. False-positives on cross-world refs (can't existence-check in a single-world audit) and inline-code placeholders (`mark://host/...`). v1 is ADR-pattern only.
Lesson: a graph-derived "is it linked?" signal silently degrades for orphans (unreachable from the crawl seed) — same failure shape as the world-map orphan bug in this project's own 2026-06-20 journal ("not proven orphan ≠ linked"). Prefer the in-hand body over a derived graph signal when you already paid for the fetch.