{"_id":"slimdex-mcp","_rev":"3-1855f3110e5bcb6e904307112e47a4ad","name":"slimdex-mcp","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"slimdex-mcp","version":"1.0.0","keywords":["mcp","model-context-protocol","claude","coding-agent","tokens","code-intelligence","llm"],"license":"MIT","_id":"slimdex-mcp@1.0.0","maintainers":[{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"}],"homepage":"https://github.com/Siddhukaushik/slimdex-mcp#readme","bugs":{"url":"https://github.com/Siddhukaushik/slimdex-mcp/issues"},"bin":{"slimdex-mcp":"dist/index.js"},"dist":{"shasum":"04899e1bc8847414c6d25bd2f6abc3d1f99bf2e6","tarball":"https://registry.npmjs.org/slimdex-mcp/-/slimdex-mcp-1.0.0.tgz","fileCount":36,"integrity":"sha512-MMJ4ES1TMbGQvDltrJMJ8MRYfczS4bYCGxvzY4nxTpoN4hUmG3bKkSrmDbDMDkq/Zpo0nmVMwjCB/Axb4hUV1g==","signatures":[{"sig":"MEQCICMVfzUwdmHyEUG51RjsIQOgjiqfwDLhV0uxB+H+VPFjAiBllW/E63RqAEQeCK+phWMYZ8F2UkPtXkQ/8UPk+quzng==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":373641},"type":"module","engines":{"node":">=20"},"gitHead":"01c0d9597cad1578a7d03ab7d7f21d0225c1c167","mcpName":"io.github.Siddhukaushik/slimdex-mcp","scripts":{"dev":"tsc --watch","test":"vitest run","audit":"node scripts/audit-coverage.mjs","build":"tsc","smoke":"npm run build && node smoke-test.mjs","start":"node dist/index.js","pretest":"npm run build","audit:corpus":"npm run build && node scripts/corpus-audit.mjs","install-hook":"node scripts/install-hook.mjs","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"},"overrides":{"@emnapi/core":"1.11.2","@emnapi/runtime":"1.11.2","@hono/node-server":"^2.0.5"},"repository":{"url":"git+https://github.com/Siddhukaushik/slimdex-mcp.git","type":"git"},"_npmVersion":"11.6.2","description":"A local MCP server for narrow code retrieval: outlines, symbol context, a dependency graph, and persistent memory instead of whole-file reads.","directories":{},"_nodeVersion":"24.13.0","dependencies":{"zod":"^3.23.8","@modelcontextprotocol/sdk":"^1.12.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.10","typescript":"^5.6.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/slimdex-mcp_1.0.0_1785808591561_0.5967270265426825","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"slimdex-mcp","version":"1.0.1","keywords":["mcp","model-context-protocol","claude","coding-agent","tokens","code-intelligence","llm"],"license":"MIT","_id":"slimdex-mcp@1.0.1","maintainers":[{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"}],"homepage":"https://github.com/Siddhukaushik/slimdex-mcp#readme","bugs":{"url":"https://github.com/Siddhukaushik/slimdex-mcp/issues"},"bin":{"slimdex-mcp":"dist/index.js"},"dist":{"shasum":"0d59dccd9c25de2b6a7a98136c68278d4046b3df","tarball":"https://registry.npmjs.org/slimdex-mcp/-/slimdex-mcp-1.0.1.tgz","fileCount":36,"integrity":"sha512-QiOAIqQEQj+ozUYCVy4sS1fyzUxIGxF/5+88gdSFQ/OiKqYVsyJMyKHw2pwuGw6opj9hL/yzgTxFERDfnAsTSQ==","signatures":[{"sig":"MEYCIQDELmS3KVL3OZiAdwDOUZ1EllNLHPj7bEGso58U6DoYRgIhAOw1TVncZBl6aMIfoOIW+ugKVKQ3gU1iuVrrxCP70/9U","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":374457},"type":"module","engines":{"node":">=20"},"gitHead":"608b236776fbe21d1a7e6df4564014f43959a705","mcpName":"io.github.Siddhukaushik/slimdex-mcp","scripts":{"dev":"tsc --watch","test":"vitest run","audit":"node scripts/audit-coverage.mjs","build":"tsc","smoke":"npm run build && node smoke-test.mjs","start":"node dist/index.js","pretest":"npm run build","audit:corpus":"npm run build && node scripts/corpus-audit.mjs","install-hook":"node scripts/install-hook.mjs","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"},"overrides":{"@emnapi/core":"1.11.2","@emnapi/runtime":"1.11.2","@hono/node-server":"^2.0.5"},"repository":{"url":"git+https://github.com/Siddhukaushik/slimdex-mcp.git","type":"git"},"_npmVersion":"11.6.2","description":"A local MCP server for narrow code retrieval: outlines, symbol context, a dependency graph, and persistent memory instead of whole-file reads.","directories":{},"_nodeVersion":"24.13.0","dependencies":{"zod":"^3.23.8","@modelcontextprotocol/sdk":"^1.12.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.10","typescript":"^5.6.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/slimdex-mcp_1.0.1_1786236552503_0.5120317028799013","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"slimdex-mcp","version":"1.1.0","mcpName":"io.github.Siddhukaushik/slimdex-mcp","description":"A local MCP server for narrow code retrieval: outlines, symbol context, a dependency graph, and persistent memory instead of whole-file reads.","type":"module","keywords":["mcp","model-context-protocol","claude","coding-agent","tokens","code-intelligence","llm"],"homepage":"https://github.com/Siddhukaushik/slimdex-mcp#readme","repository":{"type":"git","url":"git+https://github.com/Siddhukaushik/slimdex-mcp.git"},"bin":{"slimdex-mcp":"dist/index.js"},"engines":{"node":">=20"},"scripts":{"build":"tsc","start":"node dist/index.js","dev":"tsc --watch","test":"vitest run","smoke":"npm run build && node smoke-test.mjs","prepublishOnly":"npm run build && npm test","audit":"node scripts/audit-coverage.mjs","pretest":"npm run build","audit:corpus":"npm run build && node scripts/corpus-audit.mjs","install-hook":"node scripts/install-hook.mjs","gravity":"node scripts/gravity-report.mjs"},"dependencies":{"@modelcontextprotocol/sdk":"^1.12.0","zod":"^3.23.8"},"devDependencies":{"@types/node":"^22.0.0","typescript":"^5.6.0","vitest":"^4.1.10"},"license":"MIT","overrides":{"@emnapi/core":"1.11.2","@emnapi/runtime":"1.11.2","@hono/node-server":"^2.0.5"},"gitHead":"bf7d649bdef96fef025b0165fbed89208686e640","_id":"slimdex-mcp@1.1.0","bugs":{"url":"https://github.com/Siddhukaushik/slimdex-mcp/issues"},"_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-dGADCGD1wmRBv8JRz1FxrWxFbHaFTlgZTZBdyjsT3vN5FgzFHsE3nKRuyM37VjsjagdSiQUgfw907iPzY2ic3w==","shasum":"e1743627633e873bbd69c9057ebde10fdc35e7ba","tarball":"https://registry.npmjs.org/slimdex-mcp/-/slimdex-mcp-1.1.0.tgz","fileCount":39,"unpackedSize":401069,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICuUiiKT9D2euRlySpEdpTWt2IXYTISJWs5iNp86xhoPAiAhjHJAWc/BlzAM5kxvFRyFzf1FQKjCoToYec3x09qsJQ=="}]},"_npmUser":{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"},"directories":{},"maintainers":[{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/slimdex-mcp_1.1.0_1786854562189_0.3735934214270751"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T01:56:31.387Z","modified":"2026-08-16T04:29:22.530Z","1.0.0":"2026-08-04T01:56:31.727Z","1.0.1":"2026-08-09T00:49:12.663Z","1.1.0":"2026-08-16T04:29:22.363Z"},"bugs":{"url":"https://github.com/Siddhukaushik/slimdex-mcp/issues"},"license":"MIT","homepage":"https://github.com/Siddhukaushik/slimdex-mcp#readme","keywords":["mcp","model-context-protocol","claude","coding-agent","tokens","code-intelligence","llm"],"repository":{"type":"git","url":"git+https://github.com/Siddhukaushik/slimdex-mcp.git"},"description":"A local MCP server for narrow code retrieval: outlines, symbol context, a dependency graph, and persistent memory instead of whole-file reads.","maintainers":[{"name":"siddhukaushik121","email":"vvkaushik121@gmail.com"}],"readme":"# slimdex-mcp\r\n\r\n[![npm](https://img.shields.io/npm/v/slimdex-mcp?color=cb3837&logo=npm)](https://www.npmjs.com/package/slimdex-mcp)\r\n[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-listed-0a7ea4)](https://registry.modelcontextprotocol.io)\r\n[![Glama score](https://glama.ai/mcp/servers/Siddhukaushik/slimdex-mcp/badges/score.svg)](https://glama.ai/mcp/servers/Siddhukaushik/slimdex-mcp)\r\n[![license](https://img.shields.io/npm/l/slimdex-mcp)](LICENSE)\r\n\r\n**Your agent reads a 900-line file to change one function — then pays for that\r\nfile again on every turn that follows.** The whole conversation is re-sent each\r\ntime, so an early read isn't a one-time cost. It's rent.\r\n\r\nSlimdex is a local [MCP](https://modelcontextprotocol.io) server that gives\r\ncoding agents **narrow retrieval** instead: a file's outline, one symbol's body,\r\nwho calls it, what breaks if it changes — and memory that survives the session,\r\nso the next chat starts informed rather than re-deriving the repo from zero.\r\n\r\n```bash\r\nclaude mcp add slimdex -- npx -y slimdex-mcp\r\n```\r\n\r\n**~50% fewer tokens** in day-to-day use — ~55–60% on navigation-heavy work,\r\n~45% on output-heavy work, and 85–90% on the worst case it was built for\r\n(one 6,200-line file, explored through a skeleton and 12 symbol bodies instead\r\nof four full reads).\r\n\r\n> **Status: 1.1.0, on [npm](https://www.npmjs.com/package/slimdex-mcp) and in the\r\n> [MCP Registry](https://registry.modelcontextprotocol.io).** Those numbers are\r\n> self-measured on the repos it has been run against, single sessions, not\r\n> independently validated — and `stats` counts characters, not tokens.\r\n> Read [What's actually verified](#whats-actually-verified) before relying on it.\r\n\r\n| Tool | What it returns |\r\n|------|-----------------|\r\n| `index_repo` | Builds/refreshes a persistent symbol + import index; only changed files re-parse |\r\n| `outline_file` | Declarations of one file with line numbers |\r\n| `get_file_skeleton` | Signatures with bodies elided, nesting preserved |\r\n| `read_lines` | One line range |\r\n| `get_symbol_context` | One function/class body ±2 lines, capped by `maxLines`; `names:[...]` pulls several bodies in one call |\r\n| `search_code` | `path:line:col` + the matching line with caret highlight; `limit`/`offset`/cursor pagination |\r\n| `find_definition` | Definition site(s) of a symbol as `path:line:col` |\r\n| `search_symbols` | Fuzzy symbol-name lookup, ranked exact→prefix→substring→subsequence |\r\n| `search_intent` | Natural-language query ranked over symbols by BM25 (no embeddings) — find code by what it does |\r\n| `context_pack` | One call: ranks a topic's symbols, shows how they connect, and bundles the top bodies under a budget — the whole exploration in one round-trip |\r\n| `find_references` | Textual references as `path:line:col` + enclosing function |\r\n| `find_tests` | Of the references to a symbol, which live in test files — or a warning that none do |\r\n| `replace_symbol` | Overwrite a symbol's body addressed by name (no re-sent old code); snapshots first, re-indexes after |\r\n| `get_context` | One call: opt-in definition / signature / callers / imports / dependents, budgeted |\r\n| `repo_map` | Dir-level file/line/symbol counts; `path:` drills into a dir's largest files |\r\n| `changed_files` | Changed files + which symbols each hunk lands in |\r\n| `dep_graph` | `imports` / `dependents` / a Mermaid diagram (`root`+`depth` BFS) |\r\n| `stats` | Per-tool call counts and response sizes, in characters, plus read follow-through and write discipline |\r\n| `batch` | Runs several calls in one request |\r\n| `recap` | Prior sessions' activity, reconstructed automatically from the server's tool-call journal — works even when nothing was saved |\r\n| `brief` | One-shot session opener: repo summary + journal-derived focus + saved conclusions checked against the live index (✓ live / ⚠ maybe stale) |\r\n| `digest_save` / `digest_get` | Store a compact repo architecture cheat-sheet once; read it back with a per-covered-file freshness verdict, so the next session skips re-exploring |\r\n| `snapshot` | Copies uncommitted files into `.slimdex/snapshots/` (also auto-runs hourly via `index_repo` on a dirty tree) — insurance against accidental resets, not a substitute for committing |\r\n| `memory_save/search/list/delete` | Durable notes in `.slimdex/memory.json` |\r\n\r\nThe retrieval guidance below also ships in the server's MCP `instructions`, so\r\nclients inject it into the model's context automatically.\r\n\r\n### Recommended agent flow\r\n\r\n`brief` first, at the very start of a session — one call that reports what the\r\nrepo is, where recent sessions were digging, and which saved conclusions still\r\nmatch the code (stale ones flagged), so a fresh chat starts informed instead of\r\nblank. Then `get_context(\"Foo\")` to answer \"what is this, who calls it, what does\r\nit depend on\" in one response. To understand a whole *area* rather than one\r\nsymbol, `context_pack(\"how does auth work\")` runs the entire exploration\r\nserver-side and hands back a single bounded bundle — the relevant symbols, how\r\nthey connect, and the top bodies — so you spend one call and one transcript\r\nentry instead of ten. Don't know the name, only what it does? —\r\n`search_intent(\"parse the config file\")` ranks symbols by intent with BM25, no\r\nembeddings. Drop to `get_symbol_context` for one body (it flags itself if the file\r\ndrifted from the index, so you don't re-read to check), `get_file_skeleton` for a\r\nfile's shape, and `read_lines` when you need exact source. Before editing a\r\nsymbol, `find_tests` on it to see what covers it; to\r\nrewrite a whole function, `replace_symbol` (you send only the new body — the old\r\ncode isn't re-sent just to locate the edit). Use `batch` to bundle several\r\nlookups. Every search tool takes `limit` (default 20) and `offset`.\r\n\r\n**Response budgeting:** `get_context` sections are opt-in via `include`\r\n(default: definition, signature, callers, imports — add `body` or `dependents`\r\nexplicitly), callers are capped by `callerLimit`, and the response is bounded\r\nby `maxChars` (default 12,000). Every cap that trips prints an explicit notice\r\n(`showing 3 of 68`, `truncated at maxChars=...`) rather than dropping data\r\nsilently. `get_symbol_context` caps its span with `maxLines` the same way, and\r\n`memory_list` returns the newest 50 facts unless told otherwise, as ~150-char\r\npreviews rather than whole bodies (`memory_get ids:[...]` expands them,\r\n`full:true` dumps everything). On an 18-fact store that is the difference\r\nbetween ~4,100 and ~18,600 chars in the call every session opens with.\r\n\r\n### Config: `<root>/.slimdex.json` (optional)\r\n\r\n```json\r\n{\r\n  \"ignoreDirs\": [\"fixtures\", \"backend/src/main/resources/static/assets\"],\r\n  \"extensions\": [\".astro\", \".vue\"],\r\n  \"suffixes\": [\".stories.mdx\"],\r\n  \"exclude\": [\"generated/\", \"legacy/vendor\"],\r\n  \"maxFileBytes\": 2000000\r\n}\r\n```\r\n\r\n`suffixes` matches a filename ending, for file types an extension can't identify.\r\nSalesforce metadata sidecars ship as a built-in: `AccountSvc.cls-meta.xml`,\r\n`panel.js-meta.xml` and `Account.object-meta.xml` are indexed, while `pom.xml`,\r\n`web.xml` and `manifest/package.xml` are not — adding `.xml` to `extensions`\r\nwould have pulled in every config tree in the repo. Suffix-matched files are\r\nindexed for search and read reach, not symbols.\r\n\r\nMerged on top of the built-in ignore list (`node_modules`, `dist`, `.venv`,\r\n`.svelte-kit`, `Pods`, `.pytest_cache`, …). An `ignoreDirs` entry is either a bare\r\nname, matching any directory so called at any depth, or a path containing `/`,\r\nanchored at the repo root and respecting directory boundaries (`src/gen` will not\r\nalso ignore `src/generated`). `index_repo` echoes what it loaded and warns about\r\nunknown keys, wrong types, or invalid JSON, so a typo'd config isn't silently\r\nindistinguishable from none.\r\n\r\n**Build output usually needs no config at all.** Beyond the directory list, any\r\nfile whose lines run past ~5,000 characters is treated as minified build output and\r\nleft out of the index — bundlers strip newlines, and hand-written source doesn't\r\nlook like that. This catches what a name list structurally cannot: a hash-named\r\nbundle (`index-B7xK2p9q.js`) inside a directory called `assets`. `assets`, `public`\r\nand `static` are deliberately *not* ignored by name, because real source lives in\r\nthem; `index_repo` reports the count as `skipped(minified build output): N`.\r\n\r\n### How the token saving works\r\n\r\nThere's no compression trick. The saving is behavioral: these tools let an agent\r\nretrieve outlines, ranges, and locations instead of whole files, and the\r\npersistent index means repeat lookups hit a cached query rather than a re-read.\r\n\r\nTwo later sessions, run by different models on different repo shapes, added\r\nreal-world numbers to the original report:\r\n\r\n**Multi-file web app, bug-fix session (GPT-5.3-Codex).**\r\n19 credits reported with slimdex; the model's own estimate for the same scope\r\nwithout it: 45–70 credits. Math: 19/45 → 19/70 ≈ **58–73% cheaper**. The\r\ncounterfactual is the model's estimate, not a measured A/B — directional.\r\n\r\n**Single giant file (folio-app: one 6,200-line, 313 KB `app.js`).**\r\nSlimdex's own stats: ~34,000 chars across 8 calls ≈ 9–10k tokens — one\r\nskeleton (213 signatures), then bodies of only ~12 relevant functions, 9 of\r\nthem fetched in a single `get_symbol_context names:[...]` call. The naive\r\npath: 313 KB ≈ 78–85k tokens across 3–4 forced full reads. Math: ~10k vs\r\n~80k ≈ **~70k tokens saved, an 85–90% reduction** on exploration. The bug's\r\ndiagnosis (an export path with no matching import path) was visible from the\r\nskeleton's signatures before a single body was opened.\r\n\r\nTogether they sketch the scaling law: **the saving scales with how much\r\nirrelevant code the naive path would drag in.** One giant file is the best\r\ncase; a normal repo lands around half to two-thirds cheaper; a repo of tiny\r\nfiles breaks even. Same standing caveats as everything here: stats count\r\nchars, not tokens (÷3.5–4), and single sessions are evidence, not benchmarks.\r\n\r\n**Both figures above measure reading only, which is the cheaper half.** Output\r\ncosts roughly 4–5× input, so an undisciplined edit wastes more than an\r\nundisciplined read: rewriting a whole function through a generic edit tool means\r\nre-sending the entire old body purely so the tool can locate it. `replace_symbol`\r\naddresses by name and that cost disappears. `stats` reports this alongside\r\nfollow-through, because the leak is otherwise invisible — the expensive path\r\nstill produces a correct edit, so nothing signals that you overpaid:\r\n\r\n```\r\nwrite discipline:\r\n  replace_symbol: 0 call(s), 0 symbol(s) rewritten by name\r\n  changed outside slimdex: 12 file(s)\r\n  pre-edit checks (find_tests/dep_graph/get_context/changed_files): 0\r\n```\r\n\r\nExternal edits are inferred from content hashes moving between two `index_repo`\r\nruns, so the number is honest about its limits: it sees that bytes changed, never\r\nwhich tool changed them, and a human editing in another window counts too.\r\n\r\n\r\n### The realistic whole-workflow band\r\n\r\nThe figures above are single-scenario *exploration* numbers — the best case,\r\nwhere the naive path would have dragged in the most irrelevant code. Averaged\r\nacross a whole real workday, not just the exploration slice, the band settles\r\nlower:\r\n\r\n- **~55–60%** on navigation-heavy work — reading and understanding a codebase,\r\n  where narrow retrieval replaces whole-file reads most often.\r\n- **~45%** on output-heavy work — churning out new code, where more of the cost\r\n  is generation the server doesn't touch (though `replace_symbol` now shaves the\r\n  write side too).\r\n- **~50% averaged** over regular day-to-day use. The saving compounds the more\r\n  sessions run through it, because `brief` and memory mean each new chat starts\r\n  informed instead of re-deriving the repo from zero.\r\n\r\nUse it regularly across sessions in your IDE for the best of this.\r\n\r\n**Treat these as one data point, not a benchmark.** Single repo, single task, one\r\nA/B run each, self-measured, no repetitions or variance. Your mileage depends\r\nheavily on whether your agent actually reaches for the narrow tools instead of\r\nfalling back to reading files — which varies by client and model. The method is\r\nrepeatable if you want to check it: run the same task in two fresh sessions, one\r\ninstructed to use only Slimdex and one instructed to avoid it, and compare\r\n`/status` cache-write.\r\n\r\n---\r\n\r\n## What's actually verified\r\n\r\nBeing explicit, since the rest of this README is easy to over-read.\r\n\r\n**Covered by the unit suite** (`npm test` runs 224 tests across 23 files):\r\n\r\n- Symbol extraction across JS/TS (incl. class and object-literal methods),\r\n  Python, Go, Rust, Java/C#, and comment skipping — `symbols.test.ts`\r\n- Import extraction for JS `import`/`require`/`export-from`, Python, Rust\r\n- Block extraction, brace-scoped and indentation-scoped, with string/comment\r\n  awareness (quotes, templates, `//`, `/* */`, full-line `#`) — `extractBlock.test.ts`\r\n- Import resolution, external-module classification, reverse-edge dependents,\r\n  Mermaid emission, and root-BFS depth scoping — `graph.test.ts`\r\n- Search match format, pagination without overlap, per-line occurrence counting,\r\n  exact totals, regex escaping/rejection — `search.test.ts`\r\n- Opaque cursor round-tripping and malformed-cursor rejection; parser-backend\r\n  fallback — `pagination.test.ts`\r\n- Outline declaration detection vs. control flow — `outline.test.ts`\r\n- `get_symbol_context` `maxLines` budgeting and truncation notice\r\n\r\n- String/comment masking and brace-depth tracking — `lexer.test.ts`\r\n- Per-language extraction for all twelve supported languages — `languages.test.ts`\r\n- The index cache returns the same object until the index is rewritten\r\n- `.slimdex.json` loading: every key applied through a real index build, plus\r\n  the failure modes (invalid JSON, unknown keys, wrong types) each producing a\r\n  visible warning instead of silence — `config.test.ts`\r\n- `changed_files` against a real temporary git repository: hunk→symbol\r\n  attribution, untracked files, explicit base refs, and formatting; skips\r\n  cleanly when git isn't installed — `git.test.ts`\r\n- The file watcher, with real fs events: a save is debounced, reindexed, and\r\n  lands in the on-disk index — `watch.test.ts`\r\n- Graph edges beyond imports: name-reference edges for import-less code\r\n  (class→used-class, interface→implementation via dependents, trigger→handler)\r\n  and declarative-wiring edges from repo XML (metadata-binding→class), with\r\n  comment/string mentions excluded and per-build caching — `apexgraph.test.ts`\r\n- The in-memory file cache serves repeats without re-reading and always serves\r\n  fresh content after an on-disk change — `fscache.test.ts`\r\n- Test-file detection across JS/TS/Python/Go/Ruby/Java/C# conventions, with\r\n  Windows separators normalized and ordinary source (`latest.ts`, `Contest.java`)\r\n  not misflagged — `testlink.test.ts`\r\n- The write side: replacing a symbol's block, trailing code preserved, and CRLF\r\n  vs LF line endings kept so an edit isn't reflowed into a whole-file diff —\r\n  `edit.test.ts`\r\n- Memory staleness: a fact is marked live when it names a symbol/file that still\r\n  exists, flagged stale only when every code mention is gone, and left unflagged\r\n  for prose — plus brief composition — `brief.test.ts`\r\n- Intent search: camelCase/snake_case tokenization, and BM25 ranking that surfaces\r\n  a differently-named symbol by its intent words while scoring an unrelated query\r\n  to nothing — `intent.test.ts`\r\n- Freshness: a file newer than its indexed mtime reads as stale (line numbers may\r\n  be off), a matching mtime reads as fresh, and a missing file never cries stale —\r\n  `freshness.test.ts`\r\n- `context_pack` assembly: header + ranked symbols + bodies in one bundle, the\r\n  no-match message, char-budget gating that still guarantees the first body, and\r\n  the symbols-limit cap — `pack.test.ts`\r\n- The architecture digest: covered files modified after the digest read as stale,\r\n  a newer digest reads clean, coverage-scope and directory-prefix filtering, and\r\n  the rendered fresh/stale verdict — `digest.test.ts`\r\n\r\n**Covered end to end, through the real MCP server** (`integration.test.ts` spawns\r\nthe server over stdio against a temporary fixture repo and asserts on output):\r\n`index_repo`, `repo_map`, `read_lines`, `get_file_skeleton`, `outline_file`,\r\n`get_symbol_context`, `find_definition`, `find_references`, `find_tests` (the hit\r\nand the no-coverage warning), `search_intent` (intent ranking), `context_pack` (one-call\r\nbundle), `digest_save`/`digest_get` (round trip with freshness verdict),\r\n`get_context` (including its `maxChars` cap),\r\n`dep_graph` (imports + mermaid), `batch`, `search_code`, `search_symbols`,\r\n`stats`, `brief`, `replace_symbol` (write-then-query round trip and the\r\nunknown-symbol refusal), the `memory_save/search/list/delete` round trip, the\r\npath-escape guard, and the not-found paths.\r\n\r\nCI runs the build and both suites on Ubuntu + Windows, Node 20 and 22.\r\n\r\n**Caveat on the watcher test:** recursive `fs.watch` is platform-dependent, so\r\n`watch.test.ts` degrades to a logged skip on filesystems that never deliver an\r\nevent — same behavior as the watcher itself. On Windows, macOS, and current\r\nLinux it asserts the full save→reindex path.\r\n\r\n`npm run smoke` still exists but proves only that the pipeline is alive — the\r\ncorrectness assertions live in `integration.test.ts`.\r\n\r\n**Verified by inspection:** `src/` contains no network calls — no code leaves\r\nyour machine. This one you can check yourself:\r\n`grep -rE \"fetch\\(|https?://|axios|http\\.request\" src/`.\r\n\r\n## Longer documentation\r\n\r\nIn [`docs/`](docs/):\r\n\r\n- [`tool-guide.md`](docs/tool-guide.md) — every tool explained twice\r\n  (technically and in plain words) with an example each, the combined\r\n  workflow, and how mtime-based persistence works\r\n- [`tool-guide.html`](docs/tool-guide.html) — the same guide as a styled,\r\n  self-contained page for the browser\r\n- [`token-savings-report.md`](docs/token-savings-report.md) — the original A/B\r\n  measurement, its method, and how to repeat it\r\n- [`agent-brain.md`](docs/agent-brain.md) — the full operating discipline as a\r\n  readable document\r\n- [`agent-brain-slim.md`](docs/agent-brain-slim.md) — **the one to drop into a\r\n  repo** as CLAUDE.md / AGENTS.md. Self-contained and one page: savings ladder,\r\n  question→tool table, memory discipline, session hygiene, honest limits, env\r\n  knobs. Same coverage as the full document at ~30% of the prose, because the\r\n  tool rules are dense tables rather than paragraphs the server already injects.\r\n\r\n## Language coverage\r\n\r\nTwo measurements, because fixtures alone prove very little.\r\n\r\n**Fixtures** — one per language, counting the declarations a developer would\r\nactually navigate to: **65/65 found, 0 false positives**, pinned by\r\n`test/languages.test.ts`.\r\n\r\n**Real third-party code** — extraction run over ~11,800 files from several\r\nhundred real packages (React, Babel, Remix, Socket.io, Playwright, Three.js,\r\nEmotion, zod, ajv …) and compared against an independently written heuristic for\r\nwhat counts as a declaration: **95.9% recall**. Reproduce it yourself:\r\n\r\n```bash\r\nnpm run audit -- ./node_modules            # or any directory of code you didn't write\r\n```\r\n\r\nThat number is a floor, not a grade — the truth heuristic counts some\r\nnon-declarations, so real recall is a little higher. What it's for is catching\r\nregressions and finding the next real gap.\r\n\r\n### About frameworks\r\n\r\nAlmost nothing that failed the audit was framework-specific. Frameworks add\r\nannotations, decorators and conventions; they rarely invent syntax. Handle the\r\nlanguage and the frameworks come with it — fflib's Application/Domain/Selector/\r\nService/UnitOfWork layers extract completely (129 declarations) without a single\r\nfflib-aware rule.\r\n\r\nThe one genuine exception is **test DSLs**. A vitest/jest/mocha/RSpec file often\r\nhas no top-level declarations at all, so entire test directories used to index to\r\nnothing. `describe`/`it`/`test` titles are now indexed as kind `test`, which is\r\nwhat you actually navigate to in a test file.\r\n\r\nFramework **semantics** are recovered wherever the reference exists somewhere\r\nin the repo, through two extra edge sources in the graph:\r\n\r\n- **Name-reference edges**, for languages that have no import statement (e.g.\r\n  Apex): if one file's code — comments and strings masked out — mentions a\r\n  top-level type defined in another file, that's an edge. This is what makes\r\n  `implements` answerable as \"who implements this interface\", and links a\r\n  trigger to the handler class it news up.\r\n- **Declarative-wiring edges**: bindings that frameworks keep in configuration\r\n  rather than code (custom-metadata records, flow definitions) usually live in\r\n  the repo as XML with the type name as an element value. Repo XML is scanned\r\n  for known type names — XML comments excluded — and each hit becomes a\r\n  `metadata-file → class` edge, so `dependents` answers \"what wires this up\".\r\n\r\nBoth scans are cached per index build and cost nothing on repos without such\r\nfiles. Pinned by `apexgraph.test.ts`. What no static reader can see is a\r\nbinding that exists **only in a live system** — configured in a running org or\r\ndatabase and never retrieved into the repo. If it's not in the repo in any\r\nform, there is no edge to draw; search the type name instead.\r\n\r\n| Language | Extensions | What's recognised |\r\n|---|---|---|\r\n| JavaScript / TypeScript | `.js .jsx .mjs .cjs .ts .tsx .vue .svelte` | classes, interfaces, types, enums, functions, top-level arrows, class and object-literal methods |\r\n| Apex | `.cls .trigger` | classes, inner classes, methods (incl. `@AuraEnabled`, `global`, generic returns), triggers |\r\n| Java | `.java` | classes, interfaces, enums, methods, generic methods with a leading `<T>` |\r\n| C# | `.cs` | classes, interfaces, structs, async and generic methods, virtual members |\r\n| Kotlin | `.kt` | classes, data classes, interfaces, `object`, `fun`, `suspend fun` |\r\n| Swift | `.swift` | classes, structs, enums, protocols, `func`, `static func` |\r\n| Python | `.py` | classes, `def`, `async def`, dunder and decorated methods |\r\n| Go | `.go` | funcs, receiver methods, struct and interface types |\r\n| Rust | `.rs` | structs, enums, traits, `fn`, `pub async fn`, impl methods |\r\n| Ruby | `.rb` | classes, modules, `def`, `def self.x`, `attr_accessor/reader/writer` |\r\n| PHP | `.php` | classes, interfaces, traits, methods, functions |\r\n| Scala | `.scala` | classes, case classes, traits, objects, `def` with modifiers |\r\n| C / C++ / Objective-C | `.c .h .cpp .hpp .cc .m .mm` | classes, structs, enums, free functions (incl. K&R braces, pointer returns), `Foo::bar` out-of-class definitions, ctors/dtors, namespaces, function-like `#define` macros, `typedef struct {…} Name`, `@interface`/`@implementation`/`@protocol` |\r\n\r\n## Performance\r\n\r\nCold index is a full parse; warm is an mtime check per file. Measured on Windows,\r\nNode 24.\r\n\r\n| Repo | Files | Symbols | Cold index | Warm index | Typical query |\r\n|---|---:|---:|---:|---:|---:|\r\n| Salesforce DX org | 56 | 344 | 0.1 s | 15 ms | < 10 ms |\r\n| Java + React app | 356 | 1,713 | 0.42 s | 26 ms | 3–57 ms |\r\n| Synthetic stress | 5,000 | 50,000 | 1.5 s | 0.24 s | 5–22 ms |\r\n\r\nThe index is held in memory and invalidated by the index file's mtime. Without\r\nthat cache every tool call re-read and re-parsed the whole index — about 20 ms of\r\ndead weight per call on the 5,000-file repo, and it grew with the repo.\r\n\r\n`find_references` is the slowest tool at scale because it is a textual scan,\r\nnot an index lookup — but a literal pre-filter now skips the line-split and\r\nper-line regex for any file whose raw source doesn't contain the searched name,\r\nwhich on a typical repo is most of them. Scope with `pathPrefix` to cut the\r\nremaining file reads when you know roughly where to look.\r\n\r\nFile contents are also served from a byte-bounded in-memory LRU (64 MB,\r\nvalidated by mtime+size per hit), so the second scan of a repo — and the\r\nskeleton→read_lines→context sequence agents actually perform on one file —\r\ncosts a `stat()` instead of a read.\r\n\r\n## Memory across sessions\r\n\r\n`memory_save` writes to `<root>/.slimdex/memory.json`, which outlives the\r\nprocess — a fact saved in one chat is readable in the next, by a different\r\nclient, after a restart. Chat and editor share one store only when both point at\r\nthe same `SLIMDEX_ROOT`.\r\n\r\nNothing is captured automatically: the server never sees your conversation, so\r\nthe agent has to decide what's worth keeping. The shipped `instructions` tell it\r\nto read memory first in a new session and to save decisions, constraints and\r\ngotchas as it learns them — but that's guidance to the model, not a guarantee.\r\n\r\n## Known limitations\r\n\r\n- Symbol extraction is **regex-based and heuristic**, not a parser or LSP. It can\r\n  miss unusual declarations, and `find_references` is a **textual** match that may\r\n  include same-named but unrelated identifiers.\r\n- Symbol and outline extraction now run against a **masked** copy of each line,\r\n  with string and comment contents blanked out, so declaration-shaped prose\r\n  inside a template literal is no longer indexed as code. Declarations are also\r\n  **depth-aware**: a `const x = () => …` or `type X = …` counts only at top\r\n  level, because locals inside a function body are not things anyone navigates\r\n  to. Class methods are still indexed at their nesting depth.\r\n- An *inline* Python `#` comment containing a brace can still confuse block\r\n  extraction (`#` is also the JS private-field sigil, so it can't be stripped\r\n  blindly).\r\n- `changed_files` attributes a hunk to the **nearest preceding declaration** —\r\n  right for a normal function body, approximate for code between declarations.\r\n  Treat it as blast radius, not a call graph.\r\n- `search_code` reports an exact total but stops at an internal scan cap on very\r\n  large result sets, printing `N+ (scan cap reached)` rather than a confident\r\n  wrong number.\r\n- Language support is uneven: JS/TS is the best-covered. C-family and Ruby,\r\n  formerly the thinnest, gained dedicated rules (free functions, `Foo::bar`\r\n  definitions, function-like macros, `attr_*`); the remaining soft spots are\r\n  advanced C++ shapes — templates split across lines, operator overloads.\r\n- For LSP-grade precision you'd swap the parser for tree-sitter or a language\r\n  server. `src/parser.ts` is the seam: a `Parser` interface selected by\r\n  `SLIMDEX_PARSER`, with the regex parser as the only implementation that\r\n  ships. A tree-sitter backend would drop in there without touching any tool or\r\n  the index format. It is **not built** — per-language grammars trade away the\r\n  \"installs instantly, runs offline, zero config\" property.\r\n\r\n## Deliberately not built\r\n\r\nIdeas evaluated and rejected, with reasoning — these are design opinions, not\r\nmeasured results:\r\n\r\n- **Symbol-ID dictionaries (`S42` → path)** — MCP has no client-side expansion\r\n  layer, so the model receives an opaque token it must spend another call to\r\n  resolve.\r\n- **Token-budget managers / cost estimators** — `chars/4` estimates are\r\n  unreliable across tokenizers, and auto-compressing on a bad estimate can drop\r\n  data the model needed.\r\n- **Delta / \"already-sent, see response #5\" caching** — after context compaction\r\n  the earlier payload is gone, so the reference resolves to nothing.\r\n- **Embeddings / semantic search** — large dependency footprint; possible future\r\n  optional flag, not a default.\r\n- **A tree-sitter parser backend** — this is the one that would close the\r\n  remaining ~4%, and it was costed rather than hand-waved: `web-tree-sitter` is\r\n  WASM so it needs no native compilation, but the grammars\r\n  (`tree-sitter-wasms`) are **51.7 MB** unpacked against ~4.5 MB for the whole\r\n  current install. Evaluated and declined at 95.9% measured recall, because\r\n  \"installs in a second, runs offline, no configuration\" is the property this\r\n  server exists to have. `src/parser.ts` remains the seam if that calculus ever\r\n  changes — a backend drops in there without touching a tool or the index\r\n  format.\r\n\r\n---\r\n\r\n## Install\r\n\r\nPublished on npm as [`slimdex-mcp`](https://www.npmjs.com/package/slimdex-mcp),\r\nand listed in the [MCP Registry](https://registry.modelcontextprotocol.io) as\r\n`io.github.Siddhukaushik/slimdex-mcp`. Nothing to build — point your client at:\r\n\r\n```bash\r\nnpx slimdex-mcp\r\n```\r\n\r\nOr from source, if you want to hack on it:\r\n\r\n```bash\r\ngit clone https://github.com/Siddhukaushik/slimdex-mcp\r\ncd slimdex-mcp\r\nnpm install\r\nnpm run build      # produces dist/index.js\r\nnpm test           # vitest unit suite\r\n```\r\n\r\nVerify it runs end to end against a repo:\r\n\r\n```bash\r\nnpm run smoke                                # this repo\r\nnode smoke-test.mjs \"C:/path/to/some/repo\"   # any other\r\n```\r\n\r\n### Environment variables\r\n\r\n| Var | Effect |\r\n|-----|--------|\r\n| `SLIMDEX_ROOT` | Repo to index (or pass as the first CLI arg; defaults to cwd) |\r\n| `SLIMDEX_WATCH` | Set to `1` to auto-reindex on file save (native watcher, no deps) |\r\n| `SLIMDEX_PARSER` | Parser backend; only `regex` exists today |\r\n| `SLIMDEX_PRETTY` | Set to `1` to restore the verbose, human-aligned rendering: longer headers and column padding in `search_code`, `find_definition`, `search_symbols`, `find_references`, `repo_map`, `read_lines`, `outline_file`. Terse is the **default** — that padding is context the model pays for in every later turn. `SLIMDEX_TERSE=0` does the same thing. |\r\n| `SLIMDEX_PROFILE` | `lean` advertises 15 tools instead of 29, cutting the tool schemas re-sent on every turn from ~22,300 to ~12,600 chars. The other 14 (`get_context`, `changed_files`, `find_tests`, `dep_graph`, `outline_file`, `search_symbols`, `recap`, `memory_list`, `memory_search`, `memory_delete`, `digest_save`, `digest_get`, `snapshot`, `stats`) still work and are called through `batch` — and the server instructions name them under this profile, so the model is told what is batch-only rather than left to discover it. Default `full`. |\r\n| `SLIMDEX_NO_DEDUPE` | Set to `1` to disable repeat-response suppression (a second identical `read_lines`/`get_file_skeleton`/`outline_file` on an unchanged file answers with a pointer to the earlier call instead of the body; a third identical call re-emits in full). |\r\n\r\n## The persistent cache\r\n\r\nPer repository, Slimdex writes to `<repo>/.slimdex/`:\r\n\r\n- `index.json` — the code index (mtime-invalidated per file, and discarded\r\n  wholesale when the index format version changes, so a stale index built by an\r\n  older extractor is never reused)\r\n- `memory.json` — saved memory facts\r\n- `stats.json` — per-tool usage counters\r\n\r\nThe directory ignores itself: a `*` `.gitignore` is written inside it (the\r\n`node_modules/.cache` trick), so it never shows up in `git status` and you don't\r\nhave to touch the repo's own `.gitignore`. Delete that inner file if you *want*\r\nto commit the cache.\r\n\r\n---\r\n\r\n## Wiring it into MCP clients\r\n\r\nMCP is a shared standard, so the same server should plug into any MCP-capable\r\nclient. The project root is passed via `SLIMDEX_ROOT` (or as the first CLI\r\narg).\r\n\r\n**Only Claude Code and Claude Desktop have actually been run.** The others below\r\nare the standard config shape for each client, written from their documented\r\nformat — they are untested here and may need adjustment.\r\n\r\nSince 1.0.0 the simplest wiring is `npx -y slimdex-mcp` — no clone, no build, and\r\nit stays current. The examples below keep the `node <ABS_PATH>` form for anyone\r\nrunning from source; to use the published package instead, swap\r\n`\"command\": \"node\", \"args\": [\"<ABS_PATH>\"]` for\r\n`\"command\": \"npx\", \"args\": [\"-y\", \"slimdex-mcp\"]`.\r\n\r\nReplace `<ABS_PATH>` with your build output, e.g.\r\n`C:\\path\\to\\slimdex-mcp\\dist\\index.js`, and `<REPO>` with the repo to index.\r\n\r\n**No tuning required.** The savings that matter are on by default in every\r\nclient: memory facts list as previews, responses are terse, an identical re-read\r\nof an unchanged file answers with a pointer instead of the body, and several\r\nsymbol edits go in one call. The env vars below are for opting *out*, or for\r\n`lean` — which trades a further ~8,700 chars/turn against routing a third of the\r\ntools through `batch`, so it is deliberately not the default.\r\n\r\n### Claude Code (CLI) — tested\r\n```bash\r\nclaude mcp add slimdex --env SLIMDEX_ROOT=<REPO> -- npx -y slimdex-mcp\r\n```\r\nFrom source instead: `-- node <ABS_PATH>`.\r\n\r\n### Claude Desktop — tested\r\n`%APPDATA%\\Claude\\claude_desktop_config.json`\r\n```json\r\n{\r\n  \"mcpServers\": {\r\n    \"slimdex\": {\r\n      \"command\": \"npx\",\r\n      \"args\": [\"-y\", \"slimdex-mcp\"],\r\n      \"env\": { \"SLIMDEX_ROOT\": \"<REPO>\" }\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n### Codex CLI — tested\r\n`~/.codex/config.toml`\r\n```toml\r\n[mcp_servers.slimdex]\r\ncommand = 'C:\\Program Files\\nodejs\\node.exe'\r\nargs = ['<ABS_PATH>']\r\nstartup_timeout_sec = 30\r\n```\r\nRegistered globally like this, slimdex attaches to every Codex task and uses\r\nthat task's working directory as the repo root — no `SLIMDEX_ROOT` needed. Codex\r\nlaunches the server with a restricted environment, so give `command` an absolute\r\npath to node rather than relying on `PATH`.\r\n\r\n### Cursor — untested\r\n`.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global)\r\n```json\r\n{\r\n  \"mcpServers\": {\r\n    \"slimdex\": {\r\n      \"command\": \"node\",\r\n      \"args\": [\"<ABS_PATH>\"],\r\n      \"env\": { \"SLIMDEX_ROOT\": \"${workspaceFolder}\" }\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n### Windsurf — tested\r\n`~/.codeium/windsurf/mcp_config.json` — same `mcpServers` shape as Cursor.\r\n\r\n### VS Code (Copilot / MCP) — tested\r\n`.vscode/mcp.json`\r\n```json\r\n{\r\n  \"servers\": {\r\n    \"slimdex\": {\r\n      \"command\": \"node\",\r\n      \"args\": [\"<ABS_PATH>\"],\r\n      \"env\": { \"SLIMDEX_ROOT\": \"${workspaceFolder}\" }\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n### Cline (VS Code extension) — tested\r\nCline settings → MCP Servers → add:\r\n```json\r\n{\r\n  \"slimdex\": {\r\n    \"command\": \"node\",\r\n    \"args\": [\"<ABS_PATH>\"],\r\n    \"env\": { \"SLIMDEX_ROOT\": \"<REPO>\" }\r\n  }\r\n}\r\n```\r\n\r\n### Zed — tested\r\n`settings.json` → `context_servers`\r\n```json\r\n{\r\n  \"context_servers\": {\r\n    \"slimdex\": {\r\n      \"command\": { \"path\": \"node\", \"args\": [\"<ABS_PATH>\"], \"env\": { \"SLIMDEX_ROOT\": \"<REPO>\" } }\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n> For clients that expose the workspace folder (Cursor, VS Code),\r\n> `${workspaceFolder}` keeps Slimdex pointed at the repo you have open.\r\n\r\n## Typical agent workflow\r\n\r\n1. `index_repo` once at the start (faster on subsequent runs), then `brief` to\r\n   pick up where past sessions left off with stale notes already flagged.\r\n2. `repo_map` → get the lay of the land.\r\n3. `outline_file` on a file of interest → pick line ranges.\r\n4. `read_lines` for just those ranges.\r\n5. `find_definition` / `find_references` / `dep_graph` to navigate.\r\n6. `find_tests` before editing a symbol; `replace_symbol` to rewrite one without\r\n   re-sending its old body.\r\n7. `memory_save` decisions and gotchas so the next session starts informed.\r\n\r\n## License\r\n\r\nMIT © 2026 Kael VK Inc. (Business Number 751569161 RC0001) — see [LICENSE](LICENSE).\r\n\r\nProvided as is, with no warranty and no support. If it doesn't build, doesn't\r\nrun, or doesn't work on your setup, that's yours to carry — see the disclaimer\r\nin the license.\r\n","readmeFilename":"README.md"}