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.