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.
- package/README.md +290 -26
- package/docs/anci/v0.1-DRAFT.md +420 -0
- package/docs/scale.md +129 -0
- package/package.json +10 -5
- package/scripts/postinstall.js +413 -0
- package/src/acp/agent.js +5 -5
- package/src/acp/providers/index.js +2 -2
- package/src/agents/leiden.js +11 -17
- package/src/agents/scan-structure.js +1 -1
- package/src/anci/consumer.js +305 -0
- package/src/anci/deserialize.js +160 -0
- package/src/anci/emit.js +85 -0
- package/src/anci/serialize.js +264 -0
- package/src/anci/yaml.js +401 -0
- package/src/bitmap/bitset.js +190 -0
- package/src/bitmap/index.js +121 -0
- package/src/bitmap/sidecar.js +545 -0
- package/src/bitmap/tools.js +310 -0
- package/src/cli/anci.js +237 -0
- package/src/cli/check.js +57 -0
- package/src/cli/index.js +28 -2
- package/src/cli/init.js +297 -65
- package/src/cli/inspect.js +295 -0
- package/src/cli/pr-impact.js +497 -0
- package/src/cli/serve.js +1 -1
- package/src/cli/watch.js +6 -0
- package/src/engine/worker.js +24 -4
- package/src/extractors/imports.js +176 -0
- package/src/extractors/languages/html.js +4 -1
- package/src/extractors/languages/javascript.js +5 -0
- package/src/extractors/languages/prisma.js +4 -1
- package/src/extractors/languages/python.js +5 -1
- package/src/extractors/languages/r.js +4 -1
- package/src/extractors/languages/typescript.js +2 -0
- package/src/extractors/tree-sitter-parser.js +15 -0
- package/src/mcp/change-plan.js +8 -8
- package/src/mcp/diff-parser.js +246 -0
- package/src/mcp/server-v2.js +489 -8
- package/src/mcp/validate.js +304 -0
- package/src/store/config-loader.js +77 -0
- package/src/store/sqlite-store.js +389 -4
- package/src/store/sync-v2.js +472 -97
- 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.
|
|
4
|
-
"description": "Structural intelligence layer for AI coding tools. Indexes your codebase into SQLite — routes, models, import graph, blast radius, domains — and exposes
|
|
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
|
|
37
|
+
"node": ">=18.0.0"
|
|
33
38
|
},
|
|
34
39
|
"author": {
|
|
35
40
|
"name": "Ansh Sonkar",
|