carto-md 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +290 -26
  2. package/docs/anci/v0.1-DRAFT.md +420 -0
  3. package/docs/scale.md +129 -0
  4. package/package.json +10 -5
  5. package/scripts/postinstall.js +413 -0
  6. package/src/acp/agent.js +5 -5
  7. package/src/acp/providers/index.js +2 -2
  8. package/src/agents/leiden.js +11 -17
  9. package/src/agents/scan-structure.js +1 -1
  10. package/src/anci/consumer.js +305 -0
  11. package/src/anci/deserialize.js +160 -0
  12. package/src/anci/emit.js +85 -0
  13. package/src/anci/serialize.js +264 -0
  14. package/src/anci/yaml.js +401 -0
  15. package/src/bitmap/bitset.js +190 -0
  16. package/src/bitmap/index.js +121 -0
  17. package/src/bitmap/sidecar.js +545 -0
  18. package/src/bitmap/tools.js +310 -0
  19. package/src/cli/anci.js +237 -0
  20. package/src/cli/check.js +57 -0
  21. package/src/cli/index.js +28 -2
  22. package/src/cli/init.js +297 -65
  23. package/src/cli/inspect.js +295 -0
  24. package/src/cli/pr-impact.js +497 -0
  25. package/src/cli/serve.js +1 -1
  26. package/src/cli/watch.js +6 -0
  27. package/src/engine/worker.js +24 -4
  28. package/src/extractors/imports.js +176 -0
  29. package/src/extractors/languages/html.js +4 -1
  30. package/src/extractors/languages/javascript.js +5 -0
  31. package/src/extractors/languages/prisma.js +4 -1
  32. package/src/extractors/languages/python.js +5 -1
  33. package/src/extractors/languages/r.js +4 -1
  34. package/src/extractors/languages/typescript.js +2 -0
  35. package/src/extractors/tree-sitter-parser.js +15 -0
  36. package/src/mcp/change-plan.js +8 -8
  37. package/src/mcp/diff-parser.js +246 -0
  38. package/src/mcp/server-v2.js +489 -8
  39. package/src/mcp/validate.js +304 -0
  40. package/src/store/config-loader.js +77 -0
  41. package/src/store/sqlite-store.js +389 -4
  42. package/src/store/sync-v2.js +472 -97
  43. package/BENCHMARK_RESULTS.md +0 -34
@@ -0,0 +1,420 @@
1
+ # ANCI v0.1 DRAFT — Architecturally Normalized Code Index
2
+
3
+ > **Status:** DRAFT (unstable). Do not depend on this format in production
4
+ > consumers without pinning to a specific carto-md version. The wire
5
+ > format may change in any direction up to v1.0.
6
+ >
7
+ > **Spec:** v0.1.0-DRAFT
8
+ > **Reference implementation:** carto-md ≥ 2.0.9
9
+ > **Editor:** [@theanshsonkar](https://github.com/theanshsonkar)
10
+ > **License:** MIT (the spec itself)
11
+ > **Repository:** https://github.com/theanshsonkar/carto
12
+
13
+ ---
14
+
15
+ ## 1. Motivation
16
+
17
+ Every AI coding tool today re-discovers a codebase's architecture from
18
+ scratch on every session. Cursor builds its own embedding index. Cline
19
+ builds its own. Continue builds its own. The work duplicates across
20
+ tools and is lost across sessions.
21
+
22
+ ANCI fills the hole. It is a **static file format** — two files,
23
+ `anci.yaml` and `anci.bin`, sitting at a known location in a repository
24
+ — that describes that codebase's architecture in a form any AI tool can
25
+ read without indexing it itself.
26
+
27
+ ANCI is to codebases what OpenAPI is to REST APIs: a standardized way for
28
+ something to describe itself to consumers it doesn't know about.
29
+
30
+ ## 2. Goals & non-goals
31
+
32
+ ### Goals
33
+
34
+ * **Tool-neutral.** No assumption about which AI tool consumes ANCI.
35
+ * **Local-first.** ANCI files live next to the code, in `.carto/` by
36
+ convention. Never sent over a network unless the user chooses to.
37
+ * **Hybrid representation.** Human-readable header for grep / inspection;
38
+ binary body for fast queries on million-file repos.
39
+ * **Bounded size.** Target ≤ 20 MB total for a 100K-file repo.
40
+ * **Lossless round-trip.** Producer → file → consumer reproduces the
41
+ exact graph, domains, and metadata.
42
+ * **Forward-compatible.** New optional sections may be added in v0.x
43
+ releases. Consumers ignore unknown sections.
44
+
45
+ ### Non-goals
46
+
47
+ * **Not a query language.** Consumers traverse the data structures with
48
+ their own code. ANCI is just the data.
49
+ * **Not a network protocol.** No RPC. No streaming. Static files only.
50
+ * **Not a replacement for source code.** ANCI describes structure
51
+ (graph, domains, routes), not source.
52
+ * **Not yet stable.** v0.1 DRAFT may break in v0.2, v0.3, etc. Stability
53
+ begins at v1.0.
54
+
55
+ ## 3. File layout
56
+
57
+ A repository that ships ANCI places two files in `.carto/`:
58
+
59
+ ```
60
+ .carto/
61
+ ├── anci.yaml # Header. UTF-8 YAML. Required. Human-readable.
62
+ ├── anci.bin # Body. Binary. Required. Machine-readable.
63
+ ```
64
+
65
+ Producers write both. Consumers read either independently:
66
+
67
+ * If a consumer only needs domain names and route counts, parse
68
+ `anci.yaml` and ignore the body.
69
+ * If a consumer needs blast radius or graph traversal, parse the body.
70
+
71
+ The header MUST point to the body's filename and byte length. If they
72
+ disagree, the body is authoritative for graph data and the header is
73
+ authoritative for human metadata.
74
+
75
+ ## 4. Header format (`anci.yaml`)
76
+
77
+ UTF-8, YAML 1.2, **strict subset**:
78
+
79
+ * 2-space indentation
80
+ * `key: value` pairs only (no flow style `{}`/`[]`)
81
+ * All string values double-quoted (no implicit typing)
82
+ * Lists use `- ` prefix
83
+ * No multi-line strings, no anchors, no aliases, no tags
84
+
85
+ A v0.1.0-DRAFT producer MUST emit a header that conforms to this
86
+ schema. A consumer MAY accept other YAML inputs but reference
87
+ implementations only emit/parse the strict subset.
88
+
89
+ ### 4.1 Schema
90
+
91
+ ```yaml
92
+ anci:
93
+ version: "0.1.0-DRAFT" # required, string
94
+ generator: "carto-md@2.0.9" # required, string
95
+ generated_at: "2026-06-07T..." # required, ISO-8601 UTC
96
+ body:
97
+ file: "anci.bin" # required, relative to header
98
+ bytes: 12345 # required, body length on disk
99
+
100
+ project:
101
+ total_files: 7567 # required
102
+ total_routes: 86 # required
103
+ total_models: 12 # required
104
+ total_import_edges: 13420 # required
105
+
106
+ domains: # required, possibly empty
107
+ - name: "AUTH"
108
+ file_count: 42
109
+ route_count: 7
110
+ model_count: 1
111
+
112
+ high_impact: # optional, top N by transitive dep count
113
+ - file: "src/auth/session.ts"
114
+ transitive_dependents: 47
115
+
116
+ routes: # optional
117
+ - method: "POST"
118
+ path: "/auth/login"
119
+ file: "src/auth/login.ts"
120
+ framework: "express"
121
+ handler: "login"
122
+
123
+ models: # optional
124
+ - name: "User"
125
+ kind: "prisma"
126
+ file: "prisma/schema.prisma"
127
+ ```
128
+
129
+ Field names are stable for the v0.x line. Adding new optional fields is
130
+ not a breaking change. Removing or renaming a field IS breaking and
131
+ requires a version bump.
132
+
133
+ ### 4.2 Versioning
134
+
135
+ | Field | Meaning |
136
+ |-----------------|-----------------------------------------------------------|
137
+ | `anci.version` | Spec version. v0.x is unstable. v1.0+ follows semver. |
138
+ | `anci.generator`| Producer identity. Free-form string, conventionally `name@version`. |
139
+
140
+ Consumers MUST refuse to parse a header whose `anci.version` is not in
141
+ their compatibility set. Reference consumer behavior (carto-md):
142
+
143
+ * Accepts: `0.1.x-DRAFT`
144
+ * Rejects: anything else
145
+
146
+ ## 5. Body format (`anci.bin`)
147
+
148
+ Binary, little-endian, length-prefixed sections. Designed so a single
149
+ streaming pass can fully reconstruct all bitmaps, paths, and domain
150
+ mappings.
151
+
152
+ ### 5.1 Header
153
+
154
+ | Offset | Size | Field | Value |
155
+ |-------:|------:|--------------|-----------------------------------------------|
156
+ | 0 | 4 B | magic | `0x49434E41` (`'A','N','C','I'` little-endian)|
157
+ | 4 | 1 B | version | `0x01` |
158
+ | 5 | 3 B | reserved | zero |
159
+ | 8 | 4 B | size_bits | u32 — bit dimension of every bitmap (= max file id + 1) |
160
+
161
+ Total fixed-header length: **12 bytes.**
162
+
163
+ ### 5.2 Sections
164
+
165
+ After the fixed header, sections appear **in this order**. Each section
166
+ begins with a u32 count.
167
+
168
+ #### 5.2.1 `forward` — file → its imports
169
+
170
+ ```
171
+ count_u32
172
+ count × {
173
+ from_file_id_u32
174
+ words_len_u32
175
+ words_u32 × words_len // raw Uint32Array bytes, LE
176
+ }
177
+ ```
178
+
179
+ Bit `j` of the bitmap of `from_file_id` is 1 iff the file with id
180
+ `from_file_id` directly imports the file with id `j`.
181
+
182
+ #### 5.2.2 `reverse` — file → its direct dependents
183
+
184
+ Same record shape as `forward`. Bit `j` set iff file `to_file_id` is
185
+ directly imported by file `j`.
186
+
187
+ #### 5.2.3 `popcount` — pre-computed transitive dependent counts
188
+
189
+ ```
190
+ count_u32
191
+ count × {
192
+ file_id_u32
193
+ count_u32 // # of distinct dependents reachable in ≤ 5 hops
194
+ }
195
+ ```
196
+
197
+ Sorted DESC by count. Lets `getHighImpactFiles(n)` answer in O(1).
198
+ Producers MUST clamp BFS to 5 hops to match the carto-md reference.
199
+
200
+ #### 5.2.4 `paths` — file id → path string
201
+
202
+ ```
203
+ count_u32
204
+ count × {
205
+ file_id_u32
206
+ path_len_u32
207
+ path_bytes (UTF-8, no NUL terminator)
208
+ }
209
+ ```
210
+
211
+ Paths are project-relative POSIX paths (forward slashes, no leading `./`).
212
+
213
+ #### 5.2.5 `file_domain` — file id → domain id
214
+
215
+ ```
216
+ count_u32
217
+ count × {
218
+ file_id_u32
219
+ domain_id_u32
220
+ }
221
+ ```
222
+
223
+ Files absent from this section have no domain assignment. A file MAY
224
+ have at most one domain.
225
+
226
+ #### 5.2.6 `domain_names` — domain id → human name
227
+
228
+ ```
229
+ count_u32
230
+ count × {
231
+ domain_id_u32
232
+ name_len_u32
233
+ name_bytes (UTF-8)
234
+ }
235
+ ```
236
+
237
+ Domain names are uppercase by convention (`AUTH`, `PAYMENTS`,
238
+ `DATABASE`) but the spec does not constrain their contents.
239
+
240
+ ### 5.3 Total layout (concrete byte map)
241
+
242
+ ```
243
+ [ 12 B fixed header ]
244
+ [ forward section ]
245
+ [ reverse section ]
246
+ [ popcount section ]
247
+ [ paths section ]
248
+ [ file_domain section ]
249
+ [ domain_names section]
250
+ EOF
251
+ ```
252
+
253
+ A consumer that knows the spec version can stream-parse top to bottom.
254
+ There is no out-of-line index — the format is designed for one
255
+ sequential read.
256
+
257
+ ## 6. Generation algorithm (reference)
258
+
259
+ Producers SHOULD compute the body as follows:
260
+
261
+ 1. Assign each file a stable, dense integer id.
262
+ 2. For each file, build a bitmap of its forward imports (resolved
263
+ imports only; unresolved imports are excluded).
264
+ 3. Reverse the forward bitmaps into `reverse`.
265
+ 4. For each file with ≥ 1 dependent, run a 5-hop BFS over `reverse`
266
+ using the same self-loop guard as `bitmap/sidecar.js`:
267
+ a. Seed `visited` with direct dependents.
268
+ b. Expand frontier by one hop, mask `visited`, add result to
269
+ `visited`. Repeat to depth 5 or until frontier is empty.
270
+ c. Clear the seed bit (cycles must not count the file itself).
271
+ d. Record `popcount(visited)` if > 0.
272
+ 5. Sort `popcount` records DESC.
273
+ 6. Write sections in the order specified in §5.2.
274
+
275
+ The carto-md reference implementation lives in `src/anci/serialize.js`.
276
+
277
+ ## 7. Consumer responsibilities
278
+
279
+ A consumer MUST:
280
+
281
+ * Validate magic bytes and version before reading any section.
282
+ * Refuse to parse if `size_bits` differs from the bitmap dimension
283
+ derived from any section's `words_len`.
284
+ * Treat unknown sections (in future v0.x releases) as opaque and skip
285
+ them. The reference implementation enforces strict order today, but
286
+ consumers SHOULD be permissive on order to ease forward compatibility.
287
+
288
+ A consumer SHOULD:
289
+
290
+ * Cache the parsed body in memory rather than re-parsing on every
291
+ query. The format is designed for fast load, not fast random seek.
292
+ * Cross-validate the header's `body.bytes` against the actual file
293
+ size and warn (not fail) on mismatch.
294
+
295
+ ## 8. Versioning & deprecation policy
296
+
297
+ ### v0.1.0-DRAFT (this spec)
298
+
299
+ * Format may change in any direction up to v1.0.
300
+ * Consumers pin to specific producer versions.
301
+ * Reference implementation is carto-md ≥ 2.0.9.
302
+ * No backward compatibility guarantees.
303
+
304
+ ### Future v0.x (e.g. v0.2)
305
+
306
+ * MAY add optional header fields.
307
+ * MAY add optional body sections (appended after `domain_names`).
308
+ * MUST NOT remove or rename existing fields.
309
+ * MUST bump `anci.version` to a new minor.
310
+ * Reference consumer accepts the latest v0.x and the version it shipped
311
+ against.
312
+
313
+ ### v1.0 stability
314
+
315
+ When the format is judged ready (reference partner integrations exist,
316
+ plus 6 months of v0.x stability), `anci.version` graduates to `1.0.0`
317
+ and standard semver applies thereafter:
318
+
319
+ * MAJOR: incompatible breaking change.
320
+ * MINOR: backward-compatible addition.
321
+ * PATCH: clarification only.
322
+
323
+ A v1.x consumer MUST accept all v1.x bodies. v0.x compatibility at v1.0
324
+ is not guaranteed — producers re-emit at the v1.0 schema.
325
+
326
+ ## 9. Security considerations
327
+
328
+ ANCI is a passive description of code structure. It contains:
329
+
330
+ * File paths (project-relative).
331
+ * Domain names.
332
+ * Route paths and HTTP methods.
333
+ * Model names and kinds.
334
+ * Import graph as integer bitmaps.
335
+
336
+ ANCI does **not** contain source code, secrets, env values, or
337
+ user-identifying information. Producers MUST NOT include source content
338
+ in any field.
339
+
340
+ A repository that publishes its `.carto/anci.{yaml,bin}` to a remote
341
+ location is exposing its architecture. This is the same disclosure
342
+ level as making the directory tree public. Producers SHOULD respect
343
+ `.cartoignore` and SHOULD NOT include paths matching its patterns.
344
+
345
+ ## 10. Why YAML + binary (and not just YAML, or just binary)
346
+
347
+ | Property | YAML alone | Binary alone | Hybrid |
348
+ |------------------------|:----------:|:------------:|:------:|
349
+ | Human-readable | ✅ | ❌ | ✅ |
350
+ | Grep-able from CI | ✅ | ❌ | ✅ |
351
+ | Compact at 100K files | ❌ (50–500 MB) | ✅ | ✅ (5–20 MB) |
352
+ | Fast graph queries | ❌ | ✅ | ✅ |
353
+ | Easy partial parse | ✅ | ⚠️ | ✅ |
354
+
355
+ The split mirrors the structure of the data: metadata is small and
356
+ varied (good for YAML); the graph is large and uniform (good for
357
+ binary).
358
+
359
+ ## 11. Why these specific bitmap sections
360
+
361
+ The body ships exactly the data needed to answer the four MCP tools
362
+ that drive most AI-tool queries:
363
+
364
+ * `getBlastRadius(file)` → walk `reverse` BFS-style.
365
+ * `simulateChangeImpact(files)` → OR-aggregate `reverse` bitmaps.
366
+ * `getHighImpactFiles(n)` → take first N from `popcount`.
367
+ * `getDomainsList()` / domain membership → header + `file_domain` +
368
+ `domain_names`.
369
+
370
+ Everything else (cross-domain detection, similarity, change-plan
371
+ ranking) can be derived from these primitives. The format keeps the
372
+ ship surface small.
373
+
374
+ ## 12. Compatibility with carto-md `bitmap.bin`
375
+
376
+ ANCI v0.1 and carto-md's internal `bitmap.bin` (magic `0x54524243`
377
+ "CBRT") share record-level encoding but are **distinct file formats
378
+ with distinct magics**. Reasons:
379
+
380
+ * `bitmap.bin` is private cache; `anci.bin` is public export.
381
+ * `bitmap.bin` includes `crossForward` and `domainBitmaps` (internal
382
+ query optimizations); ANCI omits them. Consumers can re-derive both
383
+ in O(N) on load.
384
+ * `bitmap.bin` may evolve at carto-md's pace; ANCI evolves only at
385
+ spec-revision pace.
386
+
387
+ A consumer MUST NOT attempt to load `bitmap.bin` as ANCI or vice versa.
388
+
389
+ ## 13. Reference vs. interoperability tests
390
+
391
+ The carto-md reference implementation includes a roundtrip test suite
392
+ (`test/test.js` → `ANCI roundtrip`). Other implementations are
393
+ encouraged to:
394
+
395
+ * Run carto-md's reference suite against their own
396
+ serialize/deserialize code.
397
+ * Publish their own conformance tests for the spec to consume.
398
+
399
+ The DRAFT spec deliberately ships **without** an independent
400
+ conformance harness. v1.0 will require one before formalization.
401
+
402
+ ## 14. Open questions (RFC-stage feedback welcome)
403
+
404
+ * **Should the body be Roaring-compressed instead of dense
405
+ Uint32Array?** Roaring is smaller on sparse repos but adds a
406
+ dependency. v0.1 ships dense; v1.0 may add an opt-in compressed
407
+ encoding.
408
+ * **Should env vars be in the header?** Currently no — they leak
409
+ configuration surface. Open question for partners.
410
+ * **Should source language be part of `paths`?** Currently no — language
411
+ is derivable from extension. Open if consumers ask.
412
+ * **Cross-repo references?** Out of scope for v0.1. Tier 3 work.
413
+
414
+ ## 15. Acknowledgements
415
+
416
+ Inspired by OpenAPI's standardization play (2010-2015), Git's pack
417
+ reachability bitmaps (2013), and Lucene's Roaring posting lists (2014).
418
+
419
+ The spec is iterated alongside carto-md. File issues at
420
+ https://github.com/theanshsonkar/carto for proposed changes.
package/docs/scale.md ADDED
@@ -0,0 +1,129 @@
1
+ # Carto at scale
2
+
3
+ > Empirical scale data for the bitmap engine — synthetic stress sweep + every real-world repo in the Carto corpus. Reproducer commands at the bottom.
4
+
5
+ The bitmap-backed graph engine is designed to handle 100K-1M files cleanly. This page is the empirical receipt. It captures both the **dense-synth stress sweep** (deterministic worst-case fan-out distribution) and the **real-world corpus** (4 of the 7 larger repos, the largest open-source codebases under MIT/permissive licenses we routinely test against).
6
+
7
+ ## What's measured
8
+
9
+ For each run we capture:
10
+
11
+ - **Init time** — full `runSyncV2` from a wiped `.carto/` directory.
12
+ - **Sync time** — second `runSyncV2` immediately after, no files changed (mtime+hash skip path).
13
+ - **DB size** — `.carto/carto.db` bytes on disk.
14
+ - **bitmap.bin size** — the sidecar bytes on disk.
15
+ - **Sidecar (RAM)** — sum of all bitmap word-array byte lengths held in memory.
16
+ - **Peak RSS** — process resident memory after init and during query workloads.
17
+ - **MCP query p50 / p99** — 1000 calls each (50-call warmup) for the 5 production bitmap tools (`blastRadius`, `crossDomain`, `highImpactFiles`, `similarPatterns`) plus `simulate_change_impact`. Random query inputs are seeded so identical `--seed` produces identical workloads.
18
+
19
+ The query stage opens SQLite **read-only** — invariant honored. The bitmap-as-derived-disposable invariant is also honored: the runner only reads from `bitmap.bin`, never writes.
20
+
21
+ All numbers below were captured on the maintainer dev box: Apple silicon, Node 20.20, macOS, NVMe SSD. Recapture via the commands at the bottom.
22
+
23
+ ## Synthetic stress sweep (dense fan-out)
24
+
25
+ Source: `bench/scale-test/`. Generates a deterministic-for-seed TypeScript repo with a worst-case-shaped import graph: 5 domain-like top-level dirs (`auth`/`payments`/`database`/`events`/`core`), Pareto(α=2) fan-out clipped to [1,8], 75% same-domain / 25% cross-domain, hotspot-biased targets so the first ~10% of file ids accumulate most in-edges. Imports always reference an earlier file id (acyclic).
26
+
27
+ Edge density ≈ 1.5 per file. **This is denser than every real-world repo in the corpus** — real codebases have many leaf files with zero imports, so the synth deliberately exercises the worst case for the bitmap layer.
28
+
29
+ | Files | Edges | Init | Sync | DB | bitmap.bin | Sidecar (RAM) | Peak RSS |
30
+ |------:|------:|-----:|-----:|---:|-----------:|--------------:|---------:|
31
+ | 1,000 | 1,392 | 533ms | 70ms | 756 KB | 222 KB | 186 KB | 149 MB |
32
+ | 10,000 | 14,876 | 3.28s | 627ms | 6.5 MB | 17.0 MB | 18.1 MB | 350 MB |
33
+ | 50,000 | 76,145 | 1.1m | 5.14s | 36.9 MB | 414.9 MB | 488.4 MB | 1.35 GB |
34
+
35
+ | Files | blastRadius p50/p99 | crossDomain p50/p99 | highImpactFiles p50/p99 | similarPatterns p50/p99 | simulate_change_impact p50/p99 |
36
+ |------:|---------------------|---------------------|------------------------|------------------------|--------------------------------|
37
+ | 1,000 | 1.6µs / 24.4µs | 19.5µs / 25.0µs | 709ns / 4.3µs | 276µs / 484µs | 7.0µs / 78.8µs |
38
+ | 10,000 | 6.6µs / 67.7µs | 705µs / 914µs | 750ns / 1.4µs | 17.0ms / 19.3ms | 11.6µs / 113µs |
39
+ | 50,000 | 22.4µs / 472µs | 28.1ms / 28.9ms | 750ns / 1.3µs | 462ms / 798ms | 49.9µs / 455µs |
40
+
41
+ ### What 50K reveals
42
+
43
+ Three distinct failure modes show up at the dense-synth 50K row, **all of them addressable by a Roaring-bitmap upgrade on the roadmap**:
44
+
45
+ 1. **Storage scales linearly with N** — every file gets its own forward + reverse bitmap of size ~`N/8` bytes. At N=50K bitmap.bin is 415 MB; at N=100K linear extrapolation puts it at ~1.7 GB. The current pure-Node `Uint32Array` bitset (`src/bitmap/bitset.js`) trades storage efficiency for raw query speed and zero native deps. Roaring would cut this 5-50× depending on density.
46
+ 2. **`crossDomain` p50 hits 28 ms** — linear sweep over the `crossForward` Map. Fixable with a precomputed cross-domain edge bitmap that's OR-aggregated once at build time, not iterated per query.
47
+ 3. **`similarPatterns` p50 hits 462 ms** — Jaccard over import sets at scale costs O(candidates × set-intersection). The MCP layer calls this with `top_k=5` so user-facing latency is well under the raw bench number, but the absolute cost is real.
48
+
49
+ Three things stay healthy at 50K: `blastRadius` p50 22µs, `highImpactFiles` p50 750ns (popcount index is O(1) array slice), `simulate_change_impact` p50 50µs.
50
+
51
+ ### Pending (not yet captured on this box)
52
+
53
+ | Files | Notes |
54
+ |------:|-------|
55
+ | 100,000 | Stress-test for the dense-bitset implementation. ~3-5 min walltime expected. Worth running before any Roaring work to lock in the regression baseline. |
56
+ | 500,000 | Practical ceiling for the dense-bitset; expect bitmap.bin > 4 GB, possibly hitting OOM on 16 GB-RAM dev boxes. |
57
+ | 1,000,000 | Roaring-bitmap upgrade target. Expect dense-bitset OOM. |
58
+
59
+ Reproducible via `npm run bench:scale -- --size 100000 --keep --out /tmp/carto-100k`.
60
+
61
+ ## Real-world corpus
62
+
63
+ These are the 4 largest repos in `~/carto-test-repos`, all already indexed by `~/carto-test-repos/run-bench.sh` (the bench harness baseline). The query latency below was measured against those existing indexes via `--queries-only` so this measurement does not perturb the indexes used by `node test/accuracy-corpus.js`.
64
+
65
+ | Repo | Indexed files | Edges | DB | bitmap.bin | Sidecar (RAM) |
66
+ |------|--------------:|------:|---:|-----------:|--------------:|
67
+ | [cal.com](https://github.com/calcom/cal.com) | 4,351 | 3,478 | 3.1 MB | 1.9 MB | 2.0 MB |
68
+ | [nextjs](https://github.com/vercel/next.js) | 6,193 | 7,930 | 15.0 MB | 4.4 MB | 4.3 MB |
69
+ | [supabase](https://github.com/supabase/supabase) | 6,330 | 5,189 | 4.8 MB | 4.4 MB | 4.4 MB |
70
+ | [vscode](https://github.com/microsoft/vscode) | 7,567 | 13,335 | 14.3 MB | 4.1 MB | 4.5 MB |
71
+
72
+ | Repo | blastRadius p50/p99 | crossDomain p50/p99 | highImpactFiles p50/p99 | similarPatterns p50/p99 | simulate_change_impact p50/p99 |
73
+ |------|---------------------|---------------------|------------------------|------------------------|--------------------------------|
74
+ | cal.com | 4.3µs / 51µs | 148µs / 279µs | 667ns / 1.0µs | 1.0µs / 1.99ms | 10.3µs / 71.0µs |
75
+ | nextjs | 5.0µs / 155µs | 196µs / 284µs | 750ns / 1.3µs | 1.2µs / 3.01ms | 28.9µs / 211µs |
76
+ | supabase | 5.5µs / 47.9µs | 201µs / 260µs | 708ns / 1.0µs | 1.5µs / 3.19ms | 10.8µs / 65.9µs |
77
+ | vscode | 2.7µs / 428µs | 1.23ms / 1.47ms | 750ns / 1.7µs | 834ns / 4.03ms | 19.3µs / 637µs |
78
+
79
+ ### Real-world vs dense-synth
80
+
81
+ The 7,567-file vscode row vs the 10,000-file synth row tells the most important story:
82
+
83
+ - vscode `similarPatterns` p50 is **834ns**; synth-10K is **17ms**. Real codebases have far sparser per-file import sets — most files import 0-3 things, a handful import many. Synth is a deliberate worst case (Pareto fan-out always ≥1, often 4-8). Real Jaccard-over-import-sets fits in cache; synth doesn't.
84
+ - vscode `crossDomain` p50 is **1.23ms**; synth-10K is **704µs**. Cross-domain edges scale with edge density; synth is denser. Both are sub-2ms at any sensible scale.
85
+ - vscode `bitmap.bin` is **4.1 MB** at 7,567 files; synth-10K is **17 MB** at 10,000 files. ~4× density difference, reflected directly in storage.
86
+
87
+ The takeaway: **dense-synth identifies the failure modes; real-world data sets the actual user expectations.** Both are needed.
88
+
89
+ ## Linux kernel and Chromium
90
+
91
+ Block 1.B in PEAK §10 explicitly calls out the Linux kernel and Chromium as scale targets. Neither is in `~/carto-test-repos` today (multi-GB clones, gated on the maintainer's disk budget). The `bench/scale-test/real-world.js` driver lands here so capturing those rows is one command once a clone exists:
92
+
93
+ ```bash
94
+ git clone --depth=1 https://github.com/torvalds/linux ~/clones/linux
95
+ node bench/scale-test/real-world.js --repo ~/clones/linux --name linux-kernel
96
+ ```
97
+
98
+ ```bash
99
+ # Chromium follows the official `fetch` flow; expect a 30+ GB clone.
100
+ node bench/scale-test/real-world.js --repo ~/clones/chromium/src --name chromium
101
+ ```
102
+
103
+ These rows are intentionally not yet captured. The 50K dense-synth + 7,567-file vscode + 6,330-file supabase rows above already establish the scaling profile of the current implementation; whether kernel/Chromium clear or fail is *known* in the bitmap-storage sense (linear in N, hits the GB scale at 100K+ files in the worst case). Capturing them on the maintainer box is a confirm-the-known activity, not a discover-the-unknown one.
104
+
105
+ ## Reproducing this page
106
+
107
+ Every number above is mechanically reproducible. The exact commands:
108
+
109
+ ```bash
110
+ # Synth sweep (deterministic, dense-fan-out worst case)
111
+ npm run bench:scale -- --size 1000
112
+ npm run bench:scale -- --size 10000
113
+ npm run bench:scale -- --size 50000
114
+
115
+ # Real-world corpus (uses existing indexes; doesn't re-index)
116
+ node bench/scale-test/real-world.js --repo ~/carto-test-repos/tier-b/vscode --name vscode --queries-only
117
+ node bench/scale-test/real-world.js --repo ~/carto-test-repos/tier-b/supabase --name supabase --queries-only
118
+ node bench/scale-test/real-world.js --repo ~/carto-test-repos/tier-b/nextjs --name nextjs --queries-only
119
+ node bench/scale-test/real-world.js --repo ~/carto-test-repos/tier-b/cal.com --name cal.com --queries-only
120
+
121
+ # Larger (maintainer-machine work)
122
+ npm run bench:scale -- --size 100000 --keep --out /tmp/carto-100k
123
+ ```
124
+
125
+ Each run writes `bench/scale-test/results/<tag>-<ISO-timestamp>.json` and refreshes `bench/scale-test/REPORT.md`. The aggregator keeps the latest run per (kind, label) so re-running a row replaces the old number.
126
+
127
+ ---
128
+
129
+ _See `bench/scale-test/README.md` for full harness documentation._
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "carto-md",
3
- "version": "2.0.7",
4
- "description": "Structural intelligence layer for AI coding tools. Indexes your codebase into SQLite — routes, models, import graph, blast radius, domains — and exposes 16 MCP tools for Kiro, Cursor, and Claude.",
3
+ "version": "2.0.9",
4
+ "description": "Structural intelligence layer for AI coding tools. Indexes your codebase into SQLite — routes, models, import graph, blast radius, domains — and exposes 22 MCP tools for Kiro, Cursor, and Claude.",
5
5
  "bin": {
6
6
  "carto": "src/cli/index.js"
7
7
  },
@@ -10,7 +10,10 @@
10
10
  "test": "node test/test.js",
11
11
  "test:correctness": "node test/correctness.js",
12
12
  "test:benchmark": "node test/benchmark.js",
13
- "test:bench-ci": "node test/bench-ci.js"
13
+ "test:bench-ci": "node test/bench-ci.js",
14
+ "postinstall": "node scripts/postinstall.js",
15
+ "bench:bitmap": "node bench/bitmap-validation/index.js",
16
+ "bench:scale": "node bench/scale-test/index.js"
14
17
  },
15
18
  "dependencies": {
16
19
  "@agentclientprotocol/sdk": "^0.22.1",
@@ -18,7 +21,9 @@
18
21
  "@modelcontextprotocol/sdk": "^1.0.0",
19
22
  "better-sqlite3": "11.7.0",
20
23
  "chokidar": "^3.6.0",
21
- "tree-sitter": "0.25.0",
24
+ "tree-sitter": "0.25.0"
25
+ },
26
+ "optionalDependencies": {
22
27
  "tree-sitter-c-sharp": "0.21.3",
23
28
  "tree-sitter-cpp": "0.23.4",
24
29
  "tree-sitter-go": "0.25.0",
@@ -29,7 +34,7 @@
29
34
  "tree-sitter-typescript": "0.23.2"
30
35
  },
31
36
  "engines": {
32
- "node": ">=18.0.0 <26.0.0"
37
+ "node": ">=18.0.0"
33
38
  },
34
39
  "author": {
35
40
  "name": "Ansh Sonkar",