Repository navigation
feat: add unlinked mentions as graph edges - #42
Open
aponcedeleonch wants to merge 6 commits into
Open
aponcedeleonch wants to merge 6 commits into
aponcedeleonch wants to merge 6 commits into
Conversation
References written as text rather than link syntax now become graph edges (ADR 0026): path tokens in prose or code spans, bare file names that match exactly one document, and config-declared name-prefixed invocations resolved through front-matter name/aliases and filtered by target globs (.matlatl.yml mentions.invocations). Mentions are always on and join the navigational set, so they count in reachability, orphans, PageRank and every other metric. Code blocks, raw HTML and text inside link syntax are never scanned, information scent ignores mentions, and an unresolved or ambiguous mention is never a finding, so check can only get softer. graph.json bumps to schema v8: one "mention" edge per resolved occurrence carrying kind, line and text, beside the "reference" edges, plus a required summary.mentions count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unlinked mentions missed or hid most cross-references in a repo with subprojects: - A path written relative to the enclosing project (`.claude/rules/x.md` in a subproject's docs) never resolved: only the origin's directory and the repo root were tried. Paths now try each ancestor directory of the origin, nearest first. - A file name or invocation shared by several documents was always dropped, even when one is clearly in scope. The candidate whose dot-directory scope most closely encloses the origin now wins, so a subproject's `.claude/skills/x` shadows the repository's. - A mention naming a deleted or renamed file vanished, which is the stale reference a consumer most needs. A markdown-named path or file name that resolves to nothing is now a Broken mention, and a shared name with no nearest candidate is Ambiguous with every candidate. Both are emitted as graph.json mention edges with that health, and neither becomes a finding or a graph edge, so check stays softer-only. - HTML comments were never scanned, though templates and maintainer notes point at files from them. Comments are now scanned; other raw HTML still is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ambiguous mention of a common name fanned out to one graph.json edge per candidate: a bare SKILL.md produced about a hundred edges. Emit one edge per occurrence instead, with `to` set to the name as written and a `candidates` array listing what it may name. Glob patterns in prose (`*-test-quality-overlay.md`, `docs/*.md`) were read as stale file names. A token touching a glob character is now not a mention. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run against a real repo, the nearest-scope tie-break picked `.claude/rules/frontdoor.md` for a prose `frontdoor.md` that meant the design doc of that name, and a skill's reference `architecture.md` over every docs/architecture.md: a plain document's scope rarely encloses the mentioning doc, so a dot-directory file always won. Apply the tie-break only when every candidate lives under a dot-directory; any mix stays ambiguous. Also drop an ambiguous mention whose candidates include its own origin (most likely a self-reference, like `notes.md` inside a notes.md), and keep unresolved mentions out of the references count, which llms.txt and the report present as resolved references. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestIntegration_GraphJSONMentions still asserted the edge set from the first mentions commit. The later commits keep stale mentions as Broken edges, emit one Ambiguous edge per shared name, and extract mentions from HTML comments, so graph.json now carries four more mention edges (docs/nope.md, ghost.md, testing.md, and the comment's docs/sibling.md). The test only runs under -tags=integration, which is why it went unnoticed until CI. Update the expectation to the ADR 0026 behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch
force-pushed
the
feat/unlinked-mentions
branch
from
October 16, 2026 06:44
492aec3 to
63a2982
Compare
freeze.json hashes the Go sources and static binaries of everything eval-supervisor and fake-opencode import. The mention work changes mdparser, reference, graphmodel, corpus, application, emit and fakeopencode, so the recorded hashes no longer match and the eval scaffold job fails with "freeze.json does not match current Nimbus tree". Regenerated with the go.mod toolchain (go1.26.4) and the committed runtime image IDs, so only the hashes of packages this branch touches change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
matlatl's graph only saw link syntax, so references written as text were invisible. A consumer repo (stacklok-enterprise-platform) enforces "docs must not reference agent context files" by post-processing
graph.jsonedges, and keeps a bespoke regex scanner for the roughly half of references that are unlinked. This PR makes those mentions native, typed edges so that scanner can go away. Design and rationale are in ADR 0026.Three kinds of mention:
path: a path-shaped token in prose, a code span or an HTML comment (Per `.claude/rules/metrics.md`,.claude/skills/foo/). Resolved doc-relative first, then relative to each parent directory of the doc, nearest first, ending at the repo root, all through the same root-containment guard as links. That covers paths written relative to an enclosing subproject (.claude/rules/x.mdinenterprise/app/docs/). A directory resolves to its README/index, else itsSKILL.md.filename: a bare name likemetrics.md, matched by basename.invocation: a configured prefix plus a name (/panel-review), resolved through the existing front-mattername:/aliases:index and filtered by the rule'stargetsglobs. The prefix and globs come from.matlatl.yml, so matlatl still ships no tool-specific knowledge:When a file name or invocation matches several docs and every candidate lives under a dot-directory, the one in the nearest enclosing project wins, so a subproject's
.claude/skills/xshadows the repo's for docs inside that subproject. If a plain doc is among the candidates (docs/architecture.mdbeside a skill'sreference/architecture.md), nothing is picked.Behavior
*-overlay.md). A link stays a link with no double counting. HTML comments are scanned, since templates and maintainer notes point at files from them.valid: one doc.broken: a.mdpath or file name that names nothing, i.e. a stale reference.ambiguous: a shared name with no single nearest candidate. Any other unresolved token is dropped, and so is a mention of the doc itself.summary.references.checkcan only get softer.graph.jsonis schema v8: one"mention"edge per occurrence withkind,line,textand itshealth, beside the existing"reference"edges (now built from a link-only projection), plus a requiredsummary.mentions. A broken edge'stois the missing doc. An ambiguous edge'stois the name as written, with acandidatesarray, so a common name likeSKILL.mdadds one edge, not one per file.findings.jsonandtrails.jsonare unchanged.Things reviewers should know
testdata/corpus/docs/island/*,testdata/dirlinks/adr/*). Those names are now mentions, so I reworded the prose to use titles instead of loosening assertions. The only golden change is the graph.json version, note andsummary.mentions.llms.txtchanged: our docs contain 24 mentions, all true references.task dogfoodstays green.matlatl servedoesn't read.matlatl.yml, so its analysis carries path and filename mentions but not invocations.Tests
mdparser: code span and plain-text paths, trailing-slash directories, filenames split by goldmark at_, fragments, invocations, URL/path-prefixed non-matches, glob patterns, fenced and indented code, link syntax, reference definitions, non-comment raw HTML, block and inline HTML comments, headings.reference: doc-relative, parent-directory and repo-root paths, root-absolute and content roots, directory to index and toSKILL.md, stale path and file name as broken, shared basename among plain docs vs among tool files vs mixed, nearest-scope invocation (deepest wins, repo root encloses all, no enclosing scope is ambiguous), a shared name that may be the origin, invocation vianame:filtered by glob, unmatched invocation, unconfigured prefix, self-mention, dedup,MatchGlob/ValidateGlob.graphmodel: mentions in the projection but notLinkProjectionOut, not scored for scent (mutation-checked), lift orphans.config: valid rules, unknown-key notices, every hard-error shape.application: mentions never become findings or broken-link placeholders, and unresolved ones stay out of the references count.graph-mentions.jsonovertestdata/mentions/(valid, broken, ambiguous with candidates, HTML comment), schema validation including a negative case for a mention edge withoutline, and CLI integration tests forgraph --format jsonand forcheck --strictstaying green with dangling mentions.Checked against the consumer repo
Ran the branch on stacklok-enterprise-platform and compared with its regex scanner over
docs/(150 mentions of rules, skills and agents). This drove the resolution changes above.CLAUDE.md/SKILL.mdmentions the regex misses. With that repo's.matlatlignoreloosened (its atrium.agents/skills and one excluded doc), 149 of 150. The one left is a regex false positive on a link split across two lines.checkon that repo is unchanged: 0 orphans, 0 unreachable, 0 ambiguous.Pre-existing failures, same on
main:eval/internal/nimbus(freeze.json mismatch),eval/internal/agentoutcome(needs an OCI runtime helper), andTestServeListenerOwnsListenerAndOnlyServesMCP, which is flaky onmain(7/25 failures on HEAD vs 6/25 on this branch under-race).Consumer follow-up
Enable
mentions.invocationsin.matlatl.yml(targets for.claude/skillsand.agents/skills), stop ignoring atrium's.agents/, bump the matlatl pin, and have docs-cite-context readtype: "mention"edges byhealth, readingcandidatesfor ambiguous ones. Then deletescripts/context-lint/mention.goand the docs-mention-context step. Mentions only resolve to in-corpus docs, so.matlatlignorethere must not exclude.claude/.🤖 Generated with Claude Code