What problem does this solve?
Markdown docs and shell scripts reference other repo files constantly: a coding-standards doc links the modules it governs, a script sources a helper library or invokes a sibling tool. None of that becomes graph edges today. CBM_LANG_MARKDOWN exists in language detection (src/discover/language.c) but no pipeline pass consumes it, and shell files get no reference extraction either.
Concrete, reproducible case on a public repo: cdeust/zetetic-team-subagents (Shell Markdown heavy). The two most-referenced files there are rules/coding-standards.md (173 references from other files) and tools/memory-tool.sh (171), both counted by exact grep. Both have zero inbound edges in the graph, so a "which file has the highest fan-in" question gets answered with a Python file instead, an undershoot of roughly 17x, delivered with citations. I hit this in an A/B retrieval benchmark I ran in August with independently verified scoring: the failure mode is "grounded but wrong", and it is invisible from inside a session.
Proposed solution
A pre-dump pass modeled on pass_configlink.c (same shape: strategies with per-strategy confidence, operating on the graph buffer before dump), emitting REFERENCES_FILE edges between existing File nodes only. Unresolvable targets are dropped, the pass never invents nodes. Targets resolve against the referencing file's directory and the repo root; repeated references between the same file pair collapse into one edge carrying strategy, confidence and count.
To respect the 500-line PR guidance I split the work into two reviewable increments, both implemented and tested on my fork:
- Markdown (
cdeust/codebase-memory-mcp branch feat/doclinks-markdown): inline links, backtick paths, bare path mentions, plus the shared infrastructure (path normalization, resolution, dedupe) and pipeline/incremental registration. Pass is 495 lines, tests 313 mirroring test_configlink.c (real files in a tmpdir, File nodes in a gbuf, assert edges).
- Shell (branch
feat/doclinks-shell, stacked on 1): source path / . path lines and repo-relative script invocations, 218 lines including tests.
Validation on both: scripts/build.sh clean; focused serial runner (doclinks, configlink, pipeline, edge_structural, lang_contract suites) green under ASan/UBSan (340 and 342 tests respectively); cppcheck clean on the new files with the repo's flags; commits DCO signed-off. Good public test beds: cdeust/zetetic-team-subagents for the extreme case, and this repository itself (README/CONTRIBUTING link real files, scripts/ sources helpers).
One open question: is REFERENCES_FILE the right edge type name for you, or would you rather fold it into an existing kind? Happy to adjust before opening PR 1.
Alternatives considered
- Full markdown symbol extraction (heading nodes, doc sections): heavier, different question; file-level reference edges are what fan-in needs and stay cheap.
- Treating this in query-land (e.g. grep at question time): loses the persistent-graph advantage and doesn't fix fan-in answers.
- A tree-sitter markdown grammar pass: overkill for link extraction; the vendored grammar set doesn't need to grow for this.
Confirmations
What problem does this solve?
Markdown docs and shell scripts reference other repo files constantly: a coding-standards doc links the modules it governs, a script sources a helper library or invokes a sibling tool. None of that becomes graph edges today.
CBM_LANG_MARKDOWNexists in language detection (src/discover/language.c) but no pipeline pass consumes it, and shell files get no reference extraction either.Concrete, reproducible case on a public repo:
cdeust/zetetic-team-subagents(Shell Markdown heavy). The two most-referenced files there arerules/coding-standards.md(173 references from other files) andtools/memory-tool.sh(171), both counted by exact grep. Both have zero inbound edges in the graph, so a "which file has the highest fan-in" question gets answered with a Python file instead, an undershoot of roughly 17x, delivered with citations. I hit this in an A/B retrieval benchmark I ran in August with independently verified scoring: the failure mode is "grounded but wrong", and it is invisible from inside a session.Proposed solution
A pre-dump pass modeled on
pass_configlink.c(same shape: strategies with per-strategy confidence, operating on the graph buffer before dump), emittingREFERENCES_FILEedges between existing File nodes only. Unresolvable targets are dropped, the pass never invents nodes. Targets resolve against the referencing file's directory and the repo root; repeated references between the same file pair collapse into one edge carrying strategy, confidence and count.To respect the 500-line PR guidance I split the work into two reviewable increments, both implemented and tested on my fork:
cdeust/codebase-memory-mcpbranchfeat/doclinks-markdown): inline links, backtick paths, bare path mentions, plus the shared infrastructure (path normalization, resolution, dedupe) and pipeline/incremental registration. Pass is 495 lines, tests 313 mirroringtest_configlink.c(real files in a tmpdir, File nodes in a gbuf, assert edges).feat/doclinks-shell, stacked on 1):source path/. pathlines and repo-relative script invocations, 218 lines including tests.Validation on both:
scripts/build.shclean; focused serial runner (doclinks, configlink, pipeline, edge_structural, lang_contract suites) green under ASan/UBSan (340 and 342 tests respectively); cppcheck clean on the new files with the repo's flags; commits DCO signed-off. Good public test beds:cdeust/zetetic-team-subagentsfor the extreme case, and this repository itself (README/CONTRIBUTING link real files, scripts/ sources helpers).One open question: is
REFERENCES_FILEthe right edge type name for you, or would you rather fold it into an existing kind? Happy to adjust before opening PR 1.Alternatives considered
Confirmations