@maxgfr/codeindex 2.12.0 → 2.14.0

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 CHANGED
@@ -8,9 +8,8 @@ import resolution, a typed cross-file link-graph, and graph analytics — shippe
8
8
  as a single zero-dependency `engine.mjs` that consumer tools **vendor** (copy
9
9
  into their repo) instead of installing.
10
10
 
11
- Extracted from [ultraindex](https://github.com/maxgfr/ultraindex) 5.1.0's core,
12
- and consumed by the ultra* skill family (ultraindex, ultradoc, ultrasec,
13
- ultraeval, reconstruct, construct, ultra11y).
11
+ Designed for downstream tools — agent skills, CLIs, CI gates — that vendor the
12
+ engine as a single file instead of taking an npm dependency.
14
13
 
15
14
  ## What it does
16
15
 
@@ -44,7 +43,28 @@ const { scan, graph, symbols } = buildIndexArtifacts("/path/to/repo");
44
43
 
45
44
  The AST tier is optional: without a `grammars/` directory next to the bundle
46
45
  the engine silently uses its regex tier. Only tools that want AST precision
47
- (e.g. ultraindex) also vendor `scripts/grammars/` (~17 MiB of wasm).
46
+ also vendor `scripts/grammars/` (~17 MiB of wasm).
47
+
48
+ ### Slim grammars (pull instead of vendor)
49
+
50
+ Consumers that want AST precision but not the ~17 MiB of vendored wasm can
51
+ `codeindex grammars pull` the grammars once into a shared, per-machine cache
52
+ (`<XDG_CACHE_HOME|~/.cache>/codeindex/grammars/<ENGINE_VERSION>`) instead:
53
+
54
+ ```sh
55
+ codeindex grammars status # active tier (adjacent/env/cache/none) + whether a pull is needed
56
+ codeindex grammars pull # fetch the per-release grammars asset, sha256-verified, into the cache
57
+ ```
58
+
59
+ Resolution is **adjacent > env > cache > regex**: a bundle-adjacent `grammars/`
60
+ still wins if present (offline setups are untouched), then
61
+ `CODEINDEX_GRAMMARS_DIR`, then the pulled cache. `pull` fetches the official
62
+ `grammars-<version>.tar.gz` release asset (its `.sha256` sidecar is verified
63
+ before anything is written) and extracts it atomically; the same wasm bytes
64
+ produce **byte-identical** AST extraction from the cache as from a vendored dir.
65
+ It is fully **offline-safe**: with no grammars resolvable anywhere — and after a
66
+ failed or absent pull — the engine silently falls back to the regex tier exactly
67
+ as it does today; a pull never throws into indexing.
48
68
 
49
69
  ## Use from npm
50
70
 
@@ -62,8 +82,8 @@ const scan = scanRepo("/path/to/repo");
62
82
  ```
63
83
 
64
84
  The CLI ships in the same package — see **Use as a CLI** below for the global
65
- install command. Skills should still prefer vendoring: it keeps their own
66
- bundle single-file and pinned to an exact commit without an npm dependency.
85
+ install command. Consumer tools should still prefer vendoring: it keeps their
86
+ own bundle single-file and pinned to an exact commit without an npm dependency.
67
87
 
68
88
  ## Use as a CLI
69
89
 
@@ -129,9 +149,13 @@ and the `embeddings.bin` artifact are byte-identical across builds and platforms
129
149
  It is **opt-in by asset**: with no model on disk the engine silently stays
130
150
  lexical, and `--semantic` without a model returns lexical results on **exit 0**
131
151
  (a stderr note only). Models are **never** shipped in the package; a model is
132
- resolved from `CODEINDEX_EMBED_DIR` or `<repo>/.codeindex/models/`.
152
+ resolved from `CODEINDEX_EMBED_DIR` or `<repo>/.codeindex/models/`. Getting one
153
+ is zero-config: `codeindex embed pull` fetches the official `embed-model-v1`
154
+ release asset, sha256-verified before anything is written.
133
155
 
134
156
  ```sh
157
+ codeindex embed pull --repo . # fetch the official model asset into
158
+ # CODEINDEX_EMBED_DIR (or <repo>/.codeindex/models/); sha256-verified
135
159
  codeindex embed status --repo . # effective mode + reachability (JSON)
136
160
  codeindex embed build --repo . --out .codeindex # write embeddings.bin
137
161
  codeindex search "http client retry" --repo . --semantic
@@ -182,8 +206,8 @@ into their own CLIs); `cli.mjs` is the thin standalone CLI/MCP wrapper.
182
206
  ## Versioning
183
207
 
184
208
  - `ENGINE_VERSION` — the release tag, embedded greppably in the bundle.
185
- - `SCHEMA_VERSION` — the `graph.json`/`symbols.json` shape (continues
186
- ultraindex's lineage; currently 4). Consumers reject mismatched artifacts.
209
+ - `SCHEMA_VERSION` — the `graph.json`/`symbols.json` shape (currently 4).
210
+ Consumers reject mismatched artifacts.
187
211
  - `EXTRACTOR_VERSION` — the extraction output shape; incremental caches keyed
188
212
  on it are discarded wholesale when it bumps.
189
213
 
@@ -214,8 +238,8 @@ pnpm check:build # proves the committed bundle is byte-reproducible
214
238
  pnpm test:e2e # opt-in: pinned real-repo builds with ratchets
215
239
  ```
216
240
 
217
- The compat suite pins the exact bytes ultraindex 5.1.0 produced for the
218
- `mini-repo` fixture — the proof that extraction was lossless.
241
+ The compat suite pins golden bytes for the `mini-repo` fixture the proof
242
+ that extraction stays lossless across releases.
219
243
 
220
244
  ## License
221
245
 
package/docs/MIGRATION.md CHANGED
@@ -1,4 +1,4 @@
1
- # Migrating a skill onto the codeindex engine
1
+ # Migrating a consumer tool onto the codeindex engine
2
2
 
3
3
  The engine ships as two files, released together at every tag:
4
4
 
@@ -6,12 +6,12 @@ The engine ships as two files, released together at every tag:
6
6
  - `scripts/engine.d.mts` — TypeScript declarations for the bundle
7
7
 
8
8
  Consumers **vendor** them (commit a copy) — never an npm dependency — so every
9
- skill stays standalone-installable.
9
+ consumer stays standalone-installable.
10
10
 
11
11
  ## Vendoring steps
12
12
 
13
- 1. Copy `scripts/sync-engine.mjs` (reference implementation lives in the
14
- ultraeval repo) into your repo and run:
13
+ 1. Add a small `scripts/sync-engine.mjs` to your repo (a ~50-line fetch script
14
+ with the behavior described below) and run:
15
15
 
16
16
  ```sh
17
17
  node scripts/sync-engine.mjs --ref v1.0.0
@@ -40,8 +40,7 @@ skill stays standalone-installable.
40
40
 
41
41
  The AST tier is optional: without a `grammars/` directory next to the vendored
42
42
  bundle the engine uses its regex tier (15 languages). Only vendor
43
- `scripts/grammars/` (~17 MiB wasm) if you need AST-exact symbols (ultraindex
44
- does; nobody else should).
43
+ `scripts/grammars/` (~17 MiB wasm) if you need AST-exact symbols.
45
44
 
46
45
  ## Version constants
47
46
 
@@ -53,7 +52,7 @@ does; nobody else should).
53
52
 
54
53
  `buildGraph(...)` / `buildIndexArtifacts(...)` accept
55
54
  `meta: { version, schemaVersion }` so a consumer stamps its own identity into
56
- artifacts it persists (ultraindex does this to keep its graph.json lineage).
55
+ artifacts it persists and keeps its own `graph.json` lineage.
57
56
 
58
57
  ## v2.9.0 — `search` trigram fuzzy fallback
59
58
 
@@ -93,39 +92,159 @@ embeddings live in a separate `embeddings.bin` sidecar keyed by a dedicated
93
92
  `--semantic` without a model degrades to lexical results on **exit 0** (a stderr
94
93
  note only) — so wiring it on is safe before an asset exists.
95
94
 
96
- ## Per-skill mapping (what to replace with what)
95
+ ## v2.12.0 — two return-shape changes (check your call sites)
97
96
 
98
- | Skill | Replace | With (engine export) |
99
- |---|---|---|
100
- | **ultraeval** | `walkFiles`, `SKIP_DIRS` | `scanRepo` (gitignore on by default) |
101
- | | `importsOf` + `resolveImport` (JS/TS+py regex) | `FileRecord.refs` (kind `import`) + `buildResolveContext`/`resolveImport` |
102
- | | `gitChurn`, `changedFiles` | `gitChurn`, `changedSince` |
103
- | | keeps | cycle DFS, hotspot scoring, todos/maxIndent, ANALYSIS.md rendering |
104
- | **construct** | walker clone + language histogram + test count | `scanRepo` (`.languages`) + `isTestPath` |
105
- | **ultraindex** | the whole core (walk/scan/extract/resolve/modules/graph/calls/analytics/renderers) | vendored engine + shim `src/engine.ts` re-exporting it; pass `meta: { version: VERSION, schemaVersion: SCHEMA_VERSION }` to keep graph.json byte-lineage |
106
- | | keeps | store/manifest, encyclopedia, merge/entries, find/ask, check/verify, embeddings, orchestrate |
107
- | **ultradoc** | walker, `EXT_LANG` map, symbol `RULES` + `applyExportLists` | `scanRepo`, `extToLang`/`languageOf`, `extractSymbols`/`buildSymbolIndex` |
108
- | | `PKG_MANIFESTS` workspace probing | `detectWorkspaces` |
109
- | | `rgSearch` + JS fallback | `grepRepo` |
110
- | | keeps | issues/PRs/SO/web retrieval, citations, DOC.md |
111
- | **ultra11y** | walker + glob scoping | `walk`/`scanRepo` with `include`/`exclude` |
112
- | | tsconfig path-alias resolution | `buildResolveContext` + `resolveImport` |
113
- | | keeps | its JSX component graph and every WCAG check |
114
- | **reconstruct** | walker + gitignore parser + `categorize` | `scanRepo`, `categorize` |
115
- | | `EXT_LANGUAGE` | `extToLang` |
116
- | | workspace detection + dep graph + cycle + topo | `detectWorkspaces` (packages/dependsOn/cycle/topoOrder) |
117
- | | `resolveModule` | `resolveImport` |
118
- | | keeps | framework adapters (routes), data-model inference, PRD rendering |
119
- | **ultrasec** | walker + gitignore + scope + symlink guard | `scanRepo` (`scope`, `gitignore` — its own semantics, ported here) |
120
- | | per-language defs/imports/calls extraction | `extractCode` (`symbols`/`refs`/`calls`) |
121
- | | `resolveImport` | `resolveImport` |
122
- | | `buildGraph` (import+call edges, symbolDefs) | `buildGraph` + `resolveCallEdges` |
123
- | | `callersBySymbol`, `enclosingSymbol` (raw-recall taint-BFS input) | `buildRawCallerIndex` (issue #8) every name-matched call site keyed by the raw callee name, no def resolution or gating, `enclosingSymbol` computed per site. `buildCallerIndex` is **NOT** a substitute here: it is def-resolved and gated (language-family filter, JS/TS import gate, same-file self-declaration skip) and will silently drop sites a recall consumer needs. Both are bounded by `FileRecord.calls`'s per-file 512-call cap (dedup by name+line) — a file with more raw call sites than that loses sites upstream of either function. |
124
- | | keeps | taint source→sink enumeration, external scanners, EPSS/KEV/CVSS, SARIF |
97
+ Not additive: two public return shapes changed in this release without a
98
+ compat flag, so a consumer re-pinning across v2.12.0 must check both call
99
+ sites. Artifact schemas are untouched (`SCHEMA_VERSION` / `EMBED_VERSION`
100
+ unchanged) this is API shape only.
101
+
102
+ - `resolveEmbedPullUrl()` now returns an `EmbedPullTarget`
103
+ (`{ url: string; sha256?: string }`) it previously returned
104
+ `string | undefined`. It always resolves: `CODEINDEX_EMBED_URL` wins
105
+ outright and carries **no** `sha256` (a custom mirror keeps the
106
+ un-verified behavior); with no env it falls back to the built-in official
107
+ asset **with** its pinned `sha256` so `embed pull` verifies the default
108
+ download. Replace `const url = resolveEmbedPullUrl()` with
109
+ `const { url, sha256 } = resolveEmbedPullUrl()`. The `EmbedPullTarget`
110
+ type is exported from the barrel.
111
+ - MCP `search` with `semantic: true` now returns
112
+ `{ results, tier, degradedReason? }` it previously returned the bare
113
+ ranked array. `tier` is `"endpoint" | "static" | "lexical"` and
114
+ `degradedReason` is present only when the semantic tier degraded to
115
+ lexical, so a caller can tell "fusion happened" apart from "degraded".
116
+ Plain lexical `search` (no `semantic: true`) still returns the bare
117
+ array, byte-compatible with existing consumers.
118
+
119
+ ## v2.13.0 `.codeindex` excluded from the walk
120
+
121
+ `.codeindex/` the engine's own output directory (index artifacts, pulled
122
+ models, MCP memories) — joined `IGNORE_DIRS`, so `walk`/`scanRepo` no longer
123
+ descend into it and memories stop entering BM25/embedding results (previously
124
+ every `write_memory` also churned the scan fingerprint, busting the MCP
125
+ server's memoized embedding index). Access memories through the MCP memory
126
+ tools (`read_memory` / `list_memories` / `delete_memory`), never `search`.
127
+ This is a file-set change only — extraction shape and `SCHEMA_VERSION` are
128
+ untouched, so **no re-pin is required**; a consumer that deliberately wants
129
+ `.codeindex` walked can pass `ignoreDirs` (replace semantics, also new in
130
+ this release) with its own set.
131
+
132
+ ## v2.14.0 — incremental fastpaths (all additive, no re-pin required)
133
+
134
+ Pure fastpaths: every entry point still produces byte-identical artifacts for
135
+ unchanged inputs, so **no re-pin is required** and no consumer needs to act.
136
+ `SCHEMA_VERSION` / `EMBED_VERSION` / `EXTRACTOR_VERSION` are untouched; the new
137
+ surface is additive (semver-minor).
138
+
139
+ - **`RepoScan.contentUnchanged` / `RepoScan.cacheDirty` + `ScanOptions.precomputedWalk`.**
140
+ Two derived read-only flags: `contentUnchanged` is true when a `cache` was
141
+ supplied and every kept file reused its cached record (stat fastpath or exact
142
+ content-hash) with an unchanged file set; `cacheDirty` is true when persisting
143
+ the cache would change any byte (a hash/size/mtime drift, a file-set
144
+ difference, or no cache at all). `precomputedWalk` lets a caller that already
145
+ walked hand its `WalkResult` to `scanRepo` instead of re-walking — it must
146
+ come from `walk(root, <the same options>)`.
147
+ - **`buildArtifactsFromScan(scan, opts)` export.** The downstream half of
148
+ `buildIndexArtifacts` (resolve → graph → communities → centrality → symbol
149
+ index) split out as its own export; `buildIndexArtifacts` is now `scanRepo` +
150
+ this call. A consumer already holding a `RepoScan` builds artifacts without
151
+ re-walking; the extracted body is verbatim, so output is byte-identical.
152
+ - **`cache.json` additive meta keys.** Writes gain a fixed-order `meta` block —
153
+ `engineVersion`, `commit`, `graphSha1`, `symbolsSha1`, and `embed`
154
+ (`{ embedVersion, modelId, sha1 }`) only when `embeddings.bin` was written.
155
+ Old engines ignore these keys (they only check schema/extractor); an old cache
156
+ lacking them never fastpaths but still reuses records. `cache.json` embeds
157
+ mtimes so it never was cross-machine byte-reproducible — no determinism
158
+ surface changes.
159
+ - **CLI `index` fastpath.** `index` skips `buildArtifactsFromScan`, both renders
160
+ and every artifact write when a guard proves the run would reproduce the
161
+ on-disk bytes: `scan.contentUnchanged`, `meta.engineVersion` matches,
162
+ `meta.commit` matches the scan's commit (graph.json embeds the commit — an
163
+ identical tree under a new HEAD rebuilds), the sha1 of the on-disk
164
+ graph/symbols equals the recorded shas, and the embed leg holds (no model, or
165
+ the model's embedVersion+modelId and `embeddings.bin` sha match — a model swap
166
+ rebuilds the sidecar). Any failure — deleted, truncated or tampered artifacts
167
+ included — falls through to the full rebuild that rewrites everything, so the
168
+ fastpath self-heals on corruption; `cache.json` is rewritten on the fastpath
169
+ only when `scan.cacheDirty`.
170
+ - **MCP session scan + artifacts cache.** The long-lived server memoizes a
171
+ single scan and its artifacts across tool calls: `getScan` re-runs `scanRepo`
172
+ with the prior scan re-expressed as its `cache`, so scan.ts's stat/hash oracle
173
+ decides freshness and an unchanged repo returns the SAME `RepoScan` object,
174
+ while `getArtifacts` lazily runs `buildArtifactsFromScan` memoized on scan
175
+ object identity (rendered strings are never cached). Any successful edit tool
176
+ (`replace_symbol_body`, `insert_after_/insert_before_symbol`) drops the entry
177
+ — a controlled write landing in the same mtime tick at the same byte count
178
+ would fool the (size, mtime) fastpath; `write_memory` needs no invalidation
179
+ (`.codeindex/` is off the walk since v2.13.0).
180
+ - **`runMcpServer` serverInfo override.** `runMcpServer(opts?)` accepts
181
+ `{ serverInfo?: { name?, version? } }` so a consumer embedding the server
182
+ announces its own identity in the `initialize` response; omitted fields keep
183
+ the `{ name: "codeindex", version: ENGINE_VERSION }` defaults, the zero-arg
184
+ call is unchanged, and `McpServerOptions` is exported from the barrel.
185
+ - **Lazy grammar warm — covering-set guarantee.** The CLI and MCP server warm
186
+ only the grammars for languages actually present (`grammarKeysForExts` over
187
+ the walked extensions) rather than every grammar at startup. The walk's
188
+ extension set is a superset of what `scanRepo` keeps (scope/include/exclude
189
+ only filter further), so every extracted file has its grammar loaded before
190
+ extraction and AST output stays byte-identical — including a language whose
191
+ first file appears mid-session (the MCP warm re-derives per call). The
192
+ **pre-existing cache-tier caveat is unchanged**: a record reused by hash may
193
+ have been extracted under a different grammar tier.
194
+ - **Slim grammars-pull tier (`grammars pull` / `grammars status`).** The AST
195
+ wasm sidecar (`scripts/grammars/`, ~17 MiB) stays optional and opt-in by
196
+ presence, but a consumer that vendors only `engine.mjs` no longer has to
197
+ vendor the wasm to get AST-exact symbols. `resolveGrammarsTier` /
198
+ `resolveGrammarsDir` now resolve in order **adjacent > env > cache > regex**:
199
+ the bundle-adjacent `grammars/` dir wins if present (the offline, no-network
200
+ story is untouched), then `CODEINDEX_GRAMMARS_DIR`, then the shared
201
+ version-scoped cache `sharedGrammarsCacheDir()`
202
+ (`<XDG_CACHE_HOME|~/.cache>/codeindex/grammars/<ENGINE_VERSION>`), else nothing
203
+ resolvable → the regex tier exactly as today. `codeindex grammars pull`
204
+ fetches the per-release `grammars-<ENGINE_VERSION>.tar.gz` asset (built and
205
+ uploaded to the `v<ENGINE_VERSION>` tag by the release workflow) plus its
206
+ `.sha256` sidecar, verifies the digest, and extracts atomically into that
207
+ cache with a zero-dep inline ustar reader (path-traversal-guarded, no spawned
208
+ `tar`); it is idempotent (a matching marker skips the ~22 MB download) and
209
+ `CODEINDEX_GRAMMARS_URL` overrides the source (private mirror, unverified,
210
+ like the embed-pull precedent). `codeindex grammars status` reports the active
211
+ tier, resolved dir, pinned `ENGINE_VERSION`, and whether a pull is needed
212
+ (JSON). The **guarantee**: the same committed wasm bytes loaded from the cache
213
+ produce byte-identical AST extraction as from a bundle-adjacent dir (same wasm
214
+ → same AST → same symbols), so `SCHEMA_VERSION` / `EXTRACTOR_VERSION` are
215
+ untouched and **no re-pin is required**. **Offline-safe**: `grammars pull`
216
+ never runs during indexing, and a failed/absent pull only ever leaves the
217
+ cache empty — it never throws into the scan, which silently uses the regex
218
+ tier. New exports: `resolveGrammarsTier`, `resolveGrammarsDir`,
219
+ `sharedGrammarsCacheDir`, `GrammarsTier` / `GrammarsTierName`,
220
+ `resolveGrammarsPullTarget`, `fetchGrammarsTarball`, `fetchExpectedSha256`,
221
+ `extractGrammarsTarball`, `GrammarsPullTarget`.
222
+
223
+ ## Typical mapping (what to replace with what)
224
+
225
+ What a consumer usually deletes from its own codebase, and the engine export
226
+ that replaces it:
227
+
228
+ | Hand-rolled piece | Engine replacement |
229
+ |---|---|
230
+ | file walker + skip lists + gitignore parser | `walk` / `scanRepo` (gitignore on by default; `include`/`exclude`/`scope`) |
231
+ | extension→language map | `extToLang` / `languageOf` |
232
+ | per-language symbol/import/call regexes | `extractCode` / `extractSymbols` (`symbols`/`refs`/`calls`), `buildSymbolIndex` |
233
+ | import resolution (tsconfig paths, package `exports`, go.mod, Cargo…) | `buildResolveContext` + `resolveImport` |
234
+ | workspace/monorepo probing | `detectWorkspaces` (packages/dependsOn/cycle/topoOrder) |
235
+ | dependency/link graph construction | `buildGraph` + `resolveCallEdges` |
236
+ | grep with ripgrep + JS fallback | `grepRepo` |
237
+ | git churn / changed-files helpers | `gitChurn`, `changedSince` |
238
+ | language histogram, test detection | `scanRepo` (`.languages`) + `isTestPath` |
239
+ | caller lookup, precision-gated | `buildCallerIndex` (def-resolved and gated: language-family filter, JS/TS import gate, same-file self-declaration skip) |
240
+ | caller lookup, raw recall (e.g. taint-BFS input) | `buildRawCallerIndex` (issue #8) — every name-matched call site keyed by the raw callee name, no def resolution or gating, `enclosingSymbol` computed per site. `buildCallerIndex` is **NOT** a substitute here: its gates silently drop sites a recall consumer needs. Both are bounded by `FileRecord.calls`'s per-file 512-call cap (dedup by name+line) — a file with more raw call sites than that loses sites upstream of either function. |
241
+
242
+ What a consumer keeps is everything above the index: its own scoring,
243
+ rendering, retrieval and domain logic.
125
244
 
126
245
  ## Golden-diff adjudication (every migration)
127
246
 
128
- Capture the skill's load-bearing artifact **before** touching code (a committed
247
+ Capture the consumer's load-bearing artifact **before** touching code (a committed
129
248
  snapshot test), migrate, then adjudicate every diff:
130
249
 
131
250
  - **Accept + document**: file-set changes from better ignore rules (gitignore
package/docs/SEMANTIC.md CHANGED
@@ -78,7 +78,7 @@ rows at one global scale. The download is **sha256-verified** against
78
78
  `EMBED_ASSET_SHA256` (`src/embed/model.ts`) before it is written; a mismatch
79
79
  fails the pull and writes nothing. The asset is **never** committed to git or
80
80
  shipped in the npm tarball — it lives only in the release. Reproduce it
81
- byte-for-byte with the pinned toolchain in [`scripts/embed-asset/`](../scripts/embed-asset/).
81
+ byte-for-byte with the pinned toolchain in [`scripts/embed-asset/`](https://github.com/maxgfr/codeindex/tree/main/scripts/embed-asset).
82
82
 
83
83
  ## CLI
84
84
 
@@ -110,13 +110,20 @@ codeindex search "http client retry" --repo <dir> --semantic
110
110
 
111
111
  ## Artifact: `embeddings.bin`
112
112
 
113
+ ### Layout (the `deserializeEmbeddings` contract)
114
+
113
115
  ```
114
- "CIE1" 4-byte ASCII magic
115
- uint32 LE header length
116
- UTF-8 JSON header { embedVersion, modelId, dim, count, records:[{file,symbol,line}] }
117
- int8 body count × dim signed bytes (row-major)
116
+ offset 0 "CIE1" 4-byte ASCII magic
117
+ offset 4 uint32 LE header length (headerLen)
118
+ offset 8 UTF-8 JSON header { embedVersion, modelId, dim, count, records:[{file,symbol,line}] }
119
+ offset 8+headerLen int8 body count × dim signed bytes (row-major)
118
120
  ```
119
121
 
122
+ This is exactly what `deserializeEmbeddings` (`src/embed/index.ts`) reads back:
123
+ it throws on a bad magic (a corrupt or foreign file fails loudly instead of
124
+ being misread), parses the JSON header, and slices the packed body at
125
+ `8 + headerLen`.
126
+
120
127
  The header carries **no absolute path and no timestamp**; records follow scan
121
128
  order (files sorted by `rel`, symbols in declaration order). Two builds of an
122
129
  unchanged repo produce byte-identical `embeddings.bin`. A dedicated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxgfr/codeindex",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Self-contained, deterministic repo-indexing engine: walk + language detection + symbol/import extraction (tree-sitter AST with regex fallback) + import resolution + typed cross-file link-graph + analytics. Ships as a single zero-dependency engine.mjs that consumer tools vendor.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -1,6 +1,6 @@
1
- declare const ENGINE_VERSION = "2.12.0";
1
+ declare const ENGINE_VERSION = "2.14.0";
2
2
  declare const SCHEMA_VERSION = 4;
3
- declare const EXTRACTOR_VERSION = 9;
3
+ declare const EXTRACTOR_VERSION = 10;
4
4
  type FileKind = "code" | "doc" | "config" | "asset" | "other";
5
5
  type EdgeKind = "contains" | "doc-link" | "import" | "call" | "use" | "mention";
6
6
  type Tier = 0 | 1 | 2;
@@ -121,6 +121,7 @@ interface WalkOptions {
121
121
  maxFileBytes?: number;
122
122
  maxFiles?: number;
123
123
  gitignore?: boolean;
124
+ ignoreDirs?: string[];
124
125
  }
125
126
  interface WalkedFile {
126
127
  rel: string;
@@ -147,14 +148,18 @@ interface RepoScan {
147
148
  mtimes: Map<string, number>;
148
149
  capped: boolean;
149
150
  excluded: number;
151
+ contentUnchanged: boolean;
152
+ cacheDirty: boolean;
150
153
  }
151
154
  interface ScanOptions {
152
155
  include?: string[];
153
156
  exclude?: string[];
154
157
  scope?: string;
155
158
  gitignore?: boolean;
159
+ ignoreDirs?: string[];
156
160
  maxBytes?: number;
157
161
  maxFiles?: number;
162
+ maxCallsPerFile?: number;
158
163
  out?: string;
159
164
  cache?: Map<string, {
160
165
  hash: string;
@@ -163,6 +168,7 @@ interface ScanOptions {
163
168
  mtimeMs?: number;
164
169
  }>;
165
170
  fullHash?: boolean;
171
+ precomputedWalk?: WalkResult;
166
172
  }
167
173
  declare function scanRepo(root: string, opts?: ScanOptions): RepoScan;
168
174
 
@@ -202,7 +208,9 @@ interface CodeInfo {
202
208
  }[];
203
209
  importedNames?: string[];
204
210
  }
205
- declare function extractCode(rel: string, ext: string, content: string): CodeInfo;
211
+ declare function extractCode(rel: string, ext: string, content: string, opts?: {
212
+ maxCallsPerFile?: number;
213
+ }): CodeInfo;
206
214
 
207
215
  interface MarkdownInfo {
208
216
  title?: string;
@@ -213,8 +221,22 @@ interface MarkdownInfo {
213
221
  declare function extractMarkdown(content: string): MarkdownInfo;
214
222
 
215
223
  declare function grammarKeyForExt(ext: string): string | undefined;
224
+ type GrammarsTierName = "adjacent" | "env" | "cache" | "none";
225
+ interface GrammarsTier {
226
+ tier: GrammarsTierName;
227
+ dir?: string;
228
+ cacheDir: string;
229
+ }
230
+ declare function sharedGrammarsCacheDir(): string;
231
+ declare function resolveGrammarsTier(opts?: {
232
+ moduleDir?: string;
233
+ }): GrammarsTier;
234
+ declare function resolveGrammarsDir(opts?: {
235
+ moduleDir?: string;
236
+ }): string | undefined;
216
237
  declare function ensureGrammars(keys: Iterable<string>): Promise<void>;
217
238
  declare function allGrammarKeys(): string[];
239
+ declare function grammarKeysForExts(exts: Iterable<string>): string[];
218
240
  declare function grammarReady(key: string): boolean;
219
241
 
220
242
  interface AstResult {
@@ -229,7 +251,20 @@ interface AstResult {
229
251
  }[];
230
252
  importedNames: string[];
231
253
  }
232
- declare function extractAst(rel: string, ext: string, content: string): AstResult | undefined;
254
+ declare function extractAst(rel: string, ext: string, content: string, opts?: {
255
+ maxCalls?: number;
256
+ }): AstResult | undefined;
257
+
258
+ declare const DEFAULT_GRAMMARS_URL = "https://github.com/maxgfr/codeindex/releases/download/v2.14.0/grammars-2.14.0.tar.gz";
259
+ interface GrammarsPullTarget {
260
+ url: string;
261
+ sha256Url?: string;
262
+ }
263
+ declare function resolveGrammarsPullTarget(): GrammarsPullTarget;
264
+ declare function fetchGrammarsTarball(url: string, expectedSha256?: string): Promise<Uint8Array>;
265
+ declare function fetchExpectedSha256(url: string): Promise<string>;
266
+ declare function extractTarInto(rawTar: Uint8Array, destDir: string): string[];
267
+ declare function extractGrammarsTarball(bytes: Uint8Array, destDir: string): string[];
233
268
 
234
269
  type Resolution = {
235
270
  kind: "resolved";
@@ -442,6 +477,7 @@ interface IndexArtifacts {
442
477
  symbols: SymbolIndex;
443
478
  }
444
479
  declare function buildIndexArtifacts(repo: string, opts?: BuildIndexOptions): IndexArtifacts;
480
+ declare function buildArtifactsFromScan(scan: RepoScan, opts?: BuildIndexOptions): IndexArtifacts;
445
481
 
446
482
  declare function headCommit(dir: string): string | undefined;
447
483
  interface DiffFile {
@@ -672,9 +708,15 @@ interface MermaidOptions {
672
708
  }
673
709
  declare function renderMermaid(graph: Graph, opts?: MermaidOptions): string;
674
710
 
675
- declare function runMcpServer(): Promise<void>;
711
+ interface McpServerOptions {
712
+ serverInfo?: {
713
+ name?: string;
714
+ version?: string;
715
+ };
716
+ }
717
+ declare function runMcpServer(opts?: McpServerOptions): Promise<void>;
676
718
 
677
- declare function sha1(s: string): string;
719
+ declare function sha1(s: string | Uint8Array): string;
678
720
  declare function shortHash(s: string, n?: number): string;
679
721
 
680
722
  declare function byStr(a: string, b: string): number;
@@ -705,4 +747,4 @@ declare function rrf<T>(lists: T[][], keyOf: (item: T) => string, k?: number): M
705
747
 
706
748
  declare function runCli(argv: string[]): Promise<void>;
707
749
 
708
- export { type ArchRule, type BuildIndexOptions, type BuiltinRule, type CallerEntry, type CallerIndex, type CallerIndexOptions, type CallerSite, type ChangeCoupling, type CodeInfo, type CodeSymbol, type CouplingOptions, DEFAULT_MAX_FILES, type DeadSymbol, type DiffFile, type DiffSpec, EMBED_VERSION, ENGINE_VERSION, EXTRACTOR_VERSION, type Edge, type EdgeKind, type EditResult, type EmbedEndpointOptions, type EmbeddingIndex, type EmbeddingRecord, type EmbeddingUnit, type FileCategory, type FileKind, type FileNode, type FileRecord, type FindSymbolOptions, type ForbiddenEdgeRule, type Graph, type GrepOptions, type Hotspot, type Hunk, type IgnoreRule, type IndexArtifacts, MARKDOWN_EXT, type MarkdownInfo, type MermaidOptions, type ModuleInfo, type ModuleNode, type RawCallerIndex, type RawCallerSite, type RawRef, type RenderScipOptions, type RepoMapOptions, type RepoScan, type Resolution, type ResolveContext, type RiskHotspot, type RuleSeverity, type RuleViolation, SCHEMA_VERSION, type ScanOptions, type SearchHit, type SearchOptions, type SearchResult, type SemanticSearchOptions, type SemanticSearchResult, type ShResult, type StaticEmbedModel, type SurpriseEdge, type SymbolComplexity, type SymbolIndex, type SymbolMatch, type SymbolReferences, type TestMap, type Tier, type WalkOptions, type WalkResult, type WalkedFile, type WorkspaceInfo, type WorkspaceKind, type WorkspacePackage, allGrammarKeys, applyCentrality, basicTokenize, betweennessOf, buildCallerIndex, buildEmbeddingIndex, buildEndpointIndex, buildGraph, buildIndexArtifacts, buildModules, buildRawCallerIndex, buildResolveContext, buildSymbolIndex, byKey, byStr, categorize, changeCoupling, changedSince, checkRules, classify, clip, clipInline, communityOf, compileGlobs, complexityOfSource, computeImportPairs, computeSurprises, computeSymbolRefs, computeTestMap, deleteMemory, deserializeEmbeddings, detectCommunities, detectWorkspaces, diffFiles, diffHunks, embedEndpointUrl, embedViaEndpoint, embeddingUnits, enclosingSymbol, encode, encodeQueryViaEndpoint, ensureGrammars, escapeRegExp, extToLang, extractAst, extractCode, extractMarkdown, extractSymbols, findDeadCode, findReferences, findSymbol, foldText, gitChurn, grammarKeyForExt, grammarReady, grepRepo, hasEmbedModel, have, headCommit, healthzUrl, insertAfterSymbol, insertBeforeSymbol, intDot, isCode, isDoc, isGitWorktree, isIgnored, isSurprising, isTestFile, isTestPath, keywords, languageOf, listMemories, loadEmbedModel, pagerankOf, parseGitignore, parseRules, probeEndpoint, quantize, rankHotspots, rankedKeywords, readMemory, readText, renderGraphJson, renderMermaid, renderRepoMap, renderScip, renderSymbolsJson, replaceSymbolBody, resolveBaseRef, resolveCallEdges, resolveDocLink, resolveEmbedEndpoint, resolveEmbedModelDir, resolveEmbedPullUrl, resolveImport, resolveUniqueSymbol, riskHotspots, roundHalfToEven, rrf, runCli, runMcpServer, scanRepo, searchIndex, searchSemantic, serializeEmbeddings, sh, sha1, shortHash, slugify, subtokens, symbolComplexity, symbolsOverview, testsForModule, tierForPath, tokenize, uniqueSymbolDefs, untestedModules, untrackedFiles, walk, wordpiece, writeMemory };
750
+ export { type ArchRule, type BuildIndexOptions, type BuiltinRule, type CallerEntry, type CallerIndex, type CallerIndexOptions, type CallerSite, type ChangeCoupling, type CodeInfo, type CodeSymbol, type CouplingOptions, DEFAULT_GRAMMARS_URL, DEFAULT_MAX_FILES, type DeadSymbol, type DiffFile, type DiffSpec, EMBED_VERSION, ENGINE_VERSION, EXTRACTOR_VERSION, type Edge, type EdgeKind, type EditResult, type EmbedEndpointOptions, type EmbedPullTarget, type EmbeddingIndex, type EmbeddingRecord, type EmbeddingUnit, type FileCategory, type FileKind, type FileNode, type FileRecord, type FindSymbolOptions, type ForbiddenEdgeRule, type GrammarsPullTarget, type GrammarsTier, type GrammarsTierName, type Graph, type GrepOptions, type Hotspot, type Hunk, type IgnoreRule, type IndexArtifacts, MARKDOWN_EXT, type MarkdownInfo, type McpServerOptions, type MermaidOptions, type ModuleInfo, type ModuleNode, type RawCallerIndex, type RawCallerSite, type RawRef, type RenderScipOptions, type RepoMapOptions, type RepoScan, type Resolution, type ResolveContext, type RiskHotspot, type RuleSeverity, type RuleViolation, SCHEMA_VERSION, type ScanOptions, type SearchHit, type SearchOptions, type SearchResult, type SemanticSearchOptions, type SemanticSearchResult, type ShResult, type StaticEmbedModel, type SurpriseEdge, type SymbolComplexity, type SymbolIndex, type SymbolMatch, type SymbolReferences, type TestMap, type Tier, type WalkOptions, type WalkResult, type WalkedFile, type WorkspaceInfo, type WorkspaceKind, type WorkspacePackage, allGrammarKeys, applyCentrality, basicTokenize, betweennessOf, buildArtifactsFromScan, buildCallerIndex, buildEmbeddingIndex, buildEndpointIndex, buildGraph, buildIndexArtifacts, buildModules, buildRawCallerIndex, buildResolveContext, buildSymbolIndex, byKey, byStr, categorize, changeCoupling, changedSince, checkRules, classify, clip, clipInline, communityOf, compileGlobs, complexityOfSource, computeImportPairs, computeSurprises, computeSymbolRefs, computeTestMap, deleteMemory, deserializeEmbeddings, detectCommunities, detectWorkspaces, diffFiles, diffHunks, embedEndpointUrl, embedViaEndpoint, embeddingUnits, enclosingSymbol, encode, encodeQueryViaEndpoint, ensureGrammars, escapeRegExp, extToLang, extractAst, extractCode, extractGrammarsTarball, extractMarkdown, extractSymbols, extractTarInto, fetchExpectedSha256, fetchGrammarsTarball, findDeadCode, findReferences, findSymbol, foldText, gitChurn, grammarKeyForExt, grammarKeysForExts, grammarReady, grepRepo, hasEmbedModel, have, headCommit, healthzUrl, insertAfterSymbol, insertBeforeSymbol, intDot, isCode, isDoc, isGitWorktree, isIgnored, isSurprising, isTestFile, isTestPath, keywords, languageOf, listMemories, loadEmbedModel, pagerankOf, parseGitignore, parseRules, probeEndpoint, quantize, rankHotspots, rankedKeywords, readMemory, readText, renderGraphJson, renderMermaid, renderRepoMap, renderScip, renderSymbolsJson, replaceSymbolBody, resolveBaseRef, resolveCallEdges, resolveDocLink, resolveEmbedEndpoint, resolveEmbedModelDir, resolveEmbedPullUrl, resolveGrammarsDir, resolveGrammarsPullTarget, resolveGrammarsTier, resolveImport, resolveUniqueSymbol, riskHotspots, roundHalfToEven, rrf, runCli, runMcpServer, scanRepo, searchIndex, searchSemantic, serializeEmbeddings, sh, sha1, sharedGrammarsCacheDir, shortHash, slugify, subtokens, symbolComplexity, symbolsOverview, testsForModule, tierForPath, tokenize, uniqueSymbolDefs, untestedModules, untrackedFiles, walk, wordpiece, writeMemory };