@maxgfr/codeindex 2.8.1 → 2.10.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 +39 -0
- package/docs/MIGRATION.md +38 -0
- package/package.json +1 -1
- package/scripts/engine.d.mts +59 -2
- package/scripts/engine.mjs +560 -32
- package/src/bm25.ts +118 -9
- package/src/embed/encode.ts +133 -0
- package/src/embed/endpoint.ts +55 -0
- package/src/embed/index.ts +125 -0
- package/src/embed/model.ts +117 -0
- package/src/embed/search.ts +90 -0
- package/src/engine-cli.ts +111 -5
- package/src/engine.ts +21 -0
- package/src/mcp.ts +48 -5
- package/src/types.ts +1 -1
package/scripts/engine.mjs
CHANGED
|
@@ -14,7 +14,7 @@ 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.
|
|
17
|
+
ENGINE_VERSION = "2.10.0";
|
|
18
18
|
SCHEMA_VERSION = 4;
|
|
19
19
|
EXTRACTOR_VERSION = 6;
|
|
20
20
|
}
|
|
@@ -9210,6 +9210,27 @@ function buildDocs(scan2) {
|
|
|
9210
9210
|
}
|
|
9211
9211
|
return docs;
|
|
9212
9212
|
}
|
|
9213
|
+
function charTrigrams(term) {
|
|
9214
|
+
const padded = `^^${term}$$`;
|
|
9215
|
+
const grams = /* @__PURE__ */ new Set();
|
|
9216
|
+
for (let i2 = 0; i2 + 3 <= padded.length; i2++) grams.add(padded.slice(i2, i2 + 3));
|
|
9217
|
+
return grams;
|
|
9218
|
+
}
|
|
9219
|
+
function diceCoefficient(a, b) {
|
|
9220
|
+
if (!a.size || !b.size) return 0;
|
|
9221
|
+
let inter = 0;
|
|
9222
|
+
for (const g of a) if (b.has(g)) inter++;
|
|
9223
|
+
return 2 * inter / (a.size + b.size);
|
|
9224
|
+
}
|
|
9225
|
+
function buildTrigramIndex(docs) {
|
|
9226
|
+
const index = /* @__PURE__ */ new Map();
|
|
9227
|
+
for (const d of docs) {
|
|
9228
|
+
for (const term of d.tf.keys()) {
|
|
9229
|
+
if (!index.has(term)) index.set(term, charTrigrams(term));
|
|
9230
|
+
}
|
|
9231
|
+
}
|
|
9232
|
+
return index;
|
|
9233
|
+
}
|
|
9213
9234
|
function searchIndex(scan2, query, opts = {}) {
|
|
9214
9235
|
const terms = [];
|
|
9215
9236
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9233,35 +9254,81 @@ function searchIndex(scan2, query, opts = {}) {
|
|
|
9233
9254
|
for (const d of docs) if (d.tf.has(t)) count++;
|
|
9234
9255
|
df.set(t, count);
|
|
9235
9256
|
}
|
|
9257
|
+
const fuzzyEnabled = opts.fuzzy ?? true;
|
|
9258
|
+
const fuzzyCandidates = /* @__PURE__ */ new Map();
|
|
9259
|
+
if (fuzzyEnabled) {
|
|
9260
|
+
const unmatched = terms.filter((t) => df.get(t) === 0);
|
|
9261
|
+
if (unmatched.length) {
|
|
9262
|
+
const trigramIndex = buildTrigramIndex(docs);
|
|
9263
|
+
for (const t of unmatched) {
|
|
9264
|
+
const grams = charTrigrams(t);
|
|
9265
|
+
const candidates = [];
|
|
9266
|
+
for (const [vocabTerm, vocabGrams] of trigramIndex) {
|
|
9267
|
+
const dice = diceCoefficient(grams, vocabGrams);
|
|
9268
|
+
if (dice >= FUZZY_DICE_THRESHOLD) candidates.push({ term: vocabTerm, dice });
|
|
9269
|
+
}
|
|
9270
|
+
candidates.sort((a, b) => b.dice - a.dice || byStr(a.term, b.term));
|
|
9271
|
+
fuzzyCandidates.set(t, candidates.slice(0, FUZZY_CAP));
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
const vocabDf = /* @__PURE__ */ new Map();
|
|
9276
|
+
const dfOfVocabTerm = (term) => {
|
|
9277
|
+
const known = df.get(term) ?? vocabDf.get(term);
|
|
9278
|
+
if (known !== void 0) return known;
|
|
9279
|
+
let count = 0;
|
|
9280
|
+
for (const d of docs) if (d.tf.has(term)) count++;
|
|
9281
|
+
vocabDf.set(term, count);
|
|
9282
|
+
return count;
|
|
9283
|
+
};
|
|
9236
9284
|
const results = [];
|
|
9237
9285
|
for (const d of docs) {
|
|
9238
9286
|
let score = 0;
|
|
9239
9287
|
const matched = [];
|
|
9288
|
+
const symbolTerms = /* @__PURE__ */ new Set();
|
|
9289
|
+
const fuzzyHit = /* @__PURE__ */ new Set();
|
|
9240
9290
|
for (const t of terms) {
|
|
9241
9291
|
const tf = d.tf.get(t);
|
|
9242
|
-
if (
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9292
|
+
if (tf) {
|
|
9293
|
+
matched.push(t);
|
|
9294
|
+
symbolTerms.add(t);
|
|
9295
|
+
const idf = Math.log(1 + (n - df.get(t) + 0.5) / (df.get(t) + 0.5));
|
|
9296
|
+
score += idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * d.len / avgLen));
|
|
9297
|
+
continue;
|
|
9298
|
+
}
|
|
9299
|
+
const candidates = fuzzyCandidates.get(t);
|
|
9300
|
+
if (!candidates) continue;
|
|
9301
|
+
for (const cand of candidates) {
|
|
9302
|
+
const ctf = d.tf.get(cand.term);
|
|
9303
|
+
if (!ctf) continue;
|
|
9304
|
+
const cdf = dfOfVocabTerm(cand.term);
|
|
9305
|
+
const idf = Math.log(1 + (n - cdf + 0.5) / (cdf + 0.5));
|
|
9306
|
+
const contribution = idf * (ctf * (K1 + 1)) / (ctf + K1 * (1 - B + B * d.len / avgLen));
|
|
9307
|
+
score += contribution * cand.dice;
|
|
9308
|
+
symbolTerms.add(cand.term);
|
|
9309
|
+
fuzzyHit.add(t);
|
|
9310
|
+
}
|
|
9246
9311
|
}
|
|
9247
|
-
if (!matched.length) continue;
|
|
9312
|
+
if (!matched.length && !fuzzyHit.size) continue;
|
|
9248
9313
|
const scored = d.symbols.map((name2) => {
|
|
9249
9314
|
const toks = new Set(subtokens(name2));
|
|
9250
9315
|
let hits = 0;
|
|
9251
|
-
for (const t of
|
|
9316
|
+
for (const t of symbolTerms) if (toks.has(t)) hits++;
|
|
9252
9317
|
return { name: name2, hits };
|
|
9253
9318
|
}).filter((s) => s.hits > 0).sort((a, b) => b.hits - a.hits || byStr(a.name, b.name));
|
|
9254
|
-
|
|
9319
|
+
const result = {
|
|
9255
9320
|
file: d.file,
|
|
9256
9321
|
score: Number(score.toFixed(4)),
|
|
9257
9322
|
matchedTerms: matched.sort(byStr),
|
|
9258
9323
|
topSymbols: scored.slice(0, TOP_SYMBOLS).map((s) => s.name)
|
|
9259
|
-
}
|
|
9324
|
+
};
|
|
9325
|
+
if (fuzzyHit.size) result.fuzzyTerms = [...fuzzyHit].sort(byStr);
|
|
9326
|
+
results.push(result);
|
|
9260
9327
|
}
|
|
9261
9328
|
results.sort((a, b) => b.score - a.score || byStr(a.file, b.file));
|
|
9262
9329
|
return results.slice(0, opts.limit ?? DEFAULT_LIMIT);
|
|
9263
9330
|
}
|
|
9264
|
-
var K1, B, DEFAULT_LIMIT, TOP_SYMBOLS;
|
|
9331
|
+
var K1, B, DEFAULT_LIMIT, TOP_SYMBOLS, FUZZY_DICE_THRESHOLD, FUZZY_CAP;
|
|
9265
9332
|
var init_bm25 = __esm({
|
|
9266
9333
|
"src/bm25.ts"() {
|
|
9267
9334
|
"use strict";
|
|
@@ -9271,6 +9338,279 @@ var init_bm25 = __esm({
|
|
|
9271
9338
|
B = 0.75;
|
|
9272
9339
|
DEFAULT_LIMIT = 20;
|
|
9273
9340
|
TOP_SYMBOLS = 5;
|
|
9341
|
+
FUZZY_DICE_THRESHOLD = 0.6;
|
|
9342
|
+
FUZZY_CAP = 3;
|
|
9343
|
+
}
|
|
9344
|
+
});
|
|
9345
|
+
|
|
9346
|
+
// src/embed/model.ts
|
|
9347
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
9348
|
+
import { join as join10 } from "path";
|
|
9349
|
+
function resolveEmbedModelDir(repo) {
|
|
9350
|
+
const env = process.env.CODEINDEX_EMBED_DIR;
|
|
9351
|
+
const candidates = [];
|
|
9352
|
+
if (env) candidates.push(env);
|
|
9353
|
+
if (repo) candidates.push(join10(repo, ".codeindex", DEFAULT_EMBED_DIRNAME));
|
|
9354
|
+
candidates.push(join10(process.cwd(), ".codeindex", DEFAULT_EMBED_DIRNAME));
|
|
9355
|
+
for (const c2 of candidates) {
|
|
9356
|
+
if (existsSync3(join10(c2, "model.json"))) return c2;
|
|
9357
|
+
}
|
|
9358
|
+
return void 0;
|
|
9359
|
+
}
|
|
9360
|
+
function hasEmbedModel(repo) {
|
|
9361
|
+
return resolveEmbedModelDir(repo) !== void 0;
|
|
9362
|
+
}
|
|
9363
|
+
function loadEmbedModel(dir) {
|
|
9364
|
+
if (!dir) return void 0;
|
|
9365
|
+
const path = join10(dir, "model.json");
|
|
9366
|
+
if (!existsSync3(path)) return void 0;
|
|
9367
|
+
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
9368
|
+
const { modelId, dim, vocab, weights } = raw;
|
|
9369
|
+
if (typeof modelId !== "string" || !modelId) throw new Error(`embed model: missing modelId in ${path}`);
|
|
9370
|
+
if (!Number.isInteger(dim) || dim <= 0) throw new Error(`embed model: bad dim ${dim} in ${path}`);
|
|
9371
|
+
if (!Array.isArray(vocab) || !Array.isArray(weights) || vocab.length !== weights.length) {
|
|
9372
|
+
throw new Error(`embed model: vocab/weights length mismatch in ${path}`);
|
|
9373
|
+
}
|
|
9374
|
+
const vocabSize = vocab.length;
|
|
9375
|
+
const flat = new Float64Array(vocabSize * dim);
|
|
9376
|
+
const vmap = /* @__PURE__ */ new Map();
|
|
9377
|
+
for (let i2 = 0; i2 < vocabSize; i2++) {
|
|
9378
|
+
const tok = vocab[i2];
|
|
9379
|
+
if (typeof tok !== "string") throw new Error(`embed model: non-string vocab entry at ${i2}`);
|
|
9380
|
+
if (!vmap.has(tok)) vmap.set(tok, i2);
|
|
9381
|
+
const row = weights[i2];
|
|
9382
|
+
if (!Array.isArray(row) || row.length !== dim) {
|
|
9383
|
+
throw new Error(`embed model: row ${i2} has length ${row?.length}, expected ${dim}`);
|
|
9384
|
+
}
|
|
9385
|
+
for (let d = 0; d < dim; d++) flat[i2 * dim + d] = Number(row[d]);
|
|
9386
|
+
}
|
|
9387
|
+
const unk = typeof raw.unk === "string" ? raw.unk : "[UNK]";
|
|
9388
|
+
const unkId = vmap.has(unk) ? vmap.get(unk) : -1;
|
|
9389
|
+
return { modelId, dim, unk, unkId, vocabSize, vocab: vmap, weights: flat };
|
|
9390
|
+
}
|
|
9391
|
+
function resolveEmbedPullUrl() {
|
|
9392
|
+
const url = process.env.CODEINDEX_EMBED_URL;
|
|
9393
|
+
return url && url.trim() ? url.trim() : void 0;
|
|
9394
|
+
}
|
|
9395
|
+
var EMBED_VERSION, DEFAULT_EMBED_DIRNAME;
|
|
9396
|
+
var init_model = __esm({
|
|
9397
|
+
"src/embed/model.ts"() {
|
|
9398
|
+
"use strict";
|
|
9399
|
+
EMBED_VERSION = 1;
|
|
9400
|
+
DEFAULT_EMBED_DIRNAME = "models";
|
|
9401
|
+
}
|
|
9402
|
+
});
|
|
9403
|
+
|
|
9404
|
+
// src/embed/encode.ts
|
|
9405
|
+
function basicTokenize(text) {
|
|
9406
|
+
const spaced = foldText(text).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
|
9407
|
+
const out2 = [];
|
|
9408
|
+
for (const part of spaced.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
9409
|
+
if (part) out2.push(part);
|
|
9410
|
+
}
|
|
9411
|
+
return out2;
|
|
9412
|
+
}
|
|
9413
|
+
function wordpiece(word, model) {
|
|
9414
|
+
if (!word) return [];
|
|
9415
|
+
const ids = [];
|
|
9416
|
+
let start2 = 0;
|
|
9417
|
+
const n = word.length;
|
|
9418
|
+
while (start2 < n) {
|
|
9419
|
+
let end = n;
|
|
9420
|
+
let match = -1;
|
|
9421
|
+
while (end > start2) {
|
|
9422
|
+
const piece = start2 === 0 ? word.slice(start2, end) : "##" + word.slice(start2, end);
|
|
9423
|
+
const id = model.vocab.get(piece);
|
|
9424
|
+
if (id !== void 0) {
|
|
9425
|
+
match = id;
|
|
9426
|
+
break;
|
|
9427
|
+
}
|
|
9428
|
+
end--;
|
|
9429
|
+
}
|
|
9430
|
+
if (match === -1) return model.unkId >= 0 ? [model.unkId] : [];
|
|
9431
|
+
ids.push(match);
|
|
9432
|
+
start2 = end;
|
|
9433
|
+
}
|
|
9434
|
+
return ids;
|
|
9435
|
+
}
|
|
9436
|
+
function tokenize(text, model) {
|
|
9437
|
+
const ids = [];
|
|
9438
|
+
for (const word of basicTokenize(text)) {
|
|
9439
|
+
for (const id of wordpiece(word, model)) ids.push(id);
|
|
9440
|
+
}
|
|
9441
|
+
return ids;
|
|
9442
|
+
}
|
|
9443
|
+
function roundHalfToEven(x) {
|
|
9444
|
+
const f = Math.floor(x);
|
|
9445
|
+
const diff = x - f;
|
|
9446
|
+
if (diff < 0.5) return f;
|
|
9447
|
+
if (diff > 0.5) return f + 1;
|
|
9448
|
+
return f % 2 === 0 ? f : f + 1;
|
|
9449
|
+
}
|
|
9450
|
+
function encode(model, text) {
|
|
9451
|
+
const { dim, weights } = model;
|
|
9452
|
+
const out2 = new Int8Array(dim);
|
|
9453
|
+
const ids = tokenize(text, model);
|
|
9454
|
+
if (ids.length === 0) return out2;
|
|
9455
|
+
const pooled = new Float64Array(dim);
|
|
9456
|
+
for (const id of ids) {
|
|
9457
|
+
const base = id * dim;
|
|
9458
|
+
for (let d = 0; d < dim; d++) pooled[d] += weights[base + d];
|
|
9459
|
+
}
|
|
9460
|
+
const inv = 1 / ids.length;
|
|
9461
|
+
for (let d = 0; d < dim; d++) pooled[d] *= inv;
|
|
9462
|
+
let sumsq = 0;
|
|
9463
|
+
for (let d = 0; d < dim; d++) sumsq += pooled[d] * pooled[d];
|
|
9464
|
+
const norm2 = Math.sqrt(sumsq);
|
|
9465
|
+
if (norm2 === 0) return out2;
|
|
9466
|
+
for (let d = 0; d < dim; d++) {
|
|
9467
|
+
let q = roundHalfToEven(pooled[d] / norm2 * QUANT);
|
|
9468
|
+
if (q > QUANT) q = QUANT;
|
|
9469
|
+
else if (q < -QUANT) q = -QUANT;
|
|
9470
|
+
out2[d] = q;
|
|
9471
|
+
}
|
|
9472
|
+
return out2;
|
|
9473
|
+
}
|
|
9474
|
+
function intDot(a, b) {
|
|
9475
|
+
const n = Math.min(a.length, b.length);
|
|
9476
|
+
let dot = 0;
|
|
9477
|
+
for (let i2 = 0; i2 < n; i2++) dot += a[i2] * b[i2];
|
|
9478
|
+
return dot;
|
|
9479
|
+
}
|
|
9480
|
+
var QUANT;
|
|
9481
|
+
var init_encode = __esm({
|
|
9482
|
+
"src/embed/encode.ts"() {
|
|
9483
|
+
"use strict";
|
|
9484
|
+
init_util();
|
|
9485
|
+
QUANT = 127;
|
|
9486
|
+
}
|
|
9487
|
+
});
|
|
9488
|
+
|
|
9489
|
+
// src/embed/index.ts
|
|
9490
|
+
function symbolText(rel, name2, signature, summary) {
|
|
9491
|
+
return [name2, signature ?? "", summary ?? "", rel.replace(/\//g, " ")].join("\n");
|
|
9492
|
+
}
|
|
9493
|
+
function fileText(rel, title, summary, headings) {
|
|
9494
|
+
return [title ?? "", summary ?? "", ...headings, rel.replace(/\//g, " ")].join("\n");
|
|
9495
|
+
}
|
|
9496
|
+
function buildEmbeddingIndex(scan2, model) {
|
|
9497
|
+
const records = [];
|
|
9498
|
+
for (const f of scan2.files) {
|
|
9499
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9500
|
+
let hadSymbol = false;
|
|
9501
|
+
for (const s of f.symbols) {
|
|
9502
|
+
if (seen.has(s.name)) continue;
|
|
9503
|
+
seen.add(s.name);
|
|
9504
|
+
hadSymbol = true;
|
|
9505
|
+
records.push({
|
|
9506
|
+
file: f.rel,
|
|
9507
|
+
symbol: s.name,
|
|
9508
|
+
line: s.line,
|
|
9509
|
+
vec: encode(model, symbolText(f.rel, s.name, s.signature, f.summary))
|
|
9510
|
+
});
|
|
9511
|
+
}
|
|
9512
|
+
if (!hadSymbol) {
|
|
9513
|
+
const text = fileText(f.rel, f.title, f.summary, f.headings);
|
|
9514
|
+
if (text.replace(/\s+/g, "")) {
|
|
9515
|
+
records.push({ file: f.rel, vec: encode(model, text) });
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9519
|
+
return { embedVersion: EMBED_VERSION, modelId: model.modelId, dim: model.dim, records };
|
|
9520
|
+
}
|
|
9521
|
+
function serializeEmbeddings(index) {
|
|
9522
|
+
const header = JSON.stringify({
|
|
9523
|
+
embedVersion: index.embedVersion,
|
|
9524
|
+
modelId: index.modelId,
|
|
9525
|
+
dim: index.dim,
|
|
9526
|
+
count: index.records.length,
|
|
9527
|
+
records: index.records.map((r) => ({ file: r.file, symbol: r.symbol ?? "", line: r.line ?? 0 }))
|
|
9528
|
+
});
|
|
9529
|
+
const headerBuf = Buffer.from(header, "utf8");
|
|
9530
|
+
const body2 = Buffer.alloc(index.records.length * index.dim);
|
|
9531
|
+
let off = 0;
|
|
9532
|
+
for (const r of index.records) {
|
|
9533
|
+
for (let d = 0; d < index.dim; d++) body2.writeInt8(r.vec[d] ?? 0, off++);
|
|
9534
|
+
}
|
|
9535
|
+
const out2 = Buffer.alloc(8 + headerBuf.length + body2.length);
|
|
9536
|
+
out2.write(MAGIC, 0, "ascii");
|
|
9537
|
+
out2.writeUInt32LE(headerBuf.length, 4);
|
|
9538
|
+
headerBuf.copy(out2, 8);
|
|
9539
|
+
body2.copy(out2, 8 + headerBuf.length);
|
|
9540
|
+
return out2;
|
|
9541
|
+
}
|
|
9542
|
+
function deserializeEmbeddings(bytes) {
|
|
9543
|
+
const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
9544
|
+
if (buf.length < 8 || buf.toString("ascii", 0, 4) !== MAGIC) {
|
|
9545
|
+
throw new Error("embeddings.bin: bad magic (not a codeindex embeddings artifact)");
|
|
9546
|
+
}
|
|
9547
|
+
const headerLen = buf.readUInt32LE(4);
|
|
9548
|
+
const header = JSON.parse(buf.toString("utf8", 8, 8 + headerLen));
|
|
9549
|
+
const bodyOff = 8 + headerLen;
|
|
9550
|
+
const { dim } = header;
|
|
9551
|
+
const records = header.records.map((m, i2) => {
|
|
9552
|
+
const vec = new Int8Array(dim);
|
|
9553
|
+
for (let d = 0; d < dim; d++) vec[d] = buf.readInt8(bodyOff + i2 * dim + d);
|
|
9554
|
+
const rec = { file: m.file, vec };
|
|
9555
|
+
if (m.symbol) rec.symbol = m.symbol;
|
|
9556
|
+
if (m.line) rec.line = m.line;
|
|
9557
|
+
return rec;
|
|
9558
|
+
});
|
|
9559
|
+
return { embedVersion: header.embedVersion, modelId: header.modelId, dim, records };
|
|
9560
|
+
}
|
|
9561
|
+
var MAGIC;
|
|
9562
|
+
var init_embed = __esm({
|
|
9563
|
+
"src/embed/index.ts"() {
|
|
9564
|
+
"use strict";
|
|
9565
|
+
init_encode();
|
|
9566
|
+
init_model();
|
|
9567
|
+
MAGIC = "CIE1";
|
|
9568
|
+
}
|
|
9569
|
+
});
|
|
9570
|
+
|
|
9571
|
+
// src/embed/search.ts
|
|
9572
|
+
function searchSemantic(scan2, query, index, opts = {}) {
|
|
9573
|
+
const limit = opts.limit ?? DEFAULT_LIMIT2;
|
|
9574
|
+
const lexical = searchIndex(scan2, query, { limit: Math.max(limit, 50), fuzzy: opts.fuzzy });
|
|
9575
|
+
if (!opts.model || !index || index.records.length === 0) {
|
|
9576
|
+
return lexical.slice(0, limit);
|
|
9577
|
+
}
|
|
9578
|
+
const q = encode(opts.model, query);
|
|
9579
|
+
const bestByFile = /* @__PURE__ */ new Map();
|
|
9580
|
+
for (const r of index.records) {
|
|
9581
|
+
const dot = intDot(q, r.vec);
|
|
9582
|
+
const prev = bestByFile.get(r.file);
|
|
9583
|
+
if (!prev || dot > prev.score) bestByFile.set(r.file, { score: dot, symbol: r.symbol });
|
|
9584
|
+
}
|
|
9585
|
+
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);
|
|
9586
|
+
const lexList = lexical.map((r) => r.file);
|
|
9587
|
+
const fused = rrf([lexList, semList], (f) => f, opts.rrfK ?? RRF_K);
|
|
9588
|
+
const lexByFile = new Map(lexical.map((r) => [r.file, r]));
|
|
9589
|
+
const results = [...fused.entries()].sort((a, b) => b[1] - a[1] || byStr(a[0], b[0])).map(([file, score]) => {
|
|
9590
|
+
const lex = lexByFile.get(file);
|
|
9591
|
+
const res = {
|
|
9592
|
+
file,
|
|
9593
|
+
score: Number(score.toFixed(4)),
|
|
9594
|
+
matchedTerms: lex?.matchedTerms ?? [],
|
|
9595
|
+
topSymbols: lex?.topSymbols ?? []
|
|
9596
|
+
};
|
|
9597
|
+
const sem = bestByFile.get(file);
|
|
9598
|
+
if (sem?.symbol) res.semanticSymbol = sem.symbol;
|
|
9599
|
+
if (lex?.fuzzyTerms) res.fuzzyTerms = lex.fuzzyTerms;
|
|
9600
|
+
return res;
|
|
9601
|
+
});
|
|
9602
|
+
return results.slice(0, limit);
|
|
9603
|
+
}
|
|
9604
|
+
var DEFAULT_LIMIT2, RRF_K;
|
|
9605
|
+
var init_search = __esm({
|
|
9606
|
+
"src/embed/search.ts"() {
|
|
9607
|
+
"use strict";
|
|
9608
|
+
init_util();
|
|
9609
|
+
init_sort();
|
|
9610
|
+
init_bm25();
|
|
9611
|
+
init_encode();
|
|
9612
|
+
DEFAULT_LIMIT2 = 20;
|
|
9613
|
+
RRF_K = 60;
|
|
9274
9614
|
}
|
|
9275
9615
|
});
|
|
9276
9616
|
|
|
@@ -9578,7 +9918,7 @@ var init_deadcode = __esm({
|
|
|
9578
9918
|
});
|
|
9579
9919
|
|
|
9580
9920
|
// src/complexity.ts
|
|
9581
|
-
import { join as
|
|
9921
|
+
import { join as join11 } from "path";
|
|
9582
9922
|
function complexityOfSource(source) {
|
|
9583
9923
|
return 1 + (source.match(BRANCH_RE) ?? []).length;
|
|
9584
9924
|
}
|
|
@@ -9588,7 +9928,7 @@ function symbolComplexity(scan2, rel, top = 50) {
|
|
|
9588
9928
|
if (f.kind !== "code") continue;
|
|
9589
9929
|
if (rel && f.rel !== rel) continue;
|
|
9590
9930
|
if (!f.symbols.length) continue;
|
|
9591
|
-
const lines = readText(
|
|
9931
|
+
const lines = readText(join11(scan2.root, f.rel)).split("\n");
|
|
9592
9932
|
for (const s of f.symbols) {
|
|
9593
9933
|
if (s.kind === "reexport" || s.kind === "reexport-all") continue;
|
|
9594
9934
|
const end = s.endLine ?? s.line;
|
|
@@ -9603,7 +9943,7 @@ function symbolComplexity(scan2, rel, top = 50) {
|
|
|
9603
9943
|
}
|
|
9604
9944
|
function riskHotspots(scan2, churn, top = 20) {
|
|
9605
9945
|
const out2 = scan2.files.filter((f) => f.kind === "code").map((f) => {
|
|
9606
|
-
const complexity = complexityOfSource(readText(
|
|
9946
|
+
const complexity = complexityOfSource(readText(join11(scan2.root, f.rel)));
|
|
9607
9947
|
const commits = churn.get(f.rel) ?? 0;
|
|
9608
9948
|
return { file: f.rel, complexity, commits, score: (commits + 1) * complexity };
|
|
9609
9949
|
});
|
|
@@ -9802,10 +10142,31 @@ function callTool(name2, args2) {
|
|
|
9802
10142
|
if (name2 === "search") {
|
|
9803
10143
|
const query = str(args2.query);
|
|
9804
10144
|
if (!query) throw new Error("`query` is required");
|
|
9805
|
-
const
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
10145
|
+
const scan2 = scanRepo(repo, scanOpts);
|
|
10146
|
+
const limit = typeof args2.limit === "number" ? args2.limit : void 0;
|
|
10147
|
+
const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
|
|
10148
|
+
if (args2.semantic === true) {
|
|
10149
|
+
const modelDir = resolveEmbedModelDir(repo);
|
|
10150
|
+
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
10151
|
+
if (model) {
|
|
10152
|
+
const index = buildEmbeddingIndex(scan2, model);
|
|
10153
|
+
return JSON.stringify(searchSemantic(scan2, query, index, { model, limit, fuzzy }), null, 2);
|
|
10154
|
+
}
|
|
10155
|
+
}
|
|
10156
|
+
return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
|
|
10157
|
+
}
|
|
10158
|
+
if (name2 === "embed_status") {
|
|
10159
|
+
const modelDir = resolveEmbedModelDir(repo);
|
|
10160
|
+
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
10161
|
+
return JSON.stringify(
|
|
10162
|
+
{
|
|
10163
|
+
embedVersion: EMBED_VERSION,
|
|
10164
|
+
model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
|
|
10165
|
+
endpoint: process.env.CODEINDEX_EMBED_ENDPOINT ?? null
|
|
10166
|
+
},
|
|
10167
|
+
null,
|
|
10168
|
+
2
|
|
10169
|
+
);
|
|
9809
10170
|
}
|
|
9810
10171
|
if (name2 === "check_rules") {
|
|
9811
10172
|
const rules = parseRules(args2.rules);
|
|
@@ -9893,6 +10254,9 @@ var init_mcp = __esm({
|
|
|
9893
10254
|
init_memory();
|
|
9894
10255
|
init_bm25();
|
|
9895
10256
|
init_rules();
|
|
10257
|
+
init_model();
|
|
10258
|
+
init_embed();
|
|
10259
|
+
init_search();
|
|
9896
10260
|
repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
|
|
9897
10261
|
scopeProps = {
|
|
9898
10262
|
scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
|
|
@@ -10105,18 +10469,31 @@ var init_mcp = __esm({
|
|
|
10105
10469
|
},
|
|
10106
10470
|
{
|
|
10107
10471
|
name: "search",
|
|
10108
|
-
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.',
|
|
10472
|
+
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).',
|
|
10109
10473
|
inputSchema: {
|
|
10110
10474
|
type: "object",
|
|
10111
10475
|
properties: {
|
|
10112
10476
|
...repoProp,
|
|
10113
10477
|
...scopeProps,
|
|
10114
10478
|
query: { type: "string", description: "Natural-language or identifier query" },
|
|
10115
|
-
limit: { type: "number", description: "Max results (default 20)" }
|
|
10479
|
+
limit: { type: "number", description: "Max results (default 20)" },
|
|
10480
|
+
fuzzy: {
|
|
10481
|
+
type: "boolean",
|
|
10482
|
+
description: "Trigram fuzzy fallback for query terms with zero document frequency (default true)"
|
|
10483
|
+
},
|
|
10484
|
+
semantic: {
|
|
10485
|
+
type: "boolean",
|
|
10486
|
+
description: "RRF-fuse the deterministic static-embedding tier with lexical when a model asset is present (default false; silently lexical-only when no model)"
|
|
10487
|
+
}
|
|
10116
10488
|
},
|
|
10117
10489
|
required: ["repo", "query"]
|
|
10118
10490
|
}
|
|
10119
10491
|
},
|
|
10492
|
+
{
|
|
10493
|
+
name: "embed_status",
|
|
10494
|
+
description: "Report the deterministic static-embedding tier: whether a model asset is resolved (opt-in, never shipped in the package), its modelId/dim, EMBED_VERSION, and any configured HTTP endpoint. Use to check whether `search` with semantic:true will actually fuse embeddings or degrade to lexical.",
|
|
10495
|
+
inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] }
|
|
10496
|
+
},
|
|
10120
10497
|
{
|
|
10121
10498
|
name: "check_rules",
|
|
10122
10499
|
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.',
|
|
@@ -10531,6 +10908,41 @@ init_pipeline();
|
|
|
10531
10908
|
init_git();
|
|
10532
10909
|
init_grep();
|
|
10533
10910
|
init_bm25();
|
|
10911
|
+
init_model();
|
|
10912
|
+
init_encode();
|
|
10913
|
+
init_embed();
|
|
10914
|
+
init_search();
|
|
10915
|
+
|
|
10916
|
+
// src/embed/endpoint.ts
|
|
10917
|
+
function resolveEmbedEndpoint(opts = {}) {
|
|
10918
|
+
const url = opts.url ?? process.env.CODEINDEX_EMBED_ENDPOINT;
|
|
10919
|
+
return url && url.trim() ? url.trim() : void 0;
|
|
10920
|
+
}
|
|
10921
|
+
async function embedViaEndpoint(texts, opts = {}) {
|
|
10922
|
+
const url = resolveEmbedEndpoint(opts);
|
|
10923
|
+
if (!url) throw new Error("no embedding endpoint configured (set CODEINDEX_EMBED_ENDPOINT or pass opts.url)");
|
|
10924
|
+
const controller = new AbortController();
|
|
10925
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 3e4);
|
|
10926
|
+
try {
|
|
10927
|
+
const res = await fetch(url, {
|
|
10928
|
+
method: "POST",
|
|
10929
|
+
headers: { "content-type": "application/json", ...opts.headers ?? {} },
|
|
10930
|
+
body: JSON.stringify({ texts }),
|
|
10931
|
+
signal: controller.signal
|
|
10932
|
+
});
|
|
10933
|
+
if (!res.ok) throw new Error(`embedding endpoint ${url} returned HTTP ${res.status}`);
|
|
10934
|
+
const data = await res.json();
|
|
10935
|
+
const vectors = data.vectors;
|
|
10936
|
+
if (!Array.isArray(vectors) || !vectors.every((v) => Array.isArray(v) && v.every((x) => typeof x === "number"))) {
|
|
10937
|
+
throw new Error(`embedding endpoint ${url} returned a malformed { vectors } payload`);
|
|
10938
|
+
}
|
|
10939
|
+
return vectors;
|
|
10940
|
+
} finally {
|
|
10941
|
+
clearTimeout(timer);
|
|
10942
|
+
}
|
|
10943
|
+
}
|
|
10944
|
+
|
|
10945
|
+
// src/engine.ts
|
|
10534
10946
|
init_rules();
|
|
10535
10947
|
init_coupling();
|
|
10536
10948
|
init_repomap();
|
|
@@ -10549,8 +10961,8 @@ init_loader();
|
|
|
10549
10961
|
init_pipeline();
|
|
10550
10962
|
init_graph_json();
|
|
10551
10963
|
init_symbols_json();
|
|
10552
|
-
import { existsSync as
|
|
10553
|
-
import { join as
|
|
10964
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
10965
|
+
import { join as join12, resolve } from "path";
|
|
10554
10966
|
init_scan();
|
|
10555
10967
|
init_callers();
|
|
10556
10968
|
init_workspaces();
|
|
@@ -10563,6 +10975,9 @@ init_complexity();
|
|
|
10563
10975
|
init_viz();
|
|
10564
10976
|
init_bm25();
|
|
10565
10977
|
init_rules();
|
|
10978
|
+
init_model();
|
|
10979
|
+
init_embed();
|
|
10980
|
+
init_search();
|
|
10566
10981
|
var HELP = `codeindex engine v${ENGINE_VERSION} \u2014 deterministic repo indexing
|
|
10567
10982
|
|
|
10568
10983
|
Usage: engine.mjs <command> [flags]
|
|
@@ -10580,7 +10995,14 @@ Commands:
|
|
|
10580
10995
|
churn Per-file git commit counts (JSON; --since <ref> to bound)
|
|
10581
10996
|
grep Search: cli.mjs grep <pattern> --repo <dir> (JSON hits)
|
|
10582
10997
|
search Keyless BM25 lexical search over symbol names, path segments,
|
|
10583
|
-
markdown headings and summaries: cli.mjs search "<query>" --repo <dir
|
|
10998
|
+
markdown headings and summaries: cli.mjs search "<query>" --repo <dir>.
|
|
10999
|
+
--semantic fuses in the deterministic static-embedding tier (RRF)
|
|
11000
|
+
when a model is present; degrades to lexical (exit 0) when absent
|
|
11001
|
+
embed Deterministic static-embedding tier (opt-in by model asset):
|
|
11002
|
+
embed status Report the resolved model + EMBED_VERSION (JSON)
|
|
11003
|
+
embed build Write embeddings.bin into --out <dir> from the repo
|
|
11004
|
+
embed pull Fetch the model asset into CODEINDEX_EMBED_DIR (or
|
|
11005
|
+
<repo>/.codeindex/models/) \u2014 needs CODEINDEX_EMBED_URL
|
|
10584
11006
|
rules Architecture rules (forbidden edges, cycles, orphans) validated
|
|
10585
11007
|
against the link-graph: --config <codeindex.rules.json>; exits 1
|
|
10586
11008
|
on any error-severity violation (a CI gate)
|
|
@@ -10606,12 +11028,16 @@ Flags:
|
|
|
10606
11028
|
--no-ast Skip tree-sitter grammars even when present (regex tier)
|
|
10607
11029
|
--config <file> Rules config for \`rules\` (JSON: [{name, from, to, \u2026}])
|
|
10608
11030
|
--limit <n> Max results for \`search\` (default 20)
|
|
11031
|
+
--no-fuzzy \`search\`: disable trigram fuzzy fallback for query terms
|
|
11032
|
+
with zero document frequency (default: enabled)
|
|
11033
|
+
--semantic \`search\`: RRF-fuse the deterministic static-embedding tier
|
|
11034
|
+
with lexical (needs a model asset; lexical-only otherwise)
|
|
10609
11035
|
--recall \`callers\`: recall-oriented binding (issue #7) \u2014 relaxes
|
|
10610
11036
|
the JS/TS import gate to unique repo-wide names and labels
|
|
10611
11037
|
each site corroborated|unique-name
|
|
10612
11038
|
`;
|
|
10613
11039
|
function parseFlags(args2) {
|
|
10614
|
-
const flags2 = { repo: process.cwd(), include: [], exclude: [], gitignore: true, noAst: false };
|
|
11040
|
+
const flags2 = { repo: process.cwd(), include: [], exclude: [], gitignore: true, noAst: false, fuzzy: true, semantic: false };
|
|
10615
11041
|
for (let i2 = 0; i2 < args2.length; i2++) {
|
|
10616
11042
|
const a = args2[i2];
|
|
10617
11043
|
const next = () => {
|
|
@@ -10643,6 +11069,8 @@ function parseFlags(args2) {
|
|
|
10643
11069
|
else if (a === "--since") flags2.since = next();
|
|
10644
11070
|
else if (a === "--config") flags2.config = resolve(next());
|
|
10645
11071
|
else if (a === "--limit") flags2.limit = num();
|
|
11072
|
+
else if (a === "--no-fuzzy") flags2.fuzzy = false;
|
|
11073
|
+
else if (a === "--semantic") flags2.semantic = true;
|
|
10646
11074
|
else if (a === "--recall") flags2.recall = true;
|
|
10647
11075
|
else if (!a.startsWith("--") && flags2.positional === void 0) flags2.positional = a;
|
|
10648
11076
|
else throw new Error(`unknown flag: ${a}`);
|
|
@@ -10679,24 +11107,24 @@ async function runCli(argv) {
|
|
|
10679
11107
|
return;
|
|
10680
11108
|
}
|
|
10681
11109
|
const flags2 = parseFlags(rest);
|
|
10682
|
-
if (!
|
|
11110
|
+
if (!existsSync4(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
|
|
10683
11111
|
if (!flags2.noAst) await ensureGrammars(allGrammarKeys());
|
|
10684
11112
|
if (cmd === "index") {
|
|
10685
11113
|
if (!flags2.out) throw new Error("index needs --out <dir>");
|
|
10686
11114
|
const outDir = flags2.out;
|
|
10687
11115
|
mkdirSync2(outDir, { recursive: true });
|
|
10688
|
-
const cachePath =
|
|
11116
|
+
const cachePath = join12(outDir, "cache.json");
|
|
10689
11117
|
let cache;
|
|
10690
11118
|
try {
|
|
10691
|
-
const parsed = JSON.parse(
|
|
11119
|
+
const parsed = JSON.parse(readFileSync6(cachePath, "utf8"));
|
|
10692
11120
|
if (parsed.schemaVersion === SCHEMA_VERSION && parsed.extractorVersion === EXTRACTOR_VERSION) {
|
|
10693
11121
|
cache = new Map(Object.entries(parsed.files));
|
|
10694
11122
|
}
|
|
10695
11123
|
} catch {
|
|
10696
11124
|
}
|
|
10697
11125
|
const { scan: scan2, graph, symbols } = buildIndexArtifacts(flags2.repo, { ...scanOptions(flags2), cache, out: outDir });
|
|
10698
|
-
writeFileSync3(
|
|
10699
|
-
writeFileSync3(
|
|
11126
|
+
writeFileSync3(join12(outDir, "graph.json"), renderGraphJson(graph));
|
|
11127
|
+
writeFileSync3(join12(outDir, "symbols.json"), renderSymbolsJson(symbols));
|
|
10700
11128
|
const files = {};
|
|
10701
11129
|
for (const f of scan2.files) {
|
|
10702
11130
|
const entry = { hash: f.hash, record: f, size: f.size };
|
|
@@ -10708,7 +11136,15 @@ async function runCli(argv) {
|
|
|
10708
11136
|
cachePath,
|
|
10709
11137
|
JSON.stringify({ schemaVersion: SCHEMA_VERSION, extractorVersion: EXTRACTOR_VERSION, files }) + "\n"
|
|
10710
11138
|
);
|
|
10711
|
-
|
|
11139
|
+
let embedNote = "";
|
|
11140
|
+
const modelDir = resolveEmbedModelDir(flags2.repo);
|
|
11141
|
+
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
11142
|
+
if (model) {
|
|
11143
|
+
const index = buildEmbeddingIndex(scan2, model);
|
|
11144
|
+
writeFileSync3(join12(outDir, "embeddings.bin"), serializeEmbeddings(index));
|
|
11145
|
+
embedNote = ` + embeddings.bin (${index.records.length} records, model ${model.modelId})`;
|
|
11146
|
+
}
|
|
11147
|
+
process.stderr.write(`codeindex: ${scan2.files.length} files \u2192 ${outDir}/graph.json + symbols.json${embedNote}${scan2.capped ? " (capped)" : ""}
|
|
10712
11148
|
`);
|
|
10713
11149
|
} else if (cmd === "scan") {
|
|
10714
11150
|
const { scan: scan2 } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
@@ -10745,11 +11181,86 @@ async function runCli(argv) {
|
|
|
10745
11181
|
} else if (cmd === "search") {
|
|
10746
11182
|
if (!flags2.positional) throw new Error('search needs a query: cli.mjs search "<query>" --repo <dir>');
|
|
10747
11183
|
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
10748
|
-
|
|
10749
|
-
|
|
11184
|
+
if (flags2.semantic) {
|
|
11185
|
+
const modelDir = resolveEmbedModelDir(flags2.repo);
|
|
11186
|
+
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
11187
|
+
if (!model) {
|
|
11188
|
+
process.stderr.write(
|
|
11189
|
+
"codeindex: semantic search unavailable (no embedding model present) \u2014 returning lexical results; run `codeindex embed pull` to enable it\n"
|
|
11190
|
+
);
|
|
11191
|
+
const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy });
|
|
11192
|
+
emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
|
|
11193
|
+
} else {
|
|
11194
|
+
const index = buildEmbeddingIndex(scan2, model);
|
|
11195
|
+
const results = searchSemantic(scan2, flags2.positional, index, { model, limit: flags2.limit, fuzzy: flags2.fuzzy });
|
|
11196
|
+
emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
|
|
11197
|
+
}
|
|
11198
|
+
} else {
|
|
11199
|
+
const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy });
|
|
11200
|
+
emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
|
|
11201
|
+
}
|
|
11202
|
+
} else if (cmd === "embed") {
|
|
11203
|
+
const sub = flags2.positional;
|
|
11204
|
+
const modelDir = resolveEmbedModelDir(flags2.repo);
|
|
11205
|
+
if (sub === "status") {
|
|
11206
|
+
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
11207
|
+
const status = {
|
|
11208
|
+
embedVersion: EMBED_VERSION,
|
|
11209
|
+
model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
|
|
11210
|
+
endpoint: process.env.CODEINDEX_EMBED_ENDPOINT ?? null
|
|
11211
|
+
};
|
|
11212
|
+
emit(JSON.stringify(status, null, 2) + "\n", flags2.out);
|
|
11213
|
+
} else if (sub === "build") {
|
|
11214
|
+
if (!flags2.out) throw new Error("embed build needs --out <dir>");
|
|
11215
|
+
if (!modelDir) {
|
|
11216
|
+
process.stderr.write("codeindex: no embedding model present \u2014 run `codeindex embed pull` first (nothing written)\n");
|
|
11217
|
+
process.exitCode = 1;
|
|
11218
|
+
return;
|
|
11219
|
+
}
|
|
11220
|
+
const model = loadEmbedModel(modelDir);
|
|
11221
|
+
mkdirSync2(flags2.out, { recursive: true });
|
|
11222
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
11223
|
+
const index = buildEmbeddingIndex(scan2, model);
|
|
11224
|
+
writeFileSync3(join12(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
|
|
11225
|
+
process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
|
|
11226
|
+
`);
|
|
11227
|
+
} else if (sub === "pull") {
|
|
11228
|
+
const url = resolveEmbedPullUrl();
|
|
11229
|
+
if (!url) {
|
|
11230
|
+
process.stderr.write(
|
|
11231
|
+
"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"
|
|
11232
|
+
);
|
|
11233
|
+
process.exitCode = 1;
|
|
11234
|
+
return;
|
|
11235
|
+
}
|
|
11236
|
+
const destDir = process.env.CODEINDEX_EMBED_DIR ?? join12(flags2.repo, ".codeindex", "models");
|
|
11237
|
+
mkdirSync2(destDir, { recursive: true });
|
|
11238
|
+
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join12(destDir, "model.json")}
|
|
11239
|
+
`);
|
|
11240
|
+
const res = await fetch(url);
|
|
11241
|
+
if (!res.ok) {
|
|
11242
|
+
process.stderr.write(`codeindex: pull failed \u2014 HTTP ${res.status} from ${url}
|
|
11243
|
+
`);
|
|
11244
|
+
process.exitCode = 1;
|
|
11245
|
+
return;
|
|
11246
|
+
}
|
|
11247
|
+
const body2 = await res.text();
|
|
11248
|
+
try {
|
|
11249
|
+
JSON.parse(body2);
|
|
11250
|
+
} catch {
|
|
11251
|
+
process.stderr.write("codeindex: pull failed \u2014 response is not a valid model.json (expected JSON)\n");
|
|
11252
|
+
process.exitCode = 1;
|
|
11253
|
+
return;
|
|
11254
|
+
}
|
|
11255
|
+
writeFileSync3(join12(destDir, "model.json"), body2);
|
|
11256
|
+
process.stderr.write(`codeindex: model written to ${join12(destDir, "model.json")}
|
|
11257
|
+
`);
|
|
11258
|
+
} else {
|
|
11259
|
+
throw new Error("embed needs a subcommand: pull | build | status");
|
|
11260
|
+
}
|
|
10750
11261
|
} else if (cmd === "rules") {
|
|
10751
11262
|
if (!flags2.config) throw new Error("rules needs --config <codeindex.rules.json>");
|
|
10752
|
-
const rules = parseRules(JSON.parse(
|
|
11263
|
+
const rules = parseRules(JSON.parse(readFileSync6(flags2.config, "utf8")));
|
|
10753
11264
|
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
10754
11265
|
const violations = checkRules(graph, rules);
|
|
10755
11266
|
const errors = violations.filter((v) => v.severity === "error").length;
|
|
@@ -10810,14 +11321,17 @@ ${HELP}`);
|
|
|
10810
11321
|
}
|
|
10811
11322
|
export {
|
|
10812
11323
|
DEFAULT_MAX_FILES,
|
|
11324
|
+
EMBED_VERSION,
|
|
10813
11325
|
ENGINE_VERSION,
|
|
10814
11326
|
EXTRACTOR_VERSION,
|
|
10815
11327
|
MARKDOWN_EXT,
|
|
10816
11328
|
SCHEMA_VERSION,
|
|
10817
11329
|
allGrammarKeys,
|
|
10818
11330
|
applyCentrality,
|
|
11331
|
+
basicTokenize,
|
|
10819
11332
|
betweennessOf,
|
|
10820
11333
|
buildCallerIndex,
|
|
11334
|
+
buildEmbeddingIndex,
|
|
10821
11335
|
buildGraph,
|
|
10822
11336
|
buildIndexArtifacts,
|
|
10823
11337
|
buildModules,
|
|
@@ -10840,11 +11354,14 @@ export {
|
|
|
10840
11354
|
computeSymbolRefs,
|
|
10841
11355
|
computeTestMap,
|
|
10842
11356
|
deleteMemory,
|
|
11357
|
+
deserializeEmbeddings,
|
|
10843
11358
|
detectCommunities,
|
|
10844
11359
|
detectWorkspaces,
|
|
10845
11360
|
diffFiles,
|
|
10846
11361
|
diffHunks,
|
|
11362
|
+
embedViaEndpoint,
|
|
10847
11363
|
enclosingSymbol,
|
|
11364
|
+
encode,
|
|
10848
11365
|
ensureGrammars,
|
|
10849
11366
|
escapeRegExp,
|
|
10850
11367
|
extToLang,
|
|
@@ -10860,10 +11377,12 @@ export {
|
|
|
10860
11377
|
grammarKeyForExt,
|
|
10861
11378
|
grammarReady,
|
|
10862
11379
|
grepRepo,
|
|
11380
|
+
hasEmbedModel,
|
|
10863
11381
|
have,
|
|
10864
11382
|
headCommit,
|
|
10865
11383
|
insertAfterSymbol,
|
|
10866
11384
|
insertBeforeSymbol,
|
|
11385
|
+
intDot,
|
|
10867
11386
|
isCode,
|
|
10868
11387
|
isDoc,
|
|
10869
11388
|
isGitWorktree,
|
|
@@ -10874,6 +11393,7 @@ export {
|
|
|
10874
11393
|
keywords,
|
|
10875
11394
|
languageOf,
|
|
10876
11395
|
listMemories,
|
|
11396
|
+
loadEmbedModel,
|
|
10877
11397
|
pagerankOf,
|
|
10878
11398
|
parseGitignore,
|
|
10879
11399
|
parseRules,
|
|
@@ -10890,14 +11410,20 @@ export {
|
|
|
10890
11410
|
resolveBaseRef,
|
|
10891
11411
|
resolveCallEdges,
|
|
10892
11412
|
resolveDocLink,
|
|
11413
|
+
resolveEmbedEndpoint,
|
|
11414
|
+
resolveEmbedModelDir,
|
|
11415
|
+
resolveEmbedPullUrl,
|
|
10893
11416
|
resolveImport,
|
|
10894
11417
|
resolveUniqueSymbol,
|
|
10895
11418
|
riskHotspots,
|
|
11419
|
+
roundHalfToEven,
|
|
10896
11420
|
rrf,
|
|
10897
11421
|
runCli,
|
|
10898
11422
|
runMcpServer,
|
|
10899
11423
|
scanRepo,
|
|
10900
11424
|
searchIndex,
|
|
11425
|
+
searchSemantic,
|
|
11426
|
+
serializeEmbeddings,
|
|
10901
11427
|
sh,
|
|
10902
11428
|
sha1,
|
|
10903
11429
|
shortHash,
|
|
@@ -10907,10 +11433,12 @@ export {
|
|
|
10907
11433
|
symbolsOverview,
|
|
10908
11434
|
testsForModule,
|
|
10909
11435
|
tierForPath,
|
|
11436
|
+
tokenize,
|
|
10910
11437
|
uniqueSymbolDefs,
|
|
10911
11438
|
untestedModules,
|
|
10912
11439
|
untrackedFiles,
|
|
10913
11440
|
walk,
|
|
11441
|
+
wordpiece,
|
|
10914
11442
|
writeMemory
|
|
10915
11443
|
};
|
|
10916
11444
|
// "Copyright" and "@license" are already caught by DIRECTIVE_RE.
|