_ _
_ __ ___ __ _ | |_ ___ ___ __| | ___
| '_ \ / _ \ / _` | | __| / __| / _ \ / _` | / _ \
| | | || __/ | (_| | | |_ | (__ | (_) | (_| | | __/
|_| |_| \___| \__,_| \__| \___| \___/ \__,_| \___|
Local-first repo intelligence for AI coding agents. The graph your agent wishes it had.
Stop letting your agent re-grep your repo on every turn. neatcode indexes your TypeScript codebase into a real AST graph, learns the conventions your code already follows, and answers your agent's structural questions over MCP — deterministic, no LLM in the hot path.
┌──────────────────┐ MCP ┌─────────────────────────────────┐
│ 💻 your editor │ ◀────stdio─────▶ │ ⚙️ neatcode-mcp (11 tools) │
│ │ │ |
│ Claude Code │ │ 🔍 query_graph 📍 find_node │
│ Cursor │ │ 🛤️ trace_flow ✅ validate │
│ Codex │ │ 💡 propose_rule 📚 get_rules │
└──────────────────┘ │ 📝 add_note 📋 repo_overview │
│ ⮕ accept_proposed_rule │
│ ⮕ derive_candidates │
│ ⮕ accept_derived_candidates │
└────────────────┬────────────────┘
│
┌───────────▼───────────┐
│ 📂 .neatcode/ │
│ ├── graph.db │
│ ├── skills.yaml │
│ ├── decisions.yaml │
│ └── REPO_OVERVIEW │
└───────────────────────┘
| Without neatcode | With neatcode |
|---|---|
| Agent re-greps the repo every turn | Agent calls query_graph against an indexed AST |
| "What conventions does this repo follow?" → vibes | Heuristic miner derives conventions from real code |
Agent writes BookingHandler despite a Controller convention |
validate_diff flags it before the file is written |
Rules live in tribal memory or stale CONTRIBUTING.md |
Rules live in .neatcode/skills.yaml, validated bidirectionally |
| Each chat starts cold | get_repo_overview ships the one-page brief at session start |
# 1. Install (one tarball, both binaries)
npm i -g @neatcodeai/cli
# 2. Index your repo
cd ~/your/typescript/repo
neatcode init
# 3. Wire it into Claude Code
neatcode claude installRestart Claude Code. Your agent now has 11 structural tools — and a fresh .neatcode/REPO_OVERVIEW.md for instant context.
🎯 Founder's target: installed in 10 minutes, useful in 1 hour.
👤 You: "Where do we handle booking cancellation?"
🤖 Agent: [calls query_graph with "booking cancellation"]
↓
📍 BookingsController.cancel (apps/api/src/bookings/…)
📍 BookingsService.cancel (called by Controller)
📍 RefundService.issueRefund (called by Service)
📍 BookingCancelledEvent (emitted at end)
Here's the flow: Controller → Service → RefundService,
with a domain event published on completion.
vs. without neatcode: agent runs 4 grep calls, reads 18 files, and still misses the event publisher.
neatcode ships TypeScript types and is built around ts-morph. It indexes any TypeScript codebase that compiles.
| What | Compatibility |
|---|---|
| TypeScript versions | 5.0 → 5.7+ (uses ts-morph 24.x; tracks the bundled compiler) |
| File extensions | .ts, .tsx (skips .d.ts and .generated.ts(x)); .js / .cjs not yet indexed |
| Module systems | ESM, CommonJS, mixed — output target doesn't matter |
tsconfig.json |
Auto-detected at repo root; respects paths, baseUrl, composite |
| JSX / TSX | Yes — .tsx parsed, JSX edges emitted |
| Decorators | Stage-3 standard + experimental (NestJS, TypeORM, class-validator) |
| Type-aware queries | Uses TS TypeChecker for cross-file call-graph resolution |
| Strict mode | Recommended but not required |
| Monorepos | pnpm / yarn / npm workspaces — each package gets its own qualified_name namespace |
| Frameworks | NestJS, React, Next.js, Express, Fastify (auto-detected; no rule packs) |
Not on TypeScript? neatcode v0.1 is TS-only by design — Python, Go, and JS support are roadmapped (see Roadmap).
neatcode <command> [options]
| Command | What it does |
|---|---|
neatcode init |
First-time setup. Creates .neatcode/, indexes every .ts/.tsx file, runs the heuristic miner, drops REPO_OVERVIEW.md. |
neatcode sync [--force] |
Re-index changed files. Content-hash cached — editing 3 files re-parses 3 files. |
neatcode ask "<question>" |
Ad-hoc graph query from the terminal. Same engine as query_graph over MCP. |
| Command | What it does |
|---|---|
neatcode skills derive [--threshold 0.85] [--min-population 3] |
Mine the graph for high-coverage patterns. Writes skills.candidates.yaml. |
neatcode skills accept <id> [--all] [--confidence high|medium] [--local] |
Promote candidates into skills.yaml (committed) or skills.local.yaml (gitignored). |
neatcode skills reject <id> [--reason "…"] |
Mark a candidate as rejected so the next derive skips it. |
| Command | What it does |
|---|---|
neatcode validate [<files…>] [--json] |
No args → check whole graph. With files → treat as planned diff. Pre-existing offenders are grandfathered under scope: "new". |
neatcode exception add <rule_id> <location> [--days N] [--reason "…"] |
Silence a specific violation. Auto-expires with --days. |
neatcode exception list |
Show active exceptions. |
neatcode exception revoke <rule_id> <location> |
Append-only revoke (original entry preserved). |
| Command | What it does |
|---|---|
neatcode claude install |
Merges .mcp.json, appends MCP guidance to CLAUDE.md. Idempotent. |
neatcode claude uninstall |
Reverses both. Pre-existing CLAUDE.md content is preserved. |
neatcode cursor install (coming v0.2) |
Wires neatcode-mcp into Cursor's MCP config. |
neatcode codex install (coming v0.2) |
Wires neatcode-mcp into Codex. |
The agent gets 11 tools the moment Claude Code restarts after neatcode claude install:
Discovery + queries
| Tool | When the agent calls it |
|---|---|
get_repo_overview |
Once per session. Returns frameworks, top symbols, layout, active rules, dirty-file status. |
query_graph |
"What handles X?", "what calls Y?" — keyword-scored search with optional 1-hop expansion. |
find_node |
Exact lookup by name or qualified_name. Returns merged class+method decorators. |
trace_flow |
Multi-hop path traversal. "From BookingsController.create to the DB" returns the call chain. |
Rules + validation
| Tool | When the agent calls it |
|---|---|
get_rules |
Returns active rules, candidates, rejected list, and repo context for curation flows. |
validate_diff |
Pre-flight on a planned change. Returns violations + four-option suggestions block. |
propose_rule_from_prompt |
Inspects a user utterance for rule-language ("never", "must", "always"). Surfaces a structured proposal. |
accept_proposed_rule |
Promote a single proposed rule into skills.yaml. |
derive_candidates |
Run the heuristic miner on demand and return candidate rules. |
accept_derived_candidates |
Bulk-accept miner candidates by id, confidence, or --all. |
Memory
| Tool | When the agent calls it |
|---|---|
add_note |
Persist a fact the user shared. Appends to .neatcode/memory/notes-YYYY-MM-DD.md. |
All deterministic. No LLM call inside the server. The agent is the brain; neatcode is the spine.
neatcode init creates the directory and seeds it with the indexer output and miner proposals. The remaining files appear on demand as you accept rules, record exceptions, and let the agent take notes.
Created by neatcode init:
<repo-root>/
├── .neatcodeignore team-shared excludes (commit it)
└── .neatcode/
├── graph.db SQLite graph (regenerate; should gitignore)
├── skills.candidates.yaml miner proposals (regenerated each sync)
├── REPO_OVERVIEW.md session-start brief (commit it)
├── plans/ /writing-plans skill scaffold (commit it)
└── specs/ /brainstorming skill scaffold (commit it)
Appears on demand:
.neatcode/
├── skills.yaml on first `skills accept` (commit it)
├── skills.local.yaml on `skills accept --local` (should gitignore)
├── skills.rejected.yaml on first `skills reject` (commit it)
├── decisions.yaml on first exception/supersede (commit it)
├── memory/ on first `add_note` MCP call (commit it)
└── mcp.log on first MCP server start (should gitignore)
ℹ️
neatcode initdoesn't touch your.gitignore. Add the regeneratable bits yourself:.neatcode/graph.db* .neatcode/skills.candidates.yaml .neatcode/skills.local.yaml .neatcode/mcp.log
graph.db is regeneratable from a single neatcode sync. The committed files above form the team-shared memory of the repo — that's the part you code-review.
Three sources, one lifecycle:
- Heuristic mining —
neatcode skills deriveproposes candidates above the coverage threshold. Four primitives mined today:decorator-required,annotation-forbidden,naming-pattern,folder-naming-convention. - Prompts — when the user says "all classes with @Controller must use @UseGuards", the agent calls
propose_rule_from_prompt. A local keyword classifier — no LLM in the loop — extracts a structured candidate. User confirms, agent runsskills accept. - Manual — hand-edit
.neatcode/skills.yaml(create it on first rule). The schema is documented and stable.
Rules are validated bidirectionally:
- A diff that violates a rule → four-option UX (record exception · time-bounded · supersede the rule · abort).
- A rule that no longer matches the codebase → can be superseded with an entry in
decisions.yaml.
| Primitive | Mined? | Example |
|---|---|---|
decorator-required |
yes | classes with @ApiTags must also have @Controller |
annotation-forbidden |
yes | classes with @Controller must not carry @Deprecated |
naming-pattern |
yes | classes with @Module end with "Module" |
folder-naming-convention |
yes | files in apps/api/services/ end with .service.ts |
import-forbidden |
via prompt | never import from packages/internal/ |
edge-forbidden |
via prompt | controllers must not call repositories directly |
Layer constraints ("controllers can't reach repositories") are expressed via edge-forbidden — no special "layer" type.
Rules default to scope: "new" — pre-existing violations are grandfathered. Only code introduced after the rule was promoted gets checked. Set scope: "all" in YAML to enforce against existing code.
This avoids the classic "we accepted 30 conventions and now CI is on fire" problem.
┌──────────────────────────────────────────────────┐
│ 📜 ts-morph parser ──▶ 🗂️ polymorphic graph │
│ (SQLite) │
└─────────────────────┬────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
🔍 query_graph 🎓 skills derive ✅ validate_diff
📍 find_node (heuristic miner) (engine.ts)
🛤️ trace_flow │ │
│ ▼ ▼
│ skills.candidates active rules ∩
│ → accept decisions.yaml
▼ │
🤖 agent answers ◀───────────────────────── ⚠️ violations
+ 4-option UX
Every relationship gets a confidence label (EXTRACTED | INFERRED | AMBIGUOUS); the validator never fires on rules built solely from AMBIGUOUS edges. The repo-root-relative qualified_name is the FK that ties nodes, edges, rules, and decisions together. There's no LLM in the hot path of any MCP tool — the LLM lives in the agent harness (Claude Code, Cursor, Codex), and neatcode is the spine.
The CLI bundles everything you need; the others are split for advanced use.
| Package | Install for | What it gives you |
|---|---|---|
@neatcodeai/cli |
Most users | neatcode + neatcode-mcp binaries (depends on the others). |
@neatcodeai/mcp |
Bare MCP server | The stdio MCP binary alone. |
@neatcodeai/core |
Library users | parseFile, Database, ask, miner — all typed. |
Single install gets you everything:
npm i -g @neatcodeai/cli
which neatcode neatcode-mcppnpm install # builds better-sqlite3 native module
pnpm build # builds all three packages
pnpm typecheck # tsc --noEmit across packages
pnpm test # vitest run
pnpm test:watch
pnpm clean
# Smoke-test
node packages/cli/dist/index.js --helpNode 20+, TypeScript strict + verbatimModuleSyntax, ESM only.
- v0.1 (shipped) — TypeScript-only, 11 MCP tools, validation engine, four-option UX, heuristic miner with four primitives, Claude Code installer.
- v0.2 — Cursor + Codex installers,
import-forbidden/edge-forbiddenmining, agent-proposed rules (skills derive --with-agent). - v0.3 — Watcher mode (
neatcode dev), JavaScript /.js/.cjsindexing, TypeScript LSP integration. - Later — Python via
tree-sitter, Go viago/parser, multi-language graphs joined by import-edge inference.
Issues, feature requests, and convention horror stories: github.com/HitmanDark07/neatcodeai/issues.
- No framework rule packs — conventions emerge per-repo.
- Decorators / generics / JSX live in
extraJSON, not schema columns. qualified_nameis always repo-root-relative.- Rules built solely on
AMBIGUOUSedges never enforce. - v0.1 doesn't solve general NestJS DI —
@Module({ providers, controllers, imports })arrays become direct edges and we stop there.
MIT · built with ❤️ by @HitmanDark07
⭐ If neatcode helps your agent stop guessing, star the repo.