derive-codebase-context
Derive codebase context
Write agent context for a repository from the repo itself: a CLAUDE.md / AGENTS.md that matches reality, the boundaries agents must not cross, and a map of where things live.
When it fires
Symptoms: write or fix our CLAUDE.md, the agent guidance is stale, the context files have multiplied (CLAUDE.md + AGENTS.md + Cursor rules), agents keep rediscovering the same layout, someone proposes a code knowledge graph / vector index / semantic search over the codebase.
The agent loads this one itself when what you are doing matches its description.
Install it
npx skills add crissmoldovan/agent-skills --skill derive-codebase-contextAny repository an agent works in. The generators need Node 22+ (builtins only) and git. Layer 2 assumes a JavaScript/TypeScript import graph through dependency-cruiser; other ecosystems need their own edge linter, and the rest of the procedure is unchanged. The measurements behind this came from a TypeScript monorepo of roughly 2,500 files. No database server, no daemon, and no network at any layer.
What to say to make it fire
These are the skill’s own published asks, word for word. You do not type a command — you say one of these, and the agent recognises it.
Set up derived codebase context in this repo. Detect what already exists first and tell me which of the three layers are present before you write anything.
We have CLAUDE.md, AGENTS.md and Cursor rules that disagree. Do layer 1: show me the drift between them as a diff first, then generate all three from one core and gate it in CI.
Turn the four architecture rules in our README into dependency-cruiser forbidden edges. Measure the current violation count with the tool before writing each rule, and mutation-test every one of them before you tell me it works.
What it ships with
The skill, in full
Derive codebase context
An agent working in a repository of a few thousand files cannot hold its shape. The reflex is to build a code knowledge graph. The measurements say do something cheaper and more durable: derive three artifacts from the tree itself, commit them, and gate each one in CI so it cannot rot.
This skill is the order to build them in, how to tell whether a repository already has them, and the one discipline that decides whether any of it works.
What the evidence says
Read this before proposing a graph, because the graph is the thing people keep proposing.
- A tree-sitter code graph is not a win across the board. Measured over 31 repositories it gives roughly 10x fewer tokens and 2.1x fewer tool calls, at 83% answer quality against 92% for an agent that simply reads files (arXiv 2603.27277). It wins on hub detection and caller ranking and loses elsewhere. So route structural questions to derived artifacts, and never take agents off grep.
- Anthropic removed a local vector index from Claude Code in favour of agentic search, over staleness, privacy and reliability. Anything built here must be derived and regenerated, never hand-maintained.
- Name-keyed graphs collapse on real repositories. In the codebase this came
from, 184 exported names were defined in more than one file, and the worst
offenders were its own conventions:
POST271 times,GET209,KEY143. A graph keyed on bare symbol names merges those into one node, and every blast-radius answer becomes "the whole app". Only compiler-resolved indexing survives that, and it is layer 4, which you defer.
When to Use
- A repository is large enough that agents guess at its structure, and you are deciding what to build about it.
- Someone proposes a code knowledge graph, a vector index, or a semantic search layer over the codebase.
- The context files have multiplied:
CLAUDE.md,AGENTS.md,GEMINI.md, Copilot instructions, Cursor rules,.clinerules, Windsurf rules, all hand-maintained and no longer saying the same thing. - Architecture rules live in prose that nothing enforces.
- A repository already has some of this and you need to know whether to finish it, upgrade it, or leave it alone.
Do not use it to answer a question about the code. That is what grep, the language server, and reading files are for, and they are better at it. This builds the artifacts; it does not replace how agents read.
Required tooling
| Need | Tool | Where it runs | Notes |
|---|---|---|---|
| Context and atlas generation | Node 22+, builtins only | local and CI | No dependencies at all. |
| Boundary enforcement | dependency-cruiser ^18 |
local and CI | One dev dependency at the root. |
| Boundary graph image | graphviz (dot) |
local only | Optional. CI never needs it. |
| Symbol navigation (companion) | uv / uvx plus Serena |
local only | Spawns a language server; the server itself indexes nothing you commit. Its memory files usually ARE tracked — see below. |
| Symbol index (layer 4) | scip-typescript or your language's SCIP indexer |
local, and CI only if you keep it | Emits a protobuf index. Gitignored, rebuilt on demand. |
| Index storage (layer 4) | SQLite or DuckDB as a library | local | An embedded file, not a server. |
No database server at any layer. No daemon, no container, no hosted index, no network call. Everything travels through git because every artifact is a pure function of the tree at HEAD, so a stale copy is fixed by regenerating it, not by pulling. Do not register the repository with a hosted indexing service unless the owner has decided the source may leave the machine.
Step 0: find out what is already there
Do this first, every time. Blindly scaffolding over a working setup is the failure this step exists to prevent, and a half-built setup is more common than either extreme.
Probe for six signals:
| Signal | Look for | What it proves |
|---|---|---|
| Shared core | A directory holding the context source that several harness files are built from. | Layer 1 has a single source. |
| Generator | A script that writes those harness files, with a --check mode. |
Layer 1 is derived, not hand-copied. |
| Gate | A CI step running that --check. |
Layer 1 is enforced. |
| Boundary config | A dependency-cruiser config whose forbidden rules name this repository's architecture. |
Layer 2 has started. |
| Atlas | A committed directory of generated registry markdown carrying a provenance line. | Layer 3 exists. |
| Formatter ignore | The generated paths listed in the formatter's ignore file. | Layers 1 and 3 survive a format run. |
Two ways to be fooled:
- A file that feeds several harnesses is not generated. Open it and look for a "generated, do not edit" header and a provenance stamp. No header means hand-maintained, however many harnesses read it.
- A dependency-cruiser config carrying only the tool's own starter rules
(
no-circular,no-orphans,not-to-dev-dep) encodes none of this repository's architecture. Layer 2 has not started.
Then branch:
- Nothing found: onboard. Build layers 1, 2, 3 in that order. Each is useful alone. Stop wherever it stops paying.
- Some found: finish the layer that exists before adding the next one. The usual half-state is a generator with no CI gate, which is the least useful shape there is: it drifts exactly as fast as hand-maintenance, and looks solved. Arm it first.
- All found: verify and report. Change nothing. Run each
--checkand the boundary check, report drift and its cause, and stop. Do not regenerate and commit unless the owner asks.
Layer 1: one source for the agent context files
The failure. Every harness wants its own file, so the repository accumulates
CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md,
.cursor/rules/*, .clinerules, .windsurfrules. Hand-maintained, they drift,
then contradict each other, and an agent obeys whichever one its harness happens
to load. Nobody can tell which rule is current.
- Measure the drift before writing anything. List every context file, diff them, and write down which rules exist in one and not another. That list is the review artifact, and it is the only chance to settle contradictions with a human.
- Author a shared core, split by topic so each harness file can compose a subset rather than take everything.
- Write the generator with language builtins only. It reads the core and writes every harness file with a "generated, do not edit, regenerate with X" header.
- Add
--check: regenerate to memory, compare against what is on disk, exit non-zero on drift. Naming the file that drifted is the whole product of a failed run. - Gate it in CI, and add the generated paths to the formatter's ignore file in the same commit. See the pitfalls: a formatter will rewrite generated markdown and break the gate before you have finished building it.
Layer 2: architectural rules as enforced import edges
The failure. The repository already writes rules in prose: this package may not import that one, background jobs may not reach the web app, nothing calls the provider SDK directly. Prose is followed until somebody is in a hurry.
- Take rules that are already written down. Do not invent architecture policy inside a lint config. A rule nobody agreed to gets disabled.
- Measure the current violations with the tool that will enforce them, not by hand. See the pitfalls: a hand count found 4 where the rule engine found 6, and a rule that starts red gets switched off rather than fixed.
- Write each rule as a forbidden edge, at error severity where the count is zero and warn where it is not, with the target count in the rule's own comment so the next person knows what it is ratcheting towards.
- Mutation test it. Every rule. No exceptions. See the discipline below.
- Run it in CI alongside the other checks.
Two traps that made real rules pass while checking nothing are in the gate hardening reference. Read it before writing a rule that mentions an npm package.
Layer 3: the generated operational atlas
The failure. Agents re-derive the same cross-cutting registries by grep, every session, and get them subtly wrong. Background jobs to their keys to where they are registered. Plugin or MCP tools to their executors. HTTP routes to methods to handlers. Whatever your repository has several dozen of and no list of.
Derive them into committed markdown:
- Parse the tree. Never ask a model to summarise it. The value here is that the artifact is exactly as true as the code.
- One row per real thing, carrying its file path, so the agent can open the source instead of trusting the summary.
- Stamp provenance: which generator wrote it, the commit the tree was at, and the command to regenerate it.
- Add
--check, as in layer 1. - Rewrite a file only when its derived facts change, and compare bodies with the provenance line blanked. Otherwise every commit rewrites every atlas file and the diff becomes noise nobody reads.
The provenance scheme is where this goes wrong in CI specifically. Read the gate hardening reference before designing it.
Layer 4: a compiler-accurate symbol index. Defer it.
This is the only thing that answers blast radius correctly in a repository with duplicate exported names, and it is roughly a week of work. Run layers 1 to 3 for a month first, because the cheap layers often remove the need.
When you do build it: a SCIP indexer for your language, loaded into an embedded
SQLite or DuckDB file. Gitignore the index. It is tens of megabytes of protobuf
that changes every commit, and committing it is putting node_modules in git.
A language server exposed over MCP (Serena, run through uvx) is a useful
companion at any point and is not a layer: it answers "who calls this" from the
tree as it is, with no index to build and nothing to rebuild after a pull.
Two things to settle before turning it on.
Its memory system is a second store, and it drifts like any other. Serena reuses project memories across sessions and its memory directory is normally tracked, so they reach the whole team through git. That puts them squarely under layer 1's rule: memories must point at the generated context files, never restate the facts in them. A memory that copies a hard rule is the exact drift this skill exists to prevent, rebuilt inside the tool meant to help. Write them for what the generated files cannot carry — commands, local traps, the verification standard — and say so in the entry-point memory so the next reader does not add a copy. Run its onboarding once and review what it writes; do not accept a generated memory that duplicates your context files.
An empty reference result is not zero references. For a very-high-fan-out symbol the reference query returns an empty object, because the answer exceeds its size cap rather than because nothing calls it. Read it as "too many to serialize" and reach for the atlas or a grep count instead. Measured on a symbol imported by roughly a thousand files.
The discipline: a gate you have not watched fail is not a gate
In the work this came from, four separate guards were born unarmed. They passed every run while verifying nothing: two boundary rules whose configuration had deleted the edges they matched, and two atlas guards that were structurally unreachable. Nothing about a green run distinguishes a guard that passed from a guard that checked nothing.
So for every gate, before you trust it:
- Back it up with
cp. Not with git.git checkout -- <file>silently does nothing for an untracked file, so it will not revert a mutation test on a file you just created, and you will lose the fix instead. - Introduce exactly the thing the gate forbids.
- Run the gate. Watch it fail, and read the message. It must name the thing you introduced. A failure with a message that names something else is a different gate firing.
- Restore from the backup.
- Run it again. Watch it pass.
A guard you cannot make fail is checking something other than what you think it is checking. Treat a new guard as broken until it has been watched to fail.
Pitfalls
- Measure with the tool that will enforce, not with a hand grep. A pathspec
like
dir/**/*.tsdoes not match files sitting directly indir/. The hand count said 4; the rule engine found 6. A baseline that is an undercount turns into a rule that starts red, and a rule that starts red gets disabled. - CI clones are shallow.
actions/checkoutgives depth 1, sogit log -1 -- <path>returns the tip commit for every path andgit rev-parse --shortabbreviates to 7 characters instead of 8. Any provenance scheme that compares a per-path SHA passes on a laptop and fails on every CI run. - Formatters rewrite generated files. A markdown formatter realigning tables broke the generated-file gate before the atlas it was meant to protect even existed. The ignore entry belongs in the same commit as the generator.
- A new test directory needs a workspace or project entry, or its first test file contributes zero tests while the summary stays green.
- Dependencies go stale after a big pull. A declared, lockfile-present package that is not actually installed will fail most of a suite. Install before believing any suite result.
Usage Examples
Set up derived codebase context in this repo. Detect what already exists first
and tell me which of the three layers are present before you write anything.
We have CLAUDE.md, AGENTS.md and Cursor rules that disagree. Do layer 1: show me
the drift between them as a diff first, then generate all three from one core
and gate it in CI.
Turn the four architecture rules in our README into dependency-cruiser forbidden
edges. Measure the current violation count with the tool before writing each
rule, and mutation-test every one of them before you tell me it works.
Verification
- Step 0 ran, and the branch taken (onboard, finish, verify-only) was stated.
- Nothing was scaffolded over an existing setup without being reported first.
- Every generated file carries a "do not edit" header and a regenerate command.
- Every generator has a
--checkmode that names the file that drifted. - Every
--checkruns in CI. - Generated paths are in the formatter's ignore file.
- Each boundary rule was measured with the enforcing tool before it was written.
- Every gate was watched to fail on a mutation and then to pass on restore.
- Mutation backups were
cpcopies, and the restore was verified. - Provenance survives a depth-1 clone.
- No index, cache, or generated binary artifact was committed.
- Layer 4 was deferred, or the owner explicitly chose to pay for it.
Deeper reading
- Onboarding runbook: the per-layer procedure in
full, including generator design,
--checksemantics, provenance stamping, and how to adapt each layer to a non-JavaScript stack. - Gate hardening: the mutation-test procedure, the four unarmed guards and the exact mechanism of each, and the CI traps that make a green check meaningless.