Local-first repo intelligence for JavaScript/TypeScript codebases, built so AI coding agents can find the right files, symbols and routes before editing.
Source code never leaves the machine. The index is a plain SQLite file beside the repo.
v1 command set complete: index, files, symbol, map and task all work
end to end (see the SRS).
cargo build --release# Build or refresh the index
codemap index /path/to/repo
# What's in there
codemap files /path/to/repo --kind source
codemap files /path/to/repo --json
# Where is this symbol, and what depends on its file
codemap symbol /path/to/repo ConfirmSubmitButton
# High-level repo map
codemap map /path/to/repo
# Paste a ticket, get the files to read first
codemap task /path/to/repo --text "$(pbpaste)"
codemap task /path/to/repo --text "..." --json --limit 8Example, against a real Next.js app:
$ codemap index ./aircraftspa
indexed /path/to/aircraftspa
files 202
symbols 238
imports 343 (146 resolved locally)
packages 1
index /path/to/aircraftspa/.codemap/index.db
$ codemap symbol ./aircraftspa ConfirmSubmitButton
ConfirmSubmitButton exported component — src/components/ConfirmSubmitButton.tsx:13-35
imported by 2 file(s):
src/app/account/aircraft/page.tsx
src/components/ConfirmSubmitButton.test.tsxThe honest test is whether a real ticket finds the files a human went to. Pasting the text of CHA-1924 — a bug about the Cancel button on the requests page — into an index of the app it was filed against:
$ codemap task ./aircraftspa --text "My Requests Cancel button has no
confirmation dialog and no revert. Clicking Cancel this request immediately
changes the request status from Requested to Cancelled..."
**Confidence:** high
## Candidate files
- `src/app/account/requests/actions.ts` (102) — path contains requests;
defines `cancelRequestAction` (function); imported by 2 file(s)
- `src/lib/portal-db.ts` (90) — defines `cancelRequest` (function);
imported by 11 file(s)
- `src/app/account/requests/[id]/page.tsx` (61) — path contains requests;
defines `RequestDetail` (component)
## Candidate symbols
- `cancelRequestAction` (function) — `src/app/account/requests/actions.ts:7-16`
- `cancelRequest` (function) — `src/lib/portal-db.ts:212-230`Those are the three files that actually had to change, ranked top, with line ranges — from the ticket text alone, no LLM involved.
Terms are lowercased, stopword-filtered and matched against paths and symbol
names. Identifiers are split on case and separators, so a task written in prose
("submit button") matches ConfirmSubmitButton — without that, prose almost
never matches camelCase.
Weights put a symbol-name hit above an incidental path hit, so naming a function surfaces its file rather than every file sharing a directory word. Test files are penalised (context, not usually the edit site) and widely-imported files get a small, capped bonus — capped, or every util file wins regardless of the task.
Every result carries the reasons that produced it. The SRS requires citation over assertion, and an unexplained ranking is not reviewable.
Ranking is deterministic: ties break on path, never on hash iteration order, so the same task text against the same index always produces the same pack.
Per file: language, classification (source / test / config / manifest / instructions / docs), and owning package in a monorepo.
Per source file, via tree-sitter: imports (including export … from
re-exports), exports, functions, classes, and arrow functions assigned to a
binding — the last being the dominant modern style and invisible to anything
that only reads function_declaration.
React components are flagged when a PascalCase name and JSX appear
together. Either signal alone is not enough: function Card() { return 42 } is
not a component, and function renderRow() { return <tr/> } is not either.
Route handlers are flagged with the reason attached, so output can cite why
rather than assert it — exports GET, POST, under an api/ directory,
named route. Test files beside a handler are excluded.
Relative specifiers and tsconfig/jsconfig paths aliases resolve to indexed
files; bare specifiers (react) are package dependencies and stay unresolved.
Alias support matters more than it sounds: on the Next.js app above there are
36 relative imports against 109 @/… aliases, so without it roughly
three-quarters of the dependency graph is missing. tsconfig is parsed
tolerantly — comments and trailing commas are common and legal to TypeScript,
and a strict JSON parse silently yields no aliases on a large share of real
repos.
- Syntax only. No type checking, no module-graph evaluation. Extraction stays fast and dependency-free; the output is candidates an agent then reads, not a verified answer.
- Indexing is a full rebuild in one transaction. A failed index leaves the previous one intact rather than a half-written mixture of two scans.
- Paths are normalised to
/, so an index built on Windows matches one built on Unix — paths are the join key for every other table. - Schema changes rebuild rather than migrate. The index is derived, so rebuilding is always correct and always cheap.
cargo test
cargo clippy --all-targets
cargo fmt --check