@maxgfr/codeindex 2.9.0 → 2.11.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.
@@ -14,9 +14,9 @@ var ENGINE_VERSION, SCHEMA_VERSION, EXTRACTOR_VERSION;
14
14
  var init_types = __esm({
15
15
  "src/types.ts"() {
16
16
  "use strict";
17
- ENGINE_VERSION = "2.9.0";
17
+ ENGINE_VERSION = "2.11.0";
18
18
  SCHEMA_VERSION = 4;
19
- EXTRACTOR_VERSION = 6;
19
+ EXTRACTOR_VERSION = 7;
20
20
  }
21
21
  });
22
22
 
@@ -6340,12 +6340,14 @@ function extractImports(ext, content) {
6340
6340
  }
6341
6341
  return [...specs].map((spec) => ({ kind: "import", spec }));
6342
6342
  }
6343
- function extractReexports(rel, content) {
6343
+ function extractReexports(rel, content, localSymbols) {
6344
6344
  if (!JS_TS.has(rel.slice(rel.lastIndexOf(".")))) return [];
6345
6345
  const lang = /\.(ts|tsx|mts|cts)$/.test(rel) ? "typescript" : "javascript";
6346
6346
  const out2 = [];
6347
6347
  const seen = /* @__PURE__ */ new Set();
6348
6348
  const lineAt = (idx) => content.slice(0, idx).split(/\r?\n/).length;
6349
+ const localKindOf = /* @__PURE__ */ new Map();
6350
+ for (const s of localSymbols) if (!localKindOf.has(s.name)) localKindOf.set(s.name, s.kind);
6349
6351
  const named = /export\s*\{([\s\S]*?)\}\s*(?:from\s*['"]([^'"]+)['"])?\s*;?/g;
6350
6352
  let m;
6351
6353
  while ((m = named.exec(content)) && out2.length < 60) {
@@ -6353,12 +6355,14 @@ function extractReexports(rel, content) {
6353
6355
  for (const part of m[1].split(",")) {
6354
6356
  const p = part.trim().replace(/^type\s+/, "");
6355
6357
  const as = /^(\S+)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(p);
6358
+ const orig = as ? as[1] : p;
6356
6359
  const name2 = as ? as[2] : p;
6357
6360
  if (!/^[A-Za-z_$][\w$]*$/.test(name2) || name2 === "default" || seen.has(name2)) continue;
6358
6361
  seen.add(name2);
6362
+ const mirroredKind = !from ? localKindOf.get(orig) : void 0;
6359
6363
  out2.push({
6360
6364
  name: name2,
6361
- kind: "reexport",
6365
+ kind: mirroredKind ?? "reexport",
6362
6366
  file: rel,
6363
6367
  line: lineAt(m.index),
6364
6368
  signature: from ? `export { ${name2} } from "${from}"` : `export { ${name2} }`,
@@ -6411,7 +6415,7 @@ function extractCode(rel, ext, content) {
6411
6415
  const ast = extractAst(rel, ext, content);
6412
6416
  const symbols = (ast ? ast.symbols : extractSymbols(rel, ext, content)).slice(0, 400);
6413
6417
  const known = new Set(symbols.map((s) => s.name));
6414
- const reexports = extractReexports(rel, content).filter((s) => !known.has(s.name));
6418
+ const reexports = extractReexports(rel, content, symbols).filter((s) => !known.has(s.name));
6415
6419
  return {
6416
6420
  symbols: [...symbols, ...reexports],
6417
6421
  summary: topDocComment(content),
@@ -9343,6 +9347,376 @@ var init_bm25 = __esm({
9343
9347
  }
9344
9348
  });
9345
9349
 
9350
+ // src/embed/model.ts
9351
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
9352
+ import { join as join10 } from "path";
9353
+ function resolveEmbedModelDir(repo) {
9354
+ const env = process.env.CODEINDEX_EMBED_DIR;
9355
+ const candidates = [];
9356
+ if (env) candidates.push(env);
9357
+ if (repo) candidates.push(join10(repo, ".codeindex", DEFAULT_EMBED_DIRNAME));
9358
+ candidates.push(join10(process.cwd(), ".codeindex", DEFAULT_EMBED_DIRNAME));
9359
+ for (const c2 of candidates) {
9360
+ if (existsSync3(join10(c2, "model.json"))) return c2;
9361
+ }
9362
+ return void 0;
9363
+ }
9364
+ function hasEmbedModel(repo) {
9365
+ return resolveEmbedModelDir(repo) !== void 0;
9366
+ }
9367
+ function loadEmbedModel(dir) {
9368
+ if (!dir) return void 0;
9369
+ const path = join10(dir, "model.json");
9370
+ if (!existsSync3(path)) return void 0;
9371
+ const raw = JSON.parse(readFileSync5(path, "utf8"));
9372
+ const { modelId, dim, vocab, weights } = raw;
9373
+ if (typeof modelId !== "string" || !modelId) throw new Error(`embed model: missing modelId in ${path}`);
9374
+ if (!Number.isInteger(dim) || dim <= 0) throw new Error(`embed model: bad dim ${dim} in ${path}`);
9375
+ if (!Array.isArray(vocab) || !Array.isArray(weights) || vocab.length !== weights.length) {
9376
+ throw new Error(`embed model: vocab/weights length mismatch in ${path}`);
9377
+ }
9378
+ const vocabSize = vocab.length;
9379
+ const flat = new Float64Array(vocabSize * dim);
9380
+ const vmap = /* @__PURE__ */ new Map();
9381
+ for (let i2 = 0; i2 < vocabSize; i2++) {
9382
+ const tok = vocab[i2];
9383
+ if (typeof tok !== "string") throw new Error(`embed model: non-string vocab entry at ${i2}`);
9384
+ if (!vmap.has(tok)) vmap.set(tok, i2);
9385
+ const row = weights[i2];
9386
+ if (!Array.isArray(row) || row.length !== dim) {
9387
+ throw new Error(`embed model: row ${i2} has length ${row?.length}, expected ${dim}`);
9388
+ }
9389
+ for (let d = 0; d < dim; d++) flat[i2 * dim + d] = Number(row[d]);
9390
+ }
9391
+ const unk = typeof raw.unk === "string" ? raw.unk : "[UNK]";
9392
+ const unkId = vmap.has(unk) ? vmap.get(unk) : -1;
9393
+ return { modelId, dim, unk, unkId, vocabSize, vocab: vmap, weights: flat };
9394
+ }
9395
+ function resolveEmbedPullUrl() {
9396
+ const url = process.env.CODEINDEX_EMBED_URL;
9397
+ return url && url.trim() ? url.trim() : void 0;
9398
+ }
9399
+ var EMBED_VERSION, DEFAULT_EMBED_DIRNAME;
9400
+ var init_model = __esm({
9401
+ "src/embed/model.ts"() {
9402
+ "use strict";
9403
+ EMBED_VERSION = 1;
9404
+ DEFAULT_EMBED_DIRNAME = "models";
9405
+ }
9406
+ });
9407
+
9408
+ // src/embed/encode.ts
9409
+ function basicTokenize(text) {
9410
+ const spaced = foldText(text).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
9411
+ const out2 = [];
9412
+ for (const part of spaced.toLowerCase().split(/[^a-z0-9]+/)) {
9413
+ if (part) out2.push(part);
9414
+ }
9415
+ return out2;
9416
+ }
9417
+ function wordpiece(word, model) {
9418
+ if (!word) return [];
9419
+ const ids = [];
9420
+ let start2 = 0;
9421
+ const n = word.length;
9422
+ while (start2 < n) {
9423
+ let end = n;
9424
+ let match = -1;
9425
+ while (end > start2) {
9426
+ const piece = start2 === 0 ? word.slice(start2, end) : "##" + word.slice(start2, end);
9427
+ const id = model.vocab.get(piece);
9428
+ if (id !== void 0) {
9429
+ match = id;
9430
+ break;
9431
+ }
9432
+ end--;
9433
+ }
9434
+ if (match === -1) return model.unkId >= 0 ? [model.unkId] : [];
9435
+ ids.push(match);
9436
+ start2 = end;
9437
+ }
9438
+ return ids;
9439
+ }
9440
+ function tokenize(text, model) {
9441
+ const ids = [];
9442
+ for (const word of basicTokenize(text)) {
9443
+ for (const id of wordpiece(word, model)) ids.push(id);
9444
+ }
9445
+ return ids;
9446
+ }
9447
+ function roundHalfToEven(x) {
9448
+ const f = Math.floor(x);
9449
+ const diff = x - f;
9450
+ if (diff < 0.5) return f;
9451
+ if (diff > 0.5) return f + 1;
9452
+ return f % 2 === 0 ? f : f + 1;
9453
+ }
9454
+ function quantize(vec) {
9455
+ const dim = vec.length;
9456
+ const out2 = new Int8Array(dim);
9457
+ let sumsq = 0;
9458
+ for (let d = 0; d < dim; d++) sumsq += vec[d] * vec[d];
9459
+ const norm2 = Math.sqrt(sumsq);
9460
+ if (norm2 === 0) return out2;
9461
+ for (let d = 0; d < dim; d++) {
9462
+ let q = roundHalfToEven(vec[d] / norm2 * QUANT);
9463
+ if (q > QUANT) q = QUANT;
9464
+ else if (q < -QUANT) q = -QUANT;
9465
+ out2[d] = q;
9466
+ }
9467
+ return out2;
9468
+ }
9469
+ function encode(model, text) {
9470
+ const { dim, weights } = model;
9471
+ const ids = tokenize(text, model);
9472
+ if (ids.length === 0) return new Int8Array(dim);
9473
+ const pooled = new Float64Array(dim);
9474
+ for (const id of ids) {
9475
+ const base = id * dim;
9476
+ for (let d = 0; d < dim; d++) pooled[d] += weights[base + d];
9477
+ }
9478
+ const inv = 1 / ids.length;
9479
+ for (let d = 0; d < dim; d++) pooled[d] *= inv;
9480
+ return quantize(pooled);
9481
+ }
9482
+ function intDot(a, b) {
9483
+ const n = Math.min(a.length, b.length);
9484
+ let dot = 0;
9485
+ for (let i2 = 0; i2 < n; i2++) dot += a[i2] * b[i2];
9486
+ return dot;
9487
+ }
9488
+ var QUANT;
9489
+ var init_encode = __esm({
9490
+ "src/embed/encode.ts"() {
9491
+ "use strict";
9492
+ init_util();
9493
+ QUANT = 127;
9494
+ }
9495
+ });
9496
+
9497
+ // src/embed/index.ts
9498
+ function symbolText(rel, name2, signature, summary) {
9499
+ return [name2, signature ?? "", summary ?? "", rel.replace(/\//g, " ")].join("\n");
9500
+ }
9501
+ function fileText(rel, title, summary, headings) {
9502
+ return [title ?? "", summary ?? "", ...headings, rel.replace(/\//g, " ")].join("\n");
9503
+ }
9504
+ function embeddingUnits(scan2) {
9505
+ const units = [];
9506
+ for (const f of scan2.files) {
9507
+ const seen = /* @__PURE__ */ new Set();
9508
+ let hadSymbol = false;
9509
+ for (const s of f.symbols) {
9510
+ if (seen.has(s.name)) continue;
9511
+ seen.add(s.name);
9512
+ hadSymbol = true;
9513
+ units.push({ file: f.rel, symbol: s.name, line: s.line, text: symbolText(f.rel, s.name, s.signature, f.summary) });
9514
+ }
9515
+ if (!hadSymbol) {
9516
+ const text = fileText(f.rel, f.title, f.summary, f.headings);
9517
+ if (text.replace(/\s+/g, "")) units.push({ file: f.rel, text });
9518
+ }
9519
+ }
9520
+ return units;
9521
+ }
9522
+ function buildEmbeddingIndex(scan2, model) {
9523
+ const records = embeddingUnits(scan2).map((u) => {
9524
+ const rec = { file: u.file, vec: encode(model, u.text) };
9525
+ if (u.symbol !== void 0) rec.symbol = u.symbol;
9526
+ if (u.line !== void 0) rec.line = u.line;
9527
+ return rec;
9528
+ });
9529
+ return { embedVersion: EMBED_VERSION, modelId: model.modelId, dim: model.dim, records };
9530
+ }
9531
+ function serializeEmbeddings(index) {
9532
+ const header = JSON.stringify({
9533
+ embedVersion: index.embedVersion,
9534
+ modelId: index.modelId,
9535
+ dim: index.dim,
9536
+ count: index.records.length,
9537
+ records: index.records.map((r) => ({ file: r.file, symbol: r.symbol ?? "", line: r.line ?? 0 }))
9538
+ });
9539
+ const headerBuf = Buffer.from(header, "utf8");
9540
+ const body2 = Buffer.alloc(index.records.length * index.dim);
9541
+ let off = 0;
9542
+ for (const r of index.records) {
9543
+ for (let d = 0; d < index.dim; d++) body2.writeInt8(r.vec[d] ?? 0, off++);
9544
+ }
9545
+ const out2 = Buffer.alloc(8 + headerBuf.length + body2.length);
9546
+ out2.write(MAGIC, 0, "ascii");
9547
+ out2.writeUInt32LE(headerBuf.length, 4);
9548
+ headerBuf.copy(out2, 8);
9549
+ body2.copy(out2, 8 + headerBuf.length);
9550
+ return out2;
9551
+ }
9552
+ function deserializeEmbeddings(bytes) {
9553
+ const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9554
+ if (buf.length < 8 || buf.toString("ascii", 0, 4) !== MAGIC) {
9555
+ throw new Error("embeddings.bin: bad magic (not a codeindex embeddings artifact)");
9556
+ }
9557
+ const headerLen = buf.readUInt32LE(4);
9558
+ const header = JSON.parse(buf.toString("utf8", 8, 8 + headerLen));
9559
+ const bodyOff = 8 + headerLen;
9560
+ const { dim } = header;
9561
+ const records = header.records.map((m, i2) => {
9562
+ const vec = new Int8Array(dim);
9563
+ for (let d = 0; d < dim; d++) vec[d] = buf.readInt8(bodyOff + i2 * dim + d);
9564
+ const rec = { file: m.file, vec };
9565
+ if (m.symbol) rec.symbol = m.symbol;
9566
+ if (m.line) rec.line = m.line;
9567
+ return rec;
9568
+ });
9569
+ return { embedVersion: header.embedVersion, modelId: header.modelId, dim, records };
9570
+ }
9571
+ var MAGIC;
9572
+ var init_embed = __esm({
9573
+ "src/embed/index.ts"() {
9574
+ "use strict";
9575
+ init_encode();
9576
+ init_model();
9577
+ MAGIC = "CIE1";
9578
+ }
9579
+ });
9580
+
9581
+ // src/embed/search.ts
9582
+ function searchSemantic(scan2, query, index, opts = {}) {
9583
+ const limit = opts.limit ?? DEFAULT_LIMIT2;
9584
+ const lexical = searchIndex(scan2, query, { limit: Math.max(limit, 50), fuzzy: opts.fuzzy });
9585
+ const q = opts.queryVec ?? (opts.model ? encode(opts.model, query) : void 0);
9586
+ if (!q || !index || index.records.length === 0) {
9587
+ return lexical.slice(0, limit);
9588
+ }
9589
+ const bestByFile = /* @__PURE__ */ new Map();
9590
+ for (const r of index.records) {
9591
+ const dot = intDot(q, r.vec);
9592
+ const prev = bestByFile.get(r.file);
9593
+ if (!prev || dot > prev.score) bestByFile.set(r.file, { score: dot, symbol: r.symbol });
9594
+ }
9595
+ const semList = [...bestByFile.entries()].filter(([, v]) => v.score > 0).sort((a, b) => b[1].score - a[1].score || byStr(a[0], b[0])).map(([file]) => file);
9596
+ const lexList = lexical.map((r) => r.file);
9597
+ const fused = rrf([lexList, semList], (f) => f, opts.rrfK ?? RRF_K);
9598
+ const lexByFile = new Map(lexical.map((r) => [r.file, r]));
9599
+ const results = [...fused.entries()].sort((a, b) => b[1] - a[1] || byStr(a[0], b[0])).map(([file, score]) => {
9600
+ const lex = lexByFile.get(file);
9601
+ const res = {
9602
+ file,
9603
+ score: Number(score.toFixed(4)),
9604
+ matchedTerms: lex?.matchedTerms ?? [],
9605
+ topSymbols: lex?.topSymbols ?? []
9606
+ };
9607
+ const sem = bestByFile.get(file);
9608
+ if (sem?.symbol) res.semanticSymbol = sem.symbol;
9609
+ if (lex?.fuzzyTerms) res.fuzzyTerms = lex.fuzzyTerms;
9610
+ return res;
9611
+ });
9612
+ return results.slice(0, limit);
9613
+ }
9614
+ var DEFAULT_LIMIT2, RRF_K;
9615
+ var init_search = __esm({
9616
+ "src/embed/search.ts"() {
9617
+ "use strict";
9618
+ init_util();
9619
+ init_sort();
9620
+ init_bm25();
9621
+ init_encode();
9622
+ DEFAULT_LIMIT2 = 20;
9623
+ RRF_K = 60;
9624
+ }
9625
+ });
9626
+
9627
+ // src/embed/endpoint.ts
9628
+ function resolveEmbedEndpoint(opts = {}) {
9629
+ const url = opts.url ?? process.env.CODEINDEX_EMBED_ENDPOINT;
9630
+ return url && url.trim() ? url.trim() : void 0;
9631
+ }
9632
+ function stripTrailingSlash(url) {
9633
+ return url.replace(/\/+$/, "");
9634
+ }
9635
+ function embedEndpointUrl(base) {
9636
+ const b = stripTrailingSlash(base);
9637
+ return b.endsWith("/embed") ? b : b + "/embed";
9638
+ }
9639
+ function healthzUrl(base) {
9640
+ return stripTrailingSlash(base).replace(/\/embed$/, "") + "/healthz";
9641
+ }
9642
+ function resolveTimeout(opts) {
9643
+ if (typeof opts.timeoutMs === "number") return opts.timeoutMs;
9644
+ const env = Number(process.env.CODEINDEX_EMBED_TIMEOUT_MS);
9645
+ return Number.isFinite(env) && env > 0 ? env : 3e4;
9646
+ }
9647
+ async function embedViaEndpoint(texts, opts = {}) {
9648
+ const base = resolveEmbedEndpoint(opts);
9649
+ if (!base) throw new Error("no embedding endpoint configured (set CODEINDEX_EMBED_ENDPOINT or pass opts.url)");
9650
+ const url = embedEndpointUrl(base);
9651
+ const controller = new AbortController();
9652
+ const timer = setTimeout(() => controller.abort(), resolveTimeout(opts));
9653
+ try {
9654
+ const res = await fetch(url, {
9655
+ method: "POST",
9656
+ headers: { "content-type": "application/json", ...opts.headers ?? {} },
9657
+ body: JSON.stringify({ texts }),
9658
+ signal: controller.signal
9659
+ });
9660
+ if (!res.ok) throw new Error(`embedding endpoint ${url} returned HTTP ${res.status}`);
9661
+ const data = await res.json();
9662
+ const vectors = data.vectors;
9663
+ if (!Array.isArray(vectors) || !vectors.every((v) => Array.isArray(v) && v.every((x) => typeof x === "number"))) {
9664
+ throw new Error(`embedding endpoint ${url} returned a malformed { vectors } payload`);
9665
+ }
9666
+ return vectors;
9667
+ } finally {
9668
+ clearTimeout(timer);
9669
+ }
9670
+ }
9671
+ async function probeEndpoint(base, opts = {}) {
9672
+ const controller = new AbortController();
9673
+ const timer = setTimeout(() => controller.abort(), resolveTimeout(opts));
9674
+ try {
9675
+ const res = await fetch(healthzUrl(base), { signal: controller.signal, headers: opts.headers });
9676
+ return res.ok;
9677
+ } catch {
9678
+ return false;
9679
+ } finally {
9680
+ clearTimeout(timer);
9681
+ }
9682
+ }
9683
+ async function encodeQueryViaEndpoint(query, opts = {}) {
9684
+ const [vec] = await embedViaEndpoint([query], opts);
9685
+ if (!vec) throw new Error("embedding endpoint returned no vector for the query");
9686
+ return quantize(vec);
9687
+ }
9688
+ async function buildEndpointIndex(scan2, opts = {}) {
9689
+ const units = embeddingUnits(scan2);
9690
+ const batchSize = opts.batchSize && opts.batchSize > 0 ? opts.batchSize : 64;
9691
+ const records = [];
9692
+ let dim = 0;
9693
+ for (let i2 = 0; i2 < units.length; i2 += batchSize) {
9694
+ const batch = units.slice(i2, i2 + batchSize);
9695
+ const vectors = await embedViaEndpoint(batch.map((u) => u.text), opts);
9696
+ if (vectors.length !== batch.length) {
9697
+ throw new Error(`embedding endpoint returned ${vectors.length} vectors for ${batch.length} texts`);
9698
+ }
9699
+ for (let j = 0; j < batch.length; j++) {
9700
+ const u = batch[j];
9701
+ const vec = quantize(vectors[j]);
9702
+ if (vec.length > dim) dim = vec.length;
9703
+ const rec = { file: u.file, vec };
9704
+ if (u.symbol !== void 0) rec.symbol = u.symbol;
9705
+ if (u.line !== void 0) rec.line = u.line;
9706
+ records.push(rec);
9707
+ }
9708
+ }
9709
+ return { embedVersion: EMBED_VERSION, modelId: "endpoint", dim, records };
9710
+ }
9711
+ var init_endpoint = __esm({
9712
+ "src/embed/endpoint.ts"() {
9713
+ "use strict";
9714
+ init_encode();
9715
+ init_model();
9716
+ init_embed();
9717
+ }
9718
+ });
9719
+
9346
9720
  // src/rules.ts
9347
9721
  function isEntrypointLike(rel) {
9348
9722
  const base = rel.split("/").pop();
@@ -9647,7 +10021,7 @@ var init_deadcode = __esm({
9647
10021
  });
9648
10022
 
9649
10023
  // src/complexity.ts
9650
- import { join as join10 } from "path";
10024
+ import { join as join11 } from "path";
9651
10025
  function complexityOfSource(source) {
9652
10026
  return 1 + (source.match(BRANCH_RE) ?? []).length;
9653
10027
  }
@@ -9657,7 +10031,7 @@ function symbolComplexity(scan2, rel, top = 50) {
9657
10031
  if (f.kind !== "code") continue;
9658
10032
  if (rel && f.rel !== rel) continue;
9659
10033
  if (!f.symbols.length) continue;
9660
- const lines = readText(join10(scan2.root, f.rel)).split("\n");
10034
+ const lines = readText(join11(scan2.root, f.rel)).split("\n");
9661
10035
  for (const s of f.symbols) {
9662
10036
  if (s.kind === "reexport" || s.kind === "reexport-all") continue;
9663
10037
  const end = s.endLine ?? s.line;
@@ -9672,7 +10046,7 @@ function symbolComplexity(scan2, rel, top = 50) {
9672
10046
  }
9673
10047
  function riskHotspots(scan2, churn, top = 20) {
9674
10048
  const out2 = scan2.files.filter((f) => f.kind === "code").map((f) => {
9675
- const complexity = complexityOfSource(readText(join10(scan2.root, f.rel)));
10049
+ const complexity = complexityOfSource(readText(join11(scan2.root, f.rel)));
9676
10050
  const commits = churn.get(f.rel) ?? 0;
9677
10051
  return { file: f.rel, complexity, commits, score: (commits + 1) * complexity };
9678
10052
  });
@@ -9738,7 +10112,7 @@ function str(v) {
9738
10112
  function strArray(v) {
9739
10113
  return Array.isArray(v) && v.every((x) => typeof x === "string") && v.length ? v : void 0;
9740
10114
  }
9741
- function callTool(name2, args2) {
10115
+ async function callTool(name2, args2) {
9742
10116
  const repo = str(args2.repo);
9743
10117
  if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
9744
10118
  const scanOpts = { scope: str(args2.scope), include: strArray(args2.include), exclude: strArray(args2.exclude) };
@@ -9871,11 +10245,42 @@ function callTool(name2, args2) {
9871
10245
  if (name2 === "search") {
9872
10246
  const query = str(args2.query);
9873
10247
  if (!query) throw new Error("`query` is required");
9874
- const results = searchIndex(scanRepo(repo, scanOpts), query, {
9875
- limit: typeof args2.limit === "number" ? args2.limit : void 0,
9876
- fuzzy: typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0
9877
- });
9878
- return JSON.stringify(results, null, 2);
10248
+ const scan2 = scanRepo(repo, scanOpts);
10249
+ const limit = typeof args2.limit === "number" ? args2.limit : void 0;
10250
+ const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
10251
+ if (args2.semantic === true) {
10252
+ const endpoint = resolveEmbedEndpoint();
10253
+ if (endpoint) {
10254
+ try {
10255
+ const index = await buildEndpointIndex(scan2);
10256
+ const queryVec = await encodeQueryViaEndpoint(query);
10257
+ return JSON.stringify(searchSemantic(scan2, query, index, { queryVec, limit, fuzzy }), null, 2);
10258
+ } catch {
10259
+ return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
10260
+ }
10261
+ }
10262
+ const modelDir = resolveEmbedModelDir(repo);
10263
+ const model = modelDir ? loadEmbedModel(modelDir) : void 0;
10264
+ if (model) {
10265
+ const index = buildEmbeddingIndex(scan2, model);
10266
+ return JSON.stringify(searchSemantic(scan2, query, index, { model, limit, fuzzy }), null, 2);
10267
+ }
10268
+ }
10269
+ return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
10270
+ }
10271
+ if (name2 === "embed_status") {
10272
+ const modelDir = resolveEmbedModelDir(repo);
10273
+ const model = modelDir ? loadEmbedModel(modelDir) : void 0;
10274
+ const endpoint = resolveEmbedEndpoint();
10275
+ const mode = endpoint ? "endpoint" : model ? "static" : "none";
10276
+ const status = {
10277
+ embedVersion: EMBED_VERSION,
10278
+ mode,
10279
+ model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
10280
+ endpoint: endpoint ?? null
10281
+ };
10282
+ if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
10283
+ return JSON.stringify(status, null, 2);
9879
10284
  }
9880
10285
  if (name2 === "check_rules") {
9881
10286
  const rules = parseRules(args2.rules);
@@ -9901,9 +10306,9 @@ async function runMcpServer() {
9901
10306
  continue;
9902
10307
  }
9903
10308
  const requests = Array.isArray(parsed) ? parsed : [parsed];
9904
- for (const req of requests) handle2(req);
10309
+ for (const req of requests) await handle2(req);
9905
10310
  }
9906
- function handle2(req) {
10311
+ async function handle2(req) {
9907
10312
  if (req.id === void 0 || req.id === null) return;
9908
10313
  try {
9909
10314
  if (req.method === "initialize") {
@@ -9924,7 +10329,7 @@ async function runMcpServer() {
9924
10329
  const name2 = str(params.name) ?? "";
9925
10330
  const args2 = params.arguments ?? {};
9926
10331
  try {
9927
- const text = callTool(name2, args2);
10332
+ const text = await callTool(name2, args2);
9928
10333
  send({ id: req.id, result: { content: [{ type: "text", text }] } });
9929
10334
  } catch (e) {
9930
10335
  send({
@@ -9963,6 +10368,10 @@ var init_mcp = __esm({
9963
10368
  init_memory();
9964
10369
  init_bm25();
9965
10370
  init_rules();
10371
+ init_model();
10372
+ init_embed();
10373
+ init_search();
10374
+ init_endpoint();
9966
10375
  repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
9967
10376
  scopeProps = {
9968
10377
  scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
@@ -10175,7 +10584,7 @@ var init_mcp = __esm({
10175
10584
  },
10176
10585
  {
10177
10586
  name: "search",
10178
- description: 'Natural-language-ish lexical search: BM25 ranking (k1=1.2, b=0.75) over symbol names (camelCase/snake_case subtokens), file path segments, markdown headings and summary lines. NOT embeddings \u2014 deterministic, diacritic-folded, zero API keys. Answers "where is auth handled?"-style queries with ranked files, matched terms and top symbols. Query terms with zero document frequency get a deterministic trigram-fuzzy fallback (typo-tolerant) unless `fuzzy: false`.',
10587
+ description: 'Natural-language-ish lexical search: BM25 ranking (k1=1.2, b=0.75) over symbol names (camelCase/snake_case subtokens), file path segments, markdown headings and summary lines. NOT embeddings by default \u2014 deterministic, diacritic-folded, zero API keys. Answers "where is auth handled?"-style queries with ranked files, matched terms and top symbols. Query terms with zero document frequency get a deterministic trigram-fuzzy fallback (typo-tolerant) unless `fuzzy: false`. Set `semantic: true` to RRF-fuse the deterministic static-embedding tier when a model asset is present (degrades to lexical otherwise \u2014 see embed_status).',
10179
10588
  inputSchema: {
10180
10589
  type: "object",
10181
10590
  properties: {
@@ -10186,11 +10595,20 @@ var init_mcp = __esm({
10186
10595
  fuzzy: {
10187
10596
  type: "boolean",
10188
10597
  description: "Trigram fuzzy fallback for query terms with zero document frequency (default true)"
10598
+ },
10599
+ semantic: {
10600
+ type: "boolean",
10601
+ description: "RRF-fuse an embedding tier with lexical (default false). Precedence: the HTTP endpoint (CODEINDEX_EMBED_ENDPOINT) if set, else a local static model. Degrades silently to lexical-only when neither is available/reachable \u2014 see embed_status."
10189
10602
  }
10190
10603
  },
10191
10604
  required: ["repo", "query"]
10192
10605
  }
10193
10606
  },
10607
+ {
10608
+ name: "embed_status",
10609
+ description: "Report the embedding tier: the effective mode (none/static/endpoint; endpoint > static model), the resolved model (opt-in, never shipped in the package) with its modelId/dim, EMBED_VERSION, and the configured HTTP endpoint with its reachability. Use to check whether `search` with semantic:true will fuse embeddings or degrade to lexical.",
10610
+ inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] }
10611
+ },
10194
10612
  {
10195
10613
  name: "check_rules",
10196
10614
  description: 'Validate dependency-cruiser-style architecture rules against the link-graph. Rules (inline JSON array): forbidden edges {name, from, to, kind?, severity?, comment?} with glob paths, plus builtins {name, builtin: "cycles"|"orphans"} (module-level import cycles; edge-less code files). Returns deterministic violations with severity error|warn \u2014 a CI gate.',
@@ -10605,6 +11023,11 @@ init_pipeline();
10605
11023
  init_git();
10606
11024
  init_grep();
10607
11025
  init_bm25();
11026
+ init_model();
11027
+ init_encode();
11028
+ init_embed();
11029
+ init_search();
11030
+ init_endpoint();
10608
11031
  init_rules();
10609
11032
  init_coupling();
10610
11033
  init_repomap();
@@ -10623,8 +11046,8 @@ init_loader();
10623
11046
  init_pipeline();
10624
11047
  init_graph_json();
10625
11048
  init_symbols_json();
10626
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
10627
- import { join as join11, resolve } from "path";
11049
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
11050
+ import { join as join12, resolve } from "path";
10628
11051
  init_scan();
10629
11052
  init_callers();
10630
11053
  init_workspaces();
@@ -10637,6 +11060,11 @@ init_complexity();
10637
11060
  init_viz();
10638
11061
  init_bm25();
10639
11062
  init_rules();
11063
+ init_model();
11064
+ init_embed();
11065
+ init_search();
11066
+ init_endpoint();
11067
+ init_util();
10640
11068
  var HELP = `codeindex engine v${ENGINE_VERSION} \u2014 deterministic repo indexing
10641
11069
 
10642
11070
  Usage: engine.mjs <command> [flags]
@@ -10654,7 +11082,18 @@ Commands:
10654
11082
  churn Per-file git commit counts (JSON; --since <ref> to bound)
10655
11083
  grep Search: cli.mjs grep <pattern> --repo <dir> (JSON hits)
10656
11084
  search Keyless BM25 lexical search over symbol names, path segments,
10657
- markdown headings and summaries: cli.mjs search "<query>" --repo <dir>
11085
+ markdown headings and summaries: cli.mjs search "<query>" --repo <dir>.
11086
+ --semantic fuses in an embedding tier (RRF) \u2014 the HTTP endpoint
11087
+ (CODEINDEX_EMBED_ENDPOINT) if set, else a local static model;
11088
+ degrades to lexical (exit 0) when neither is available/reachable
11089
+ embed Embedding tiers (opt-in). Precedence: endpoint > static model:
11090
+ embed status Effective mode (none/static/endpoint), model +
11091
+ EMBED_VERSION, and endpoint reachability (JSON)
11092
+ embed build Write embeddings.bin into --out <dir> (static tier)
11093
+ embed pull Fetch the model asset into CODEINDEX_EMBED_DIR (or
11094
+ <repo>/.codeindex/models/) \u2014 needs CODEINDEX_EMBED_URL
11095
+ embed serve Print (or --run) the docker command that starts the
11096
+ containerized embedding server (rich tier)
10658
11097
  rules Architecture rules (forbidden edges, cycles, orphans) validated
10659
11098
  against the link-graph: --config <codeindex.rules.json>; exits 1
10660
11099
  on any error-severity violation (a CI gate)
@@ -10682,12 +11121,16 @@ Flags:
10682
11121
  --limit <n> Max results for \`search\` (default 20)
10683
11122
  --no-fuzzy \`search\`: disable trigram fuzzy fallback for query terms
10684
11123
  with zero document frequency (default: enabled)
11124
+ --semantic \`search\`: RRF-fuse an embedding tier with lexical \u2014 the
11125
+ HTTP endpoint if CODEINDEX_EMBED_ENDPOINT is set, else a
11126
+ local static model (lexical-only when neither is available)
11127
+ --run \`embed serve\`: run the docker command instead of printing it
10685
11128
  --recall \`callers\`: recall-oriented binding (issue #7) \u2014 relaxes
10686
11129
  the JS/TS import gate to unique repo-wide names and labels
10687
11130
  each site corroborated|unique-name
10688
11131
  `;
10689
11132
  function parseFlags(args2) {
10690
- const flags2 = { repo: process.cwd(), include: [], exclude: [], gitignore: true, noAst: false, fuzzy: true };
11133
+ const flags2 = { repo: process.cwd(), include: [], exclude: [], gitignore: true, noAst: false, fuzzy: true, semantic: false };
10691
11134
  for (let i2 = 0; i2 < args2.length; i2++) {
10692
11135
  const a = args2[i2];
10693
11136
  const next = () => {
@@ -10720,7 +11163,9 @@ function parseFlags(args2) {
10720
11163
  else if (a === "--config") flags2.config = resolve(next());
10721
11164
  else if (a === "--limit") flags2.limit = num();
10722
11165
  else if (a === "--no-fuzzy") flags2.fuzzy = false;
11166
+ else if (a === "--semantic") flags2.semantic = true;
10723
11167
  else if (a === "--recall") flags2.recall = true;
11168
+ else if (a === "--run") flags2.run = true;
10724
11169
  else if (!a.startsWith("--") && flags2.positional === void 0) flags2.positional = a;
10725
11170
  else throw new Error(`unknown flag: ${a}`);
10726
11171
  }
@@ -10756,24 +11201,24 @@ async function runCli(argv) {
10756
11201
  return;
10757
11202
  }
10758
11203
  const flags2 = parseFlags(rest);
10759
- if (!existsSync3(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
11204
+ if (!existsSync4(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
10760
11205
  if (!flags2.noAst) await ensureGrammars(allGrammarKeys());
10761
11206
  if (cmd === "index") {
10762
11207
  if (!flags2.out) throw new Error("index needs --out <dir>");
10763
11208
  const outDir = flags2.out;
10764
11209
  mkdirSync2(outDir, { recursive: true });
10765
- const cachePath = join11(outDir, "cache.json");
11210
+ const cachePath = join12(outDir, "cache.json");
10766
11211
  let cache;
10767
11212
  try {
10768
- const parsed = JSON.parse(readFileSync5(cachePath, "utf8"));
11213
+ const parsed = JSON.parse(readFileSync6(cachePath, "utf8"));
10769
11214
  if (parsed.schemaVersion === SCHEMA_VERSION && parsed.extractorVersion === EXTRACTOR_VERSION) {
10770
11215
  cache = new Map(Object.entries(parsed.files));
10771
11216
  }
10772
11217
  } catch {
10773
11218
  }
10774
11219
  const { scan: scan2, graph, symbols } = buildIndexArtifacts(flags2.repo, { ...scanOptions(flags2), cache, out: outDir });
10775
- writeFileSync3(join11(outDir, "graph.json"), renderGraphJson(graph));
10776
- writeFileSync3(join11(outDir, "symbols.json"), renderSymbolsJson(symbols));
11220
+ writeFileSync3(join12(outDir, "graph.json"), renderGraphJson(graph));
11221
+ writeFileSync3(join12(outDir, "symbols.json"), renderSymbolsJson(symbols));
10777
11222
  const files = {};
10778
11223
  for (const f of scan2.files) {
10779
11224
  const entry = { hash: f.hash, record: f, size: f.size };
@@ -10785,7 +11230,15 @@ async function runCli(argv) {
10785
11230
  cachePath,
10786
11231
  JSON.stringify({ schemaVersion: SCHEMA_VERSION, extractorVersion: EXTRACTOR_VERSION, files }) + "\n"
10787
11232
  );
10788
- process.stderr.write(`codeindex: ${scan2.files.length} files \u2192 ${outDir}/graph.json + symbols.json${scan2.capped ? " (capped)" : ""}
11233
+ let embedNote = "";
11234
+ const modelDir = resolveEmbedModelDir(flags2.repo);
11235
+ const model = modelDir ? loadEmbedModel(modelDir) : void 0;
11236
+ if (model) {
11237
+ const index = buildEmbeddingIndex(scan2, model);
11238
+ writeFileSync3(join12(outDir, "embeddings.bin"), serializeEmbeddings(index));
11239
+ embedNote = ` + embeddings.bin (${index.records.length} records, model ${model.modelId})`;
11240
+ }
11241
+ process.stderr.write(`codeindex: ${scan2.files.length} files \u2192 ${outDir}/graph.json + symbols.json${embedNote}${scan2.capped ? " (capped)" : ""}
10789
11242
  `);
10790
11243
  } else if (cmd === "scan") {
10791
11244
  const { scan: scan2 } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
@@ -10822,11 +11275,138 @@ async function runCli(argv) {
10822
11275
  } else if (cmd === "search") {
10823
11276
  if (!flags2.positional) throw new Error('search needs a query: cli.mjs search "<query>" --repo <dir>');
10824
11277
  const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
10825
- const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy });
10826
- emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
11278
+ if (flags2.semantic) {
11279
+ const endpoint = resolveEmbedEndpoint();
11280
+ const lexical = () => {
11281
+ const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy });
11282
+ emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
11283
+ };
11284
+ if (endpoint) {
11285
+ try {
11286
+ const index = await buildEndpointIndex(scan2);
11287
+ const queryVec = await encodeQueryViaEndpoint(flags2.positional);
11288
+ const results = searchSemantic(scan2, flags2.positional, index, { queryVec, limit: flags2.limit, fuzzy: flags2.fuzzy });
11289
+ emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
11290
+ } catch (e) {
11291
+ process.stderr.write(
11292
+ `codeindex: embedding endpoint ${endpoint} unavailable (${e instanceof Error ? e.message : e}) \u2014 returning lexical results
11293
+ `
11294
+ );
11295
+ lexical();
11296
+ }
11297
+ } else {
11298
+ const modelDir = resolveEmbedModelDir(flags2.repo);
11299
+ const model = modelDir ? loadEmbedModel(modelDir) : void 0;
11300
+ if (!model) {
11301
+ process.stderr.write(
11302
+ "codeindex: semantic search unavailable (no embedding model or endpoint) \u2014 returning lexical results; run `codeindex embed pull` or set CODEINDEX_EMBED_ENDPOINT to enable it\n"
11303
+ );
11304
+ lexical();
11305
+ } else {
11306
+ const index = buildEmbeddingIndex(scan2, model);
11307
+ const results = searchSemantic(scan2, flags2.positional, index, { model, limit: flags2.limit, fuzzy: flags2.fuzzy });
11308
+ emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
11309
+ }
11310
+ }
11311
+ } else {
11312
+ const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy });
11313
+ emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
11314
+ }
11315
+ } else if (cmd === "embed") {
11316
+ const sub = flags2.positional;
11317
+ const modelDir = resolveEmbedModelDir(flags2.repo);
11318
+ if (sub === "status") {
11319
+ const model = modelDir ? loadEmbedModel(modelDir) : void 0;
11320
+ const endpoint = resolveEmbedEndpoint();
11321
+ const mode = endpoint ? "endpoint" : model ? "static" : "none";
11322
+ const status = {
11323
+ embedVersion: EMBED_VERSION,
11324
+ mode,
11325
+ model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
11326
+ endpoint: endpoint ?? null
11327
+ };
11328
+ if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
11329
+ emit(JSON.stringify(status, null, 2) + "\n", flags2.out);
11330
+ } else if (sub === "serve") {
11331
+ const dockerArgs = ["run", "-d", "-p", "8756:8756", "ghcr.io/maxgfr/codeindex-embed:latest"];
11332
+ const oneLiner = `docker ${dockerArgs.join(" ")}`;
11333
+ if (!have("docker")) {
11334
+ process.stderr.write(
11335
+ "codeindex: docker not found on PATH. Install Docker, then run:\n " + oneLiner + "\n"
11336
+ );
11337
+ process.exitCode = 1;
11338
+ return;
11339
+ }
11340
+ if (flags2.run) {
11341
+ process.stderr.write(`codeindex: starting embedding server \u2192 ${oneLiner}
11342
+ `);
11343
+ const res = sh("docker", dockerArgs);
11344
+ if (res.stdout.trim()) process.stdout.write(res.stdout.trim() + "\n");
11345
+ if (!res.ok) {
11346
+ process.stderr.write(res.stderr || "codeindex: docker run failed\n");
11347
+ process.exitCode = 1;
11348
+ return;
11349
+ }
11350
+ process.stderr.write(
11351
+ 'codeindex: server starting on http://localhost:8756 \u2014 then:\n CODEINDEX_EMBED_ENDPOINT=http://localhost:8756 codeindex search "<query>" --repo . --semantic\n'
11352
+ );
11353
+ } else {
11354
+ process.stdout.write(oneLiner + "\n");
11355
+ process.stderr.write(
11356
+ 'codeindex: run the line above to start the embedding server (or `embed serve --run`), then:\n CODEINDEX_EMBED_ENDPOINT=http://localhost:8756 codeindex search "<query>" --repo . --semantic\n'
11357
+ );
11358
+ }
11359
+ } else if (sub === "build") {
11360
+ if (!flags2.out) throw new Error("embed build needs --out <dir>");
11361
+ if (!modelDir) {
11362
+ process.stderr.write("codeindex: no embedding model present \u2014 run `codeindex embed pull` first (nothing written)\n");
11363
+ process.exitCode = 1;
11364
+ return;
11365
+ }
11366
+ const model = loadEmbedModel(modelDir);
11367
+ mkdirSync2(flags2.out, { recursive: true });
11368
+ const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
11369
+ const index = buildEmbeddingIndex(scan2, model);
11370
+ writeFileSync3(join12(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
11371
+ process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
11372
+ `);
11373
+ } else if (sub === "pull") {
11374
+ const url = resolveEmbedPullUrl();
11375
+ if (!url) {
11376
+ process.stderr.write(
11377
+ "codeindex: no model URL configured. The official static-embedding asset is not published yet.\nSet CODEINDEX_EMBED_URL to a model.json URL (optionally CODEINDEX_EMBED_DIR as the destination), then re-run `codeindex embed pull`.\n"
11378
+ );
11379
+ process.exitCode = 1;
11380
+ return;
11381
+ }
11382
+ const destDir = process.env.CODEINDEX_EMBED_DIR ?? join12(flags2.repo, ".codeindex", "models");
11383
+ mkdirSync2(destDir, { recursive: true });
11384
+ process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join12(destDir, "model.json")}
11385
+ `);
11386
+ const res = await fetch(url);
11387
+ if (!res.ok) {
11388
+ process.stderr.write(`codeindex: pull failed \u2014 HTTP ${res.status} from ${url}
11389
+ `);
11390
+ process.exitCode = 1;
11391
+ return;
11392
+ }
11393
+ const body2 = await res.text();
11394
+ try {
11395
+ JSON.parse(body2);
11396
+ } catch {
11397
+ process.stderr.write("codeindex: pull failed \u2014 response is not a valid model.json (expected JSON)\n");
11398
+ process.exitCode = 1;
11399
+ return;
11400
+ }
11401
+ writeFileSync3(join12(destDir, "model.json"), body2);
11402
+ process.stderr.write(`codeindex: model written to ${join12(destDir, "model.json")}
11403
+ `);
11404
+ } else {
11405
+ throw new Error("embed needs a subcommand: status | build | pull | serve");
11406
+ }
10827
11407
  } else if (cmd === "rules") {
10828
11408
  if (!flags2.config) throw new Error("rules needs --config <codeindex.rules.json>");
10829
- const rules = parseRules(JSON.parse(readFileSync5(flags2.config, "utf8")));
11409
+ const rules = parseRules(JSON.parse(readFileSync6(flags2.config, "utf8")));
10830
11410
  const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
10831
11411
  const violations = checkRules(graph, rules);
10832
11412
  const errors = violations.filter((v) => v.severity === "error").length;
@@ -10887,14 +11467,18 @@ ${HELP}`);
10887
11467
  }
10888
11468
  export {
10889
11469
  DEFAULT_MAX_FILES,
11470
+ EMBED_VERSION,
10890
11471
  ENGINE_VERSION,
10891
11472
  EXTRACTOR_VERSION,
10892
11473
  MARKDOWN_EXT,
10893
11474
  SCHEMA_VERSION,
10894
11475
  allGrammarKeys,
10895
11476
  applyCentrality,
11477
+ basicTokenize,
10896
11478
  betweennessOf,
10897
11479
  buildCallerIndex,
11480
+ buildEmbeddingIndex,
11481
+ buildEndpointIndex,
10898
11482
  buildGraph,
10899
11483
  buildIndexArtifacts,
10900
11484
  buildModules,
@@ -10917,11 +11501,17 @@ export {
10917
11501
  computeSymbolRefs,
10918
11502
  computeTestMap,
10919
11503
  deleteMemory,
11504
+ deserializeEmbeddings,
10920
11505
  detectCommunities,
10921
11506
  detectWorkspaces,
10922
11507
  diffFiles,
10923
11508
  diffHunks,
11509
+ embedEndpointUrl,
11510
+ embedViaEndpoint,
11511
+ embeddingUnits,
10924
11512
  enclosingSymbol,
11513
+ encode,
11514
+ encodeQueryViaEndpoint,
10925
11515
  ensureGrammars,
10926
11516
  escapeRegExp,
10927
11517
  extToLang,
@@ -10937,10 +11527,13 @@ export {
10937
11527
  grammarKeyForExt,
10938
11528
  grammarReady,
10939
11529
  grepRepo,
11530
+ hasEmbedModel,
10940
11531
  have,
10941
11532
  headCommit,
11533
+ healthzUrl,
10942
11534
  insertAfterSymbol,
10943
11535
  insertBeforeSymbol,
11536
+ intDot,
10944
11537
  isCode,
10945
11538
  isDoc,
10946
11539
  isGitWorktree,
@@ -10951,9 +11544,12 @@ export {
10951
11544
  keywords,
10952
11545
  languageOf,
10953
11546
  listMemories,
11547
+ loadEmbedModel,
10954
11548
  pagerankOf,
10955
11549
  parseGitignore,
10956
11550
  parseRules,
11551
+ probeEndpoint,
11552
+ quantize,
10957
11553
  rankHotspots,
10958
11554
  rankedKeywords,
10959
11555
  readMemory,
@@ -10967,14 +11563,20 @@ export {
10967
11563
  resolveBaseRef,
10968
11564
  resolveCallEdges,
10969
11565
  resolveDocLink,
11566
+ resolveEmbedEndpoint,
11567
+ resolveEmbedModelDir,
11568
+ resolveEmbedPullUrl,
10970
11569
  resolveImport,
10971
11570
  resolveUniqueSymbol,
10972
11571
  riskHotspots,
11572
+ roundHalfToEven,
10973
11573
  rrf,
10974
11574
  runCli,
10975
11575
  runMcpServer,
10976
11576
  scanRepo,
10977
11577
  searchIndex,
11578
+ searchSemantic,
11579
+ serializeEmbeddings,
10978
11580
  sh,
10979
11581
  sha1,
10980
11582
  shortHash,
@@ -10984,10 +11586,12 @@ export {
10984
11586
  symbolsOverview,
10985
11587
  testsForModule,
10986
11588
  tierForPath,
11589
+ tokenize,
10987
11590
  uniqueSymbolDefs,
10988
11591
  untestedModules,
10989
11592
  untrackedFiles,
10990
11593
  walk,
11594
+ wordpiece,
10991
11595
  writeMemory
10992
11596
  };
10993
11597
  // "Copyright" and "@license" are already caught by DIRECTIVE_RE.