tags: [codemap, codegraph, symbol-index, pre-commit, tooling, maintenance] related:
- 032_pre-index-your-codebase.md
- 033_structure-vs-intent.md status: current —
036 — Kill Your CODEMAP: When Structural Tools Make Manual Symbol Indexes Obsolete
There is a category of tooling that feels productive to maintain but is actually a liability. You know the type: a file that must be kept in sync with the code, enforced by a pre-commit hook, that blocks commits when it drifts. Every team builds at least one of these before they discover a better way.
In DarJS, that file was CODEMAP.md.
What CODEMAP Was
The idea was reasonable. DarJS is a framework spread across eight packages. New contributors — and the AI working on it — needed to know where things lived. So we built a markdown table mapping every public function to its file, its line number, and a one-line note about what it did.
## `packages/cli/commands/contracts.js`
| Symbol | Line | Notes |
|--------|------|-------|
| `runContractsCmd(subcommand, args)` | 36 | dispatches to runBuild or runServe |
| `runBuild(args)` | 49 | calls buildContractsGraph, prints stats |
| `runServe(args)` | 71 | HTTP server on port 5050; auto-builds if graph missing |
A pre-commit hook ran dar codemap --check before every commit. If any line number was stale or a new export was missing, the commit was blocked.
This worked. Until it didn’t.
The Problem With Manual Indexes
The pre-commit hook blocked commits twice in the same session. Both times for the same reason: we added a function, the lines shifted, and the CODEMAP was out of date by the time the commit ran.
The fix was mechanical — find the new line numbers, update the table, re-stage, re-commit. No thinking required. A computer could do it. In fact, a computer was doing it, just in the wrong direction: detecting the problem instead of preventing it.
This is the signature of a maintenance burden that has outgrown its justification. When you spend more time keeping a file accurate than you save by having it, the file is costing you.
But there’s a deeper problem. CODEMAP was a workaround. The question it answered — where is this symbol, what does it do, who calls it? — is a structural question about the codebase. And structural questions about a codebase should be answered by structural tools, not by a human typing line numbers into a markdown table.
What Replaced It
CodeGraph is an AST parser that builds a SQLite graph of every symbol in the project. Functions, methods, classes, imports, routes — all indexed with file paths, line numbers, docstrings, and call edges. The index updates automatically when files change.
The same questions CODEMAP answered:
# Where is runServe?
codegraph_search('runServe')
→ packages/cli/commands/contracts.js:104 function runServe(args)
# What does it call?
codegraph_callees(id)
→ buildContractsGraph, startCodeview, buildExplorerHtml, ...
# What calls it?
codegraph_callers(id)
→ runContractsCmd
Sub-millisecond. Always accurate. No maintenance.
The line numbers in CodeGraph are never stale because they come from the AST, not from a human. When you add a function and shift everything down by thirty lines, CodeGraph re-indexes on save. CODEMAP would have blocked your next commit.
The Notes Column
The one thing CODEMAP had that CodeGraph doesn’t is the Notes column: informal annotations like “auto-builds if graph missing or older than 5 minutes” or “defaults to port 5050”.
These are not structural facts. They are semantic ones. And they already have a home: the @contract blocks on the functions themselves.
/**
* @contract
* @does Starts the contracts HTML explorer; auto-rebuilds graph if missing or >5min old.
* @reuse-when You need to browse contracts in a browser with live rebuild support.
*/
function runServe(args) { … }
When the contract is on the function, it travels with the code. It shows up in codegraph_node. It’s searchable via dar find. It doesn’t require a separate file to stay in sync because it is the file.
The Notes column was a sign that semantic annotations belonged in the code but were being stored outside it.
The Decision
We removed the CODEMAP check from the pre-commit hook. One line commented out:
# 4. CODEMAP — retired; CodeGraph provides symbol lookup with no maintenance cost
# node "$ROOT/packages/cli/bin/dar.js" codemap --check || exit 1
The file stays in the repo as historical reference. The enforcement is gone.
The pre-commit hook now runs eight checks instead of nine. The one we removed was the only one that required human maintenance to stay passing. Every other check — syntax, contracts coverage, test suite, .d.ts coverage — is fully automated and self-healing.
The Pattern
CODEMAP is not unique to DarJS. Every non-trivial codebase has at least one manually maintained index: a ROUTES.md listing every API endpoint, a COMPONENTS.md mapping component names to files, a GLOSSARY.md explaining domain terms. Some of these are enforced by hooks. Most drift within weeks.
The question to ask about any of them is: what structural tool would make this redundant?
For symbol location and call graphs, the answer is an AST indexer. For API routes, it is a route-aware tool that reads the framework’s router. For component trees, it is a component graph tool. For domain terms, it is the @contract @does fields on the code itself.
Manual indexes are gap-fillers. They exist because the structural tool doesn’t exist yet. Once the structural tool exists, the manual index becomes a maintenance burden. At that point, the right move is to retire it — not to keep maintaining both.
The test is simple: if a computer could keep this file accurate automatically, and you are keeping it accurate manually, you are doing the computer’s job.
Build the computer. Kill the file.