Skip to main content
← Back to All Posts

Repo Map Drift Detection for AI Coding Agents

June 6, 2026•10 min read
Repo Map Drift Detection for AI Coding Agents

Repo maps are one of the best force multipliers for AI coding agents, right up until they go stale.

The failure mode is subtle. The map still reads well, the package names look familiar, and the agent sounds confident. But ownership moved, entrypoints changed, a service was split, and the context packet is now steering edits toward a codebase that no longer exists.

The fix is not to write better docs once. It is to treat repo maps like generated infrastructure: snapshot the code shape, score drift, and refresh the summary only when the evidence says it changed enough to matter.

Why this matters

Teams often add REPO_MAP.md, ARCHITECTURE.md, or agent instructions because they want smaller prompts and better edits. That works, but only if the summary keeps pace with the repository.

In practice, stale repo maps cause four expensive problems:

  • agents edit old extension points that no longer own the workflow
  • retrieval packs include renamed files and dead directories
  • reviewers get plausible PRs built on obsolete assumptions
  • humans stop trusting the map, so the whole context layer collapses

A drift detector gives you a middle path. You do not regenerate everything on every commit, and you do not wait for a human to notice drift after an agent writes code in the wrong lane.

Architecture or workflow overview

flowchart TD
    A[Git push or nightly CI] --> B[Symbol snapshot job]
    B --> C[Collect AST + path ownership + imports]
    C --> D[Compare against last approved repo map snapshot]
    D --> E{Drift score exceeds budget?}
    E -->|No| F[Keep current repo map]
    E -->|Yes| G[Build refresh packet]
    G --> H[Regenerate targeted sections]
    H --> I[Human review or auto-approve low risk changes]
    I --> J[Commit updated repo map artifacts]

I like thinking of this as a freshness gate, not a documentation bot. The goal is to surface meaningful structural change, then regenerate only the map sections that matter.

Implementation details

1) Snapshot the code shape, not just file counts

A cheap git diff --stat is not enough. Drift becomes visible when symbol ownership, imports, or top-level entrypoints change.

from dataclasses import dataclass
from pathlib import Path
import json
import subprocess

@dataclass
class FileShape:
    path: str
    exported_symbols: list[str]
    imports: list[str]
    owner_hint: str | None


def build_snapshot(repo_root: Path) -> dict:
    files = []
    for path in repo_root.rglob('*.ts'):
        if 'node_modules' in path.parts:
            continue
        shape = extract_typescript_shape(path)
        files.append(FileShape(**shape).__dict__)

    return {
        'commit': subprocess.check_output(
            ['git', 'rev-parse', 'HEAD'], cwd=repo_root, text=True
        ).strip(),
        'files': files,
    }

The exact parser can vary. Tree-sitter is great when you need language-agnostic snapshots. Native parsers are better when you want higher-confidence symbol ownership.

2) Score drift by importance, not raw churn

A repo map should care more about moved entrypoints and changed package boundaries than about one more helper function in a utility module.

type DriftSignal = {
  kind: "entrypoint-move" | "symbol-delete" | "owner-change" | "import-fanout" | "path-rename";
  weight: number;
  file: string;
};

export function scoreDrift(signals: DriftSignal[]) {
  return signals.reduce((sum, signal) => sum + signal.weight, 0);
}

const budget = 18;
const score = scoreDrift(signals);

if (score >= budget) {
  enqueueRepoMapRefresh({ score, signals });
}

A weighting model keeps the system from flapping on harmless churn.

Drift signalWhy it mattersSuggested weight
service entrypoint movedagent edits may start from the wrong root8
ownership annotation changedreviewers need a different team or package mental model6
public symbol deleted or renamedcontext packets become misleading fast5
import fanout spikehidden coupling usually means architecture text changed4
test-only helper churnusually low map impact1

3) Keep the refresh packet compact and reviewable

Do not ask the summarizer to reread the whole monorepo if only two domains drifted.

{
  "repo_map_section": "billing-api",
  "baseline_commit": "c1d2e3f",
  "head_commit": "f7a8b9c",
  "signals": [
    {"kind": "entrypoint-move", "file": "apps/billing-api/src/server.ts"},
    {"kind": "owner-change", "file": "packages/billing-core/src/invoice.ts"}
  ],
  "related_files": [
    "apps/billing-api/src/server.ts",
    "apps/billing-api/src/routes/invoices.ts",
    "packages/billing-core/src/invoice.ts"
  ]
}

This packet is what I would feed into a coding agent or summarizer. Small packets are easier to review, cheaper to generate, and less likely to include irrelevant stale text.

4) Publish a terminal-style drift report humans will actually read

repo-map-drift check
baseline: c1d2e3f  head: f7a8b9c
score: 21  budget: 18  status: refresh-required

signals:
  +8  entrypoint-move   apps/billing-api/src/server.ts
  +6  owner-change      packages/billing-core/src/invoice.ts
  +4  import-fanout     apps/billing-api/src/routes/invoices.ts
  +3  path-rename       apps/billing-api/src/http.ts -> src/server/http.ts

next step: regenerate sections [billing-api, billing-core]

This is one of those tiny usability wins that matters. If the artifact reads like a judgment call instead of a wall of machine output, people trust it more.

What went wrong, and the tradeoffs

Failure mode 1: the map is hand-written but never revalidated.

Failure mode 2: drift detection is too noisy.

Failure mode 3: the refresh job rewrites the whole map.

Pitfall: do not let the same agent both declare drift severity and auto-approve a large map rewrite with no structured evidence.

Best practice: preserve a small ledger of approved snapshots, drift scores, and affected sections.

ChoiceBenefitCostWhen I would use it
full repo re-summarysimple pipelineexpensive and noisyvery small repos
targeted section refreshnarrow diffs, cheaper promptsneeds section ownership modelmost active codebases
nightly drift scanpredictable costlag between change and refreshmedium velocity repos
per-push drift gatefreshest mapsmore CI pressurerepos with heavy agent usage

Practical checklist

  • [ ] define repo map sections that align to real package or service boundaries
  • [ ] generate structural snapshots with symbols, imports, and entrypoints
  • [ ] weight drift signals instead of triggering on raw churn
  • [ ] keep a drift budget per repo, not one universal threshold
  • [ ] generate compact refresh packets with only affected files
  • [ ] show a human-readable CI report alongside machine artifacts
  • [ ] keep an approval ledger for map updates and snapshot baselines
  • [ ] spot-check whether agent retrieval actually improved after refreshes

What I would not do

I would not put a giant evergreen architecture essay in every coding prompt and hope it stays right.

I would also not regenerate the entire map on every commit just because it feels thorough.

Conclusion

Repo maps are useful because they compress a codebase into something an agent and a reviewer can both reason about.

That only works if the compression stays fresh. Drift detection, weighted budgets, and targeted refresh packets are the difference between a repo map that compounds engineering speed and one that quietly teaches your agents the wrong codebase.

References

  • tree-sitter
  • TypeScript Compiler API
  • OpenTelemetry semantic conventions
  • Sourcegraph engineering blog
AI AgentsCode RetrievalRepo MapsCIDeveloper Workflow

← Back to all posts