@maxgfr/codeindex 2.18.0 → 2.19.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,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.18.0";
17
+ ENGINE_VERSION = "2.19.0";
18
18
  SCHEMA_VERSION = 4;
19
19
  EXTRACTOR_VERSION = 10;
20
20
  }
@@ -10465,583 +10465,166 @@ var init_viz = __esm({
10465
10465
  }
10466
10466
  });
10467
10467
 
10468
- // src/mcp.ts
10469
- var mcp_exports = {};
10470
- __export(mcp_exports, {
10471
- DEFAULT_MAX_RESPONSE_BYTES: () => DEFAULT_MAX_RESPONSE_BYTES,
10472
- capResponse: () => capResponse,
10473
- getArtifacts: () => getArtifacts,
10474
- getScan: () => getScan,
10475
- getScanSummary: () => getScanSummary,
10476
- memoizedEmbedModel: () => memoizedEmbedModel,
10477
- memoizedEmbeddingIndex: () => memoizedEmbeddingIndex,
10478
- negotiateProtocol: () => negotiateProtocol,
10479
- resourceLinkFor: () => resourceLinkFor,
10480
- runMcpServer: () => runMcpServer,
10481
- scanFingerprint: () => scanFingerprint,
10482
- toCacheMap: () => toCacheMap,
10483
- validateArgs: () => validateArgs,
10484
- warmGrammarsForRepo: () => warmGrammarsForRepo,
10485
- warmGrammarsForWalk: () => warmGrammarsForWalk
10486
- });
10487
- import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
10488
- import { isAbsolute, join as join16 } from "path";
10468
+ // src/mcp/protocol.ts
10469
+ import { existsSync as existsSync6 } from "fs";
10470
+ import { join as join16 } from "path";
10489
10471
  import { pathToFileURL as pathToFileURL2 } from "url";
10490
- import { createInterface } from "readline";
10491
- function str(v) {
10492
- return typeof v === "string" && v ? v : void 0;
10493
- }
10494
- function strArray(v) {
10495
- return Array.isArray(v) && v.every((x) => typeof x === "string") && v.length ? v : void 0;
10496
- }
10497
- function num(v) {
10498
- const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
10499
- return Number.isFinite(n) && n > 0 ? n : void 0;
10500
- }
10501
- function errMessage(e) {
10502
- return e instanceof Error ? e.message : String(e);
10503
- }
10504
- function scanFingerprint(scan2) {
10505
- return sha1(scan2.files.map((f) => `${f.rel}:${f.hash}`).join("\n"));
10506
- }
10507
- async function memoizedEmbeddingIndex(key, build) {
10508
- const cacheKey = `${key.mode}:${key.identity}:${scanFingerprint(key.scan)}`;
10509
- if (embeddingIndexCache && embeddingIndexCache.key === cacheKey) return embeddingIndexCache.index;
10510
- const index = await build();
10511
- embeddingIndexCache = { key: cacheKey, index };
10512
- return index;
10472
+ function validateArgs(schema, args2) {
10473
+ const props = schema.properties ?? {};
10474
+ for (const [key, value] of Object.entries(args2)) {
10475
+ if (value === void 0 || value === null) continue;
10476
+ const spec = props[key];
10477
+ if (!spec?.type) continue;
10478
+ const actual = Array.isArray(value) ? "array" : typeof value;
10479
+ if (spec.type === "number") {
10480
+ if (actual === "number") continue;
10481
+ if (actual === "string" && Number.isFinite(Number(value)) && value.trim() !== "") continue;
10482
+ return `\`${key}\` must be a number, got ${actual === "string" ? JSON.stringify(value) : actual}`;
10483
+ }
10484
+ if (spec.type === "array") {
10485
+ if (actual !== "array") return `\`${key}\` must be an array of strings, got ${actual}`;
10486
+ if (spec.items?.type === "string" && !value.every((x) => typeof x === "string")) {
10487
+ return `\`${key}\` must be an array of strings`;
10488
+ }
10489
+ continue;
10490
+ }
10491
+ if (actual !== spec.type) return `\`${key}\` must be a ${spec.type}, got ${actual}`;
10492
+ }
10493
+ return void 0;
10513
10494
  }
10514
- function memoizedEmbedModel(modelDir) {
10515
- let stat;
10495
+ function structuredContentFor(text, capped, hasSchema) {
10496
+ if (capped || !hasSchema) return void 0;
10497
+ let parsed;
10516
10498
  try {
10517
- stat = statSync5(join16(modelDir, "model.json"));
10499
+ parsed = JSON.parse(text);
10518
10500
  } catch {
10519
10501
  return void 0;
10520
10502
  }
10521
- const key = `${modelDir}:${stat.mtimeMs}:${stat.size}`;
10522
- if (embedModelCache && embedModelCache.key === key) return embedModelCache.model;
10523
- const model = loadEmbedModel(modelDir);
10524
- if (model) embedModelCache = { key, model };
10525
- return model;
10526
- }
10527
- function sessionGet(key) {
10528
- const i2 = sessionCaches.findIndex((e) => e.key === key);
10529
- if (i2 < 0) return void 0;
10530
- const [entry] = sessionCaches.splice(i2, 1);
10531
- sessionCaches.unshift(entry);
10532
- return entry;
10503
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
10504
+ return parsed;
10533
10505
  }
10534
- function sessionPut(entry) {
10535
- const i2 = sessionCaches.findIndex((e) => e.key === entry.key);
10536
- if (i2 >= 0) sessionCaches.splice(i2, 1);
10537
- sessionCaches.unshift(entry);
10538
- sessionCaches.length = Math.min(sessionCaches.length, SESSION_CACHE_MAX);
10539
- return entry;
10540
- }
10541
- function sessionClear() {
10542
- sessionCaches.length = 0;
10506
+ function negotiateProtocol(requested) {
10507
+ return typeof requested === "string" && PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL;
10543
10508
  }
10544
- function sessionKey(repo, opts) {
10545
- return repo + "\0" + JSON.stringify({
10546
- scope: opts.scope,
10547
- include: opts.include,
10548
- exclude: opts.exclude,
10549
- gitignore: opts.gitignore,
10550
- ignoreDirs: opts.ignoreDirs,
10551
- maxBytes: opts.maxBytes,
10552
- maxFiles: opts.maxFiles,
10553
- maxCallsPerFile: opts.maxCallsPerFile,
10554
- out: opts.out,
10555
- fullHash: opts.fullHash
10556
- });
10509
+ function capResponse(text, tool, repo, maxBytes) {
10510
+ const bytes = Buffer.byteLength(text, "utf8");
10511
+ if (bytes <= maxBytes) return text;
10512
+ const artifact = ARTIFACT_FOR[tool] ? join16(repo, INDEX_DIR, ARTIFACT_FOR[tool]) : void 0;
10513
+ return JSON.stringify(
10514
+ {
10515
+ truncated: true,
10516
+ tool,
10517
+ bytes,
10518
+ maxBytes,
10519
+ reason: "This response exceeds the configured limit and was withheld rather than sent as an unusable partial payload.",
10520
+ narrower: NARROWER[tool] ?? "narrow the request with `scope`, `include`/`exclude`, or a `limit`",
10521
+ ...artifact && existsSync6(artifact) ? { artifact, artifactNote: "The full result is already on disk here \u2014 read it directly if you need all of it." } : artifact ? { artifactNote: `Run \`codeindex index --repo ${repo} --out ${join16(repo, INDEX_DIR)}\` to get this as a file.` } : {}
10522
+ },
10523
+ null,
10524
+ 2
10525
+ ) + "\n";
10557
10526
  }
10558
- function getScan(repo, opts = {}, walked) {
10559
- const key = sessionKey(repo, opts);
10560
- const hit = sessionGet(key);
10561
- if (hit) {
10562
- const fresh = scanRepo(repo, { ...opts, cache: hit.cacheMap, precomputedWalk: walked });
10563
- if (fresh.contentUnchanged) {
10564
- if (fresh.cacheDirty) hit.cacheMap = toCacheMap(fresh);
10565
- if (hit.scan.commit !== fresh.commit) hit.scan.commit = fresh.commit;
10566
- return hit.scan;
10567
- }
10568
- sessionPut({ key, scan: fresh, cacheMap: toCacheMap(fresh) });
10569
- return fresh;
10570
- }
10571
- const preloaded = preloadSession(repo, { ...opts, precomputedWalk: walked });
10572
- if (preloaded) {
10573
- sessionPut({ key, scan: preloaded.scan, cacheMap: preloaded.cacheMap, arts: preloaded.arts });
10574
- return preloaded.scan;
10527
+ function resourceLinkFor(text, tool) {
10528
+ const artifactName = ARTIFACT_FOR[tool];
10529
+ if (!artifactName) return void 0;
10530
+ let parsed;
10531
+ try {
10532
+ parsed = JSON.parse(text);
10533
+ } catch {
10534
+ return void 0;
10575
10535
  }
10576
- const scan2 = scanRepo(repo, { ...opts, precomputedWalk: walked });
10577
- sessionPut({ key, scan: scan2, cacheMap: toCacheMap(scan2) });
10578
- return scan2;
10536
+ if (parsed.truncated !== true || typeof parsed.artifact !== "string") return void 0;
10537
+ return {
10538
+ type: "resource_link",
10539
+ uri: pathToFileURL2(parsed.artifact).href,
10540
+ name: artifactName,
10541
+ description: `The full ${tool} result this call was too large to inline.`,
10542
+ mimeType: "application/json"
10543
+ };
10579
10544
  }
10580
- function getScanSummary(repo, opts = {}, walked) {
10581
- if (sessionCaches.some((e) => e.key === sessionKey(repo, opts))) {
10582
- const scan2 = getScan(repo, opts, walked);
10583
- return {
10584
- root: scan2.root,
10585
- commit: scan2.commit,
10586
- fileCount: scan2.files.length,
10587
- languages: scan2.languages,
10588
- capped: scan2.capped,
10589
- excluded: scan2.excluded
10545
+ var PROTOCOL_VERSIONS, LATEST_PROTOCOL, ANNOTATIONS_SINCE, RICH_TOOLS_SINCE, DEFAULT_MAX_RESPONSE_BYTES, NARROWER, ARTIFACT_FOR;
10546
+ var init_protocol = __esm({
10547
+ "src/mcp/protocol.ts"() {
10548
+ "use strict";
10549
+ init_preload();
10550
+ PROTOCOL_VERSIONS = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
10551
+ LATEST_PROTOCOL = PROTOCOL_VERSIONS[PROTOCOL_VERSIONS.length - 1];
10552
+ ANNOTATIONS_SINCE = "2025-03-26";
10553
+ RICH_TOOLS_SINCE = "2025-06-18";
10554
+ DEFAULT_MAX_RESPONSE_BYTES = 1e6;
10555
+ NARROWER = {
10556
+ graph: "pass `scope` to a subdirectory, or use repo_map / mermaid for an overview",
10557
+ symbols: "pass `name` to look up one symbol, or use find_symbol / symbols_overview",
10558
+ callers: "pass `name` to look up one symbol's call sites",
10559
+ dead_code: "pass `scope` to a subdirectory",
10560
+ find_references: "the symbol is referenced very widely \u2014 narrow with `scope` on a graph query",
10561
+ check_rules: "narrow the rule set, or pass `scope` to a subdirectory"
10590
10562
  };
10563
+ ARTIFACT_FOR = { graph: "graph.json", symbols: "symbols.json" };
10591
10564
  }
10592
- return scanSummary(repo, { ...opts, precomputedWalk: walked });
10593
- }
10594
- function getArtifacts(repo, opts = {}, walked) {
10595
- const scan2 = getScan(repo, opts, walked);
10596
- const entry = sessionCaches.find((e) => e.scan === scan2);
10597
- if (entry) return entry.arts ??= buildArtifactsFromScan(scan2, opts);
10598
- return buildArtifactsFromScan(scan2, opts);
10599
- }
10600
- async function warmGrammarsForRepo(repo) {
10601
- await warmGrammarsForWalk(walk(repo, {}));
10602
- }
10603
- async function warmGrammarsForWalk(walked) {
10604
- await ensureGrammars(grammarKeysForExts(walked.files.map((f) => f.ext)));
10565
+ });
10566
+
10567
+ // src/mcp/tools.ts
10568
+ function annotationsFor(name2) {
10569
+ const meta = TOOL_META[name2];
10570
+ if (!meta) return void 0;
10571
+ return {
10572
+ readOnlyHint: !meta.write,
10573
+ ...meta.write ? { destructiveHint: meta.destructive === true, idempotentHint: meta.idempotent === true } : {},
10574
+ openWorldHint: meta.openWorld === true
10575
+ };
10605
10576
  }
10606
- async function callTool(name2, args2, defaultRepo) {
10607
- const repo = str(args2.repo) ?? defaultRepo;
10608
- if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
10609
- const scanOpts = { scope: str(args2.scope), include: strArray(args2.include), exclude: strArray(args2.exclude) };
10610
- let walked;
10611
- if (!SCANLESS_TOOLS.has(name2)) {
10612
- walked = walk(repo, {});
10613
- await warmGrammarsForWalk(walked);
10614
- }
10615
- if (name2 === "scan_summary") {
10616
- const s = getScanSummary(repo, scanOpts, walked);
10617
- return JSON.stringify(
10618
- { engineVersion: ENGINE_VERSION, commit: s.commit, fileCount: s.fileCount, languages: s.languages, capped: s.capped },
10619
- null,
10620
- 2
10621
- );
10622
- }
10623
- if (name2 === "graph") {
10624
- return renderGraphJson(getArtifacts(repo, scanOpts, walked).graph);
10625
- }
10626
- if (name2 === "symbols") {
10627
- const { symbols } = getArtifacts(repo, scanOpts, walked);
10628
- const lookup = str(args2.name);
10629
- if (lookup) {
10630
- return JSON.stringify({ name: lookup, defs: symbols.defs[lookup] ?? [], refs: symbols.refs[lookup] ?? [] }, null, 2);
10631
- }
10632
- return JSON.stringify(symbols, null, 2);
10633
- }
10634
- if (name2 === "callers") {
10635
- const scan2 = getScan(repo, scanOpts, walked);
10636
- const index = args2.recall === true ? buildCallerIndex(scan2, void 0, { recall: true }) : callerIndexFor(scan2);
10637
- const lookup = str(args2.name);
10638
- if (lookup) {
10639
- const entry = index.get(lookup);
10640
- return JSON.stringify(entry ?? { error: `no tracked callers for "${lookup}"` }, null, 2);
10577
+ function toolsFor(defaultRepo, protocolVersion = PROTOCOL_VERSIONS[0]) {
10578
+ const withAnnotations = protocolVersion >= ANNOTATIONS_SINCE;
10579
+ const withRich = protocolVersion >= RICH_TOOLS_SINCE;
10580
+ if (!defaultRepo && !withAnnotations && !withRich) return TOOLS;
10581
+ return TOOLS.map((t) => ({
10582
+ ...t,
10583
+ ...withRich && TOOL_META[t.name] ? { title: TOOL_META[t.name].title } : {},
10584
+ ...withRich && OUTPUT_SCHEMAS[t.name] ? { outputSchema: OUTPUT_SCHEMAS[t.name] } : {},
10585
+ ...withAnnotations ? { annotations: annotationsFor(t.name) } : {},
10586
+ inputSchema: !defaultRepo ? t.inputSchema : {
10587
+ ...t.inputSchema,
10588
+ properties: {
10589
+ ...t.inputSchema.properties,
10590
+ repo: {
10591
+ type: "string",
10592
+ description: `Absolute path to the repository root (optional \u2014 defaults to ${defaultRepo})`
10593
+ }
10594
+ },
10595
+ required: t.inputSchema.required.filter((r) => r !== "repo")
10641
10596
  }
10642
- const obj = {};
10643
- for (const [k, v] of index) obj[k] = v;
10644
- return JSON.stringify(obj, null, 2);
10645
- }
10646
- if (name2 === "workspaces") {
10647
- const info2 = detectWorkspaces(repo);
10648
- return JSON.stringify({ packages: info2.packages, cycle: info2.cycle ?? null, topoOrder: info2.topoOrder }, null, 2);
10649
- }
10650
- if (name2 === "churn") {
10651
- const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
10652
- const sorted = {};
10653
- for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
10654
- return JSON.stringify({ ok, churn: sorted }, null, 2);
10655
- }
10656
- if (name2 === "symbols_overview") {
10657
- const file = str(args2.file);
10658
- if (!file) throw new Error("`file` is required");
10659
- return JSON.stringify(symbolsOverview(getScan(repo, scanOpts, walked), file), null, 2);
10660
- }
10661
- if (name2 === "find_symbol") {
10662
- const namePath = str(args2.namePath);
10663
- if (!namePath) throw new Error("`namePath` is required");
10664
- const matches = findSymbol(getScan(repo, scanOpts, walked), namePath, {
10665
- substring: args2.substring === true,
10666
- includeBody: args2.includeBody === true,
10667
- maxResults: num(args2.maxResults)
10668
- });
10669
- return JSON.stringify(matches, null, 2);
10670
- }
10671
- if (name2 === "find_references") {
10672
- const symName = str(args2.name);
10673
- if (!symName) throw new Error("`name` is required");
10674
- return JSON.stringify(findReferences(getScan(repo, scanOpts, walked), symName), null, 2);
10675
- }
10676
- if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
10677
- const namePath = str(args2.namePath);
10678
- const body2 = typeof args2.body === "string" ? args2.body : void 0;
10679
- if (!namePath || body2 === void 0) throw new Error("`namePath` and `body` are required");
10680
- const scan2 = getScan(repo, scanOpts, walked);
10681
- const fn = name2 === "replace_symbol_body" ? replaceSymbolBody : name2 === "insert_after_symbol" ? insertAfterSymbol : insertBeforeSymbol;
10682
- const result = fn(scan2, namePath, body2, str(args2.file));
10683
- sessionClear();
10684
- return JSON.stringify(result, null, 2);
10685
- }
10686
- if (name2 === "write_memory") {
10687
- const memName = str(args2.name);
10688
- const content = typeof args2.content === "string" ? args2.content : void 0;
10689
- if (!memName || content === void 0) throw new Error("`name` and `content` are required");
10690
- return JSON.stringify({ written: writeMemory(repo, memName, content) }, null, 2);
10691
- }
10692
- if (name2 === "read_memory") {
10693
- const memName = str(args2.name);
10694
- if (!memName) throw new Error("`name` is required");
10695
- const content = readMemory(repo, memName);
10696
- if (content === void 0) throw new Error(`no memory named "${memName}" \u2014 see list_memories`);
10697
- return content;
10698
- }
10699
- if (name2 === "list_memories") {
10700
- return JSON.stringify(listMemories(repo), null, 2);
10701
- }
10702
- if (name2 === "delete_memory") {
10703
- const memName = str(args2.name);
10704
- if (!memName) throw new Error("`name` is required");
10705
- return JSON.stringify({ deleted: deleteMemory(repo, memName) }, null, 2);
10706
- }
10707
- if (name2 === "dead_code") {
10708
- const all = findDeadCode(getScan(repo, scanOpts, walked));
10709
- const limit = num(args2.limit);
10710
- if (limit === void 0 || all.length <= limit) return JSON.stringify(all, null, 2);
10711
- return JSON.stringify({ total: all.length, shown: limit, truncated: true, candidates: all.slice(0, limit) }, null, 2);
10712
- }
10713
- if (name2 === "complexity") {
10714
- const scan2 = getScan(repo, scanOpts, walked);
10715
- if (args2.risk === true) {
10716
- const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
10717
- return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn, num(args2.top)) }, null, 2);
10718
- }
10719
- return JSON.stringify(symbolComplexity(scan2, str(args2.file), num(args2.top)), null, 2);
10720
- }
10721
- if (name2 === "mermaid") {
10722
- const { graph } = getArtifacts(repo, scanOpts, walked);
10723
- return renderMermaid(graph, { module: str(args2.module), maxEdges: num(args2.maxEdges) });
10724
- }
10725
- if (name2 === "repo_map") {
10726
- const { scan: scan2, graph } = getArtifacts(repo, scanOpts, walked);
10727
- return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
10728
- }
10729
- if (name2 === "hotspots") {
10730
- const scan2 = getScan(repo, scanOpts, walked);
10731
- const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
10732
- return JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2);
10733
- }
10734
- if (name2 === "coupling") {
10735
- const { ok, couplings } = changeCoupling(repo, { since: str(args2.since) });
10736
- return JSON.stringify({ ok, couplings }, null, 2);
10737
- }
10738
- if (name2 === "grep") {
10739
- const pattern = str(args2.pattern);
10740
- if (!pattern) throw new Error("`pattern` is required");
10741
- const scope = str(args2.scope);
10742
- const globs = strArray(args2.globs);
10743
- const hits = grepRepo(repo, pattern, {
10744
- globs: scope ? [...globs ?? [], `${scope.replace(/\/+$/, "")}/**`] : globs,
10745
- ignoreCase: args2.ignoreCase === true,
10746
- maxHits: typeof args2.maxHits === "number" ? args2.maxHits : void 0
10747
- });
10748
- return JSON.stringify(hits, null, 2);
10749
- }
10750
- if (name2 === "search") {
10751
- const query = str(args2.query);
10752
- if (!query) throw new Error("`query` is required");
10753
- const scan2 = getScan(repo, scanOpts, walked);
10754
- const limit = typeof args2.limit === "number" ? args2.limit : void 0;
10755
- const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
10756
- if (args2.semantic === true) {
10757
- const endpoint = resolveEmbedEndpoint();
10758
- if (endpoint) {
10759
- try {
10760
- const index = await memoizedEmbeddingIndex({ mode: "endpoint", identity: endpoint, scan: scan2 }, () => buildEndpointIndex(scan2));
10761
- const queryVec = await encodeQueryViaEndpoint(query);
10762
- const results2 = searchSemantic(scan2, query, index, { queryVec, limit, fuzzy });
10763
- return JSON.stringify({ results: results2, tier: "endpoint" }, null, 2);
10764
- } catch (e) {
10765
- const results2 = searchIndex(scan2, query, { limit, fuzzy });
10766
- return JSON.stringify(
10767
- { results: results2, tier: "lexical", degradedReason: `embedding endpoint failed: ${errMessage(e)}` },
10768
- null,
10769
- 2
10770
- );
10771
- }
10772
- }
10773
- const modelDir = resolveEmbedModelDir(repo);
10774
- const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
10775
- if (model) {
10776
- const index = await memoizedEmbeddingIndex(
10777
- { mode: "static", identity: `${modelDir}#${model.modelId}`, scan: scan2 },
10778
- () => buildEmbeddingIndex(scan2, model)
10779
- );
10780
- const results2 = searchSemantic(scan2, query, index, { model, limit, fuzzy });
10781
- return JSON.stringify({ results: results2, tier: "static" }, null, 2);
10782
- }
10783
- const results = searchIndex(scan2, query, { limit, fuzzy });
10784
- return JSON.stringify(
10785
- { results, tier: "lexical", degradedReason: "no embedding endpoint or static model configured \u2014 see embed_status" },
10786
- null,
10787
- 2
10788
- );
10789
- }
10790
- return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
10791
- }
10792
- if (name2 === "embed_status") {
10793
- const modelDir = resolveEmbedModelDir(repo);
10794
- const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
10795
- const endpoint = resolveEmbedEndpoint();
10796
- const mode = endpoint ? "endpoint" : model ? "static" : "none";
10797
- const status = {
10798
- embedVersion: EMBED_VERSION,
10799
- mode,
10800
- model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
10801
- endpoint: endpoint ?? null
10802
- };
10803
- if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
10804
- return JSON.stringify(status, null, 2);
10805
- }
10806
- if (name2 === "check_rules") {
10807
- const configPath = str(args2.configPath);
10808
- let payload = args2.rules;
10809
- if (payload === void 0 && configPath) {
10810
- const abs = isAbsolute(configPath) ? configPath : join16(repo, configPath);
10811
- try {
10812
- payload = JSON.parse(readFileSync8(abs, "utf8"));
10813
- } catch (e) {
10814
- throw new Error(`cannot read rules from ${abs}: ${errMessage(e)}`);
10815
- }
10816
- }
10817
- if (payload === void 0) throw new Error("`rules` (or `configPath`) is required");
10818
- const rules = parseRules(payload);
10819
- const { graph } = getArtifacts(repo, scanOpts, walked);
10820
- return JSON.stringify(checkRules(graph, rules), null, 2);
10821
- }
10822
- throw new Error(`unknown tool: ${name2}`);
10823
- }
10824
- function validateArgs(schema, args2) {
10825
- const props = schema.properties ?? {};
10826
- for (const [key, value] of Object.entries(args2)) {
10827
- if (value === void 0 || value === null) continue;
10828
- const spec = props[key];
10829
- if (!spec?.type) continue;
10830
- const actual = Array.isArray(value) ? "array" : typeof value;
10831
- if (spec.type === "number") {
10832
- if (actual === "number") continue;
10833
- if (actual === "string" && Number.isFinite(Number(value)) && value.trim() !== "") continue;
10834
- return `\`${key}\` must be a number, got ${actual === "string" ? JSON.stringify(value) : actual}`;
10835
- }
10836
- if (spec.type === "array") {
10837
- if (actual !== "array") return `\`${key}\` must be an array of strings, got ${actual}`;
10838
- if (spec.items?.type === "string" && !value.every((x) => typeof x === "string")) {
10839
- return `\`${key}\` must be an array of strings`;
10840
- }
10841
- continue;
10842
- }
10843
- if (actual !== spec.type) return `\`${key}\` must be a ${spec.type}, got ${actual}`;
10844
- }
10845
- return void 0;
10846
- }
10847
- function negotiateProtocol(requested) {
10848
- return typeof requested === "string" && PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL;
10849
- }
10850
- function annotationsFor(name2) {
10851
- const meta = TOOL_META[name2];
10852
- if (!meta) return void 0;
10853
- return {
10854
- readOnlyHint: !meta.write,
10855
- ...meta.write ? { destructiveHint: meta.destructive === true, idempotentHint: meta.idempotent === true } : {},
10856
- openWorldHint: meta.openWorld === true
10857
- };
10597
+ }));
10858
10598
  }
10859
- function toolsFor(defaultRepo, protocolVersion = PROTOCOL_VERSIONS[0]) {
10860
- const withAnnotations = protocolVersion >= ANNOTATIONS_SINCE;
10861
- const withTitle = protocolVersion >= RICH_TOOLS_SINCE;
10862
- if (!defaultRepo && !withAnnotations && !withTitle) return TOOLS;
10863
- return TOOLS.map((t) => ({
10864
- ...t,
10865
- ...withTitle && TOOL_META[t.name] ? { title: TOOL_META[t.name].title } : {},
10866
- ...withAnnotations ? { annotations: annotationsFor(t.name) } : {},
10867
- inputSchema: !defaultRepo ? t.inputSchema : {
10868
- ...t.inputSchema,
10869
- properties: {
10870
- ...t.inputSchema.properties,
10871
- repo: {
10872
- type: "string",
10873
- description: `Absolute path to the repository root (optional \u2014 defaults to ${defaultRepo})`
10874
- }
10875
- },
10876
- required: t.inputSchema.required.filter((r) => r !== "repo")
10877
- }
10878
- }));
10879
- }
10880
- function capResponse(text, tool, repo, maxBytes) {
10881
- const bytes = Buffer.byteLength(text, "utf8");
10882
- if (bytes <= maxBytes) return text;
10883
- const artifact = ARTIFACT_FOR[tool] ? join16(repo, INDEX_DIR, ARTIFACT_FOR[tool]) : void 0;
10884
- return JSON.stringify(
10885
- {
10886
- truncated: true,
10887
- tool,
10888
- bytes,
10889
- maxBytes,
10890
- reason: "This response exceeds the configured limit and was withheld rather than sent as an unusable partial payload.",
10891
- narrower: NARROWER[tool] ?? "narrow the request with `scope`, `include`/`exclude`, or a `limit`",
10892
- ...artifact && existsSync6(artifact) ? { artifact, artifactNote: "The full result is already on disk here \u2014 read it directly if you need all of it." } : artifact ? { artifactNote: `Run \`codeindex index --repo ${repo} --out ${join16(repo, INDEX_DIR)}\` to get this as a file.` } : {}
10893
- },
10894
- null,
10895
- 2
10896
- ) + "\n";
10897
- }
10898
- function resourceLinkFor(text, tool) {
10899
- const artifactName = ARTIFACT_FOR[tool];
10900
- if (!artifactName) return void 0;
10901
- let parsed;
10902
- try {
10903
- parsed = JSON.parse(text);
10904
- } catch {
10905
- return void 0;
10906
- }
10907
- if (parsed.truncated !== true || typeof parsed.artifact !== "string") return void 0;
10908
- return {
10909
- type: "resource_link",
10910
- uri: pathToFileURL2(parsed.artifact).href,
10911
- name: artifactName,
10912
- description: `The full ${tool} result this call was too large to inline.`,
10913
- mimeType: "application/json"
10914
- };
10915
- }
10916
- async function runMcpServer(opts = {}) {
10917
- const serverInfo = {
10918
- name: opts.serverInfo?.name ?? "codeindex",
10919
- version: opts.serverInfo?.version ?? ENGINE_VERSION
10920
- };
10921
- let protocolVersion = PROTOCOL_VERSIONS[0];
10922
- let tools = toolsFor(opts.defaultRepo, protocolVersion);
10923
- const send = (msg) => {
10924
- process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
10925
- };
10926
- const rl = createInterface({ input: process.stdin, terminal: false });
10927
- for await (const line of rl) {
10928
- const trimmed = line.trim();
10929
- if (!trimmed) continue;
10930
- let parsed;
10931
- try {
10932
- parsed = JSON.parse(trimmed);
10933
- } catch {
10934
- send({ id: null, error: { code: -32700, message: "parse error" } });
10935
- continue;
10936
- }
10937
- const requests = Array.isArray(parsed) ? parsed : [parsed];
10938
- for (const req of requests) await handle2(req);
10939
- }
10940
- async function handle2(req) {
10941
- if (req.id === void 0 || req.id === null) return;
10942
- try {
10943
- if (req.method === "initialize") {
10944
- protocolVersion = negotiateProtocol(req.params?.protocolVersion);
10945
- tools = toolsFor(opts.defaultRepo, protocolVersion);
10946
- send({
10947
- id: req.id,
10948
- result: {
10949
- protocolVersion,
10950
- capabilities: { tools: {} },
10951
- serverInfo
10952
- }
10953
- });
10954
- } else if (req.method === "ping") {
10955
- send({ id: req.id, result: {} });
10956
- } else if (req.method === "tools/list") {
10957
- send({ id: req.id, result: { tools } });
10958
- } else if (req.method === "tools/call") {
10959
- const params = req.params ?? {};
10960
- const name2 = str(params.name) ?? "";
10961
- const args2 = params.arguments ?? {};
10962
- try {
10963
- const decl = tools.find(
10964
- (t) => t.name === name2
10965
- );
10966
- const invalid = decl ? validateArgs(decl.inputSchema, args2) : void 0;
10967
- if (invalid) throw new Error(invalid);
10968
- const raw = await callTool(name2, args2, opts.defaultRepo);
10969
- const repo = str(args2.repo) ?? opts.defaultRepo ?? "";
10970
- const text = capResponse(raw, name2, repo, opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES);
10971
- const link = text !== raw && protocolVersion >= RICH_TOOLS_SINCE ? resourceLinkFor(text, name2) : void 0;
10972
- send({
10973
- id: req.id,
10974
- result: { content: link ? [{ type: "text", text }, link] : [{ type: "text", text }] }
10975
- });
10976
- } catch (e) {
10977
- send({
10978
- id: req.id,
10979
- result: { content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }], isError: true }
10980
- });
10981
- }
10982
- } else {
10983
- send({ id: req.id, error: { code: -32601, message: `method not found: ${req.method}` } });
10984
- }
10985
- } catch (e) {
10986
- send({ id: req.id, error: { code: -32603, message: e instanceof Error ? e.message : String(e) } });
10987
- }
10988
- }
10989
- }
10990
- var repoProp, scopeProps, TOOLS, embeddingIndexCache, embedModelCache, SESSION_CACHE_MAX, sessionCaches, SCANLESS_TOOLS, PROTOCOL_VERSIONS, LATEST_PROTOCOL, ANNOTATIONS_SINCE, RICH_TOOLS_SINCE, TOOL_META, DEFAULT_MAX_RESPONSE_BYTES, NARROWER, ARTIFACT_FOR;
10991
- var init_mcp = __esm({
10992
- "src/mcp.ts"() {
10993
- "use strict";
10994
- init_types();
10995
- init_loader();
10996
- init_pipeline();
10997
- init_graph_json();
10998
- init_scan();
10999
- init_preload();
11000
- init_walk();
11001
- init_callers();
11002
- init_derived();
11003
- init_workspaces();
11004
- init_git();
11005
- init_grep();
11006
- init_coupling();
11007
- init_repomap();
11008
- init_deadcode();
11009
- init_complexity();
11010
- init_viz();
11011
- init_query();
11012
- init_edit();
11013
- init_memory();
11014
- init_bm25();
11015
- init_rules();
11016
- init_model();
11017
- init_embed();
11018
- init_search();
11019
- init_endpoint();
11020
- init_hash();
11021
- repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
11022
- scopeProps = {
11023
- scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
11024
- include: { type: "array", items: { type: "string" }, description: "Include globs" },
11025
- exclude: { type: "array", items: { type: "string" }, description: "Exclude globs" }
11026
- };
11027
- TOOLS = [
11028
- {
11029
- name: "scan_summary",
11030
- description: "Deterministically scan a repository: file count, per-language file histogram, HEAD commit, and whether the walk was capped. Fast first look at any codebase.",
11031
- inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
11032
- },
11033
- {
11034
- name: "graph",
11035
- description: "Build the full typed cross-file link-graph (import/call/use/doc-link/mention edges, module grouping, PageRank centrality, Louvain communities, tests-map). Returns graph.json. Large on big repos \u2014 prefer scan_summary/symbols/callers for targeted questions.",
11036
- inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
11037
- },
11038
- {
11039
- name: "symbols",
11040
- description: "Where is a symbol defined and which files reference it? Returns the definition sites (file, line, kind, exported) and referencing files. Omit `name` for the full symbol index.",
11041
- inputSchema: {
11042
- type: "object",
11043
- properties: { ...repoProp, name: { type: "string", description: "Symbol name to look up" } },
11044
- required: ["repo"]
10599
+ var repoProp, scopeProps, TOOLS, strArr, anyObj, OUTPUT_SCHEMAS, TOOL_META;
10600
+ var init_tools = __esm({
10601
+ "src/mcp/tools.ts"() {
10602
+ "use strict";
10603
+ init_protocol();
10604
+ repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
10605
+ scopeProps = {
10606
+ scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
10607
+ include: { type: "array", items: { type: "string" }, description: "Include globs" },
10608
+ exclude: { type: "array", items: { type: "string" }, description: "Exclude globs" }
10609
+ };
10610
+ TOOLS = [
10611
+ {
10612
+ name: "scan_summary",
10613
+ description: "Deterministically scan a repository: file count, per-language file histogram, HEAD commit, and whether the walk was capped. Fast first look at any codebase.",
10614
+ inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
10615
+ },
10616
+ {
10617
+ name: "graph",
10618
+ description: "Build the full typed cross-file link-graph (import/call/use/doc-link/mention edges, module grouping, PageRank centrality, Louvain communities, tests-map). Returns graph.json. Large on big repos \u2014 prefer scan_summary/symbols/callers for targeted questions.",
10619
+ inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
10620
+ },
10621
+ {
10622
+ name: "symbols",
10623
+ description: "Where is a symbol defined and which files reference it? Returns the definition sites (file, line, kind, exported) and referencing files. Omit `name` for the full symbol index.",
10624
+ inputSchema: {
10625
+ type: "object",
10626
+ properties: { ...repoProp, name: { type: "string", description: "Symbol name to look up" } },
10627
+ required: ["repo"]
11045
10628
  }
11046
10629
  },
11047
10630
  {
@@ -11266,32 +10849,672 @@ var init_mcp = __esm({
11266
10849
  },
11267
10850
  required: ["repo", "query"]
11268
10851
  }
11269
- },
11270
- {
11271
- name: "embed_status",
11272
- 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.",
11273
- inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] }
11274
- },
11275
- {
11276
- name: "check_rules",
11277
- 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.',
11278
- inputSchema: {
11279
- type: "object",
11280
- properties: {
11281
- ...repoProp,
11282
- ...scopeProps,
11283
- rules: { type: "array", description: "Rules array (inline JSON \u2014 see description)" },
11284
- configPath: {
11285
- type: "string",
11286
- description: "Read the rules from this JSON file instead (repo-relative or absolute) \u2014 the CLI's --config. Ignored when `rules` is given."
10852
+ },
10853
+ {
10854
+ name: "embed_status",
10855
+ 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.",
10856
+ inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] }
10857
+ },
10858
+ {
10859
+ name: "check_rules",
10860
+ 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.',
10861
+ inputSchema: {
10862
+ type: "object",
10863
+ properties: {
10864
+ ...repoProp,
10865
+ ...scopeProps,
10866
+ rules: { type: "array", description: "Rules array (inline JSON \u2014 see description)" },
10867
+ configPath: {
10868
+ type: "string",
10869
+ description: "Read the rules from this JSON file instead (repo-relative or absolute) \u2014 the CLI's --config. Ignored when `rules` is given."
10870
+ }
10871
+ },
10872
+ required: ["repo"]
10873
+ }
10874
+ }
10875
+ ];
10876
+ strArr = { type: "array", items: { type: "string" } };
10877
+ anyObj = { type: "object" };
10878
+ OUTPUT_SCHEMAS = {
10879
+ scan_summary: {
10880
+ type: "object",
10881
+ properties: {
10882
+ engineVersion: { type: "string" },
10883
+ commit: { type: "string" },
10884
+ fileCount: { type: "integer" },
10885
+ languages: { type: "object", additionalProperties: { type: "integer" } },
10886
+ capped: { type: "boolean" }
10887
+ },
10888
+ required: ["engineVersion", "fileCount", "languages", "capped"]
10889
+ },
10890
+ graph: {
10891
+ type: "object",
10892
+ properties: {
10893
+ schemaVersion: { type: "integer" },
10894
+ version: { type: "string" },
10895
+ commit: { type: "string" },
10896
+ fileCount: { type: "integer" },
10897
+ languages: { type: "object", additionalProperties: { type: "integer" } },
10898
+ files: { type: "array", items: anyObj },
10899
+ modules: { type: "array", items: anyObj },
10900
+ fileEdges: { type: "array", items: anyObj },
10901
+ moduleEdges: { type: "array", items: anyObj }
10902
+ },
10903
+ required: ["schemaVersion", "files", "fileEdges", "modules", "moduleEdges"]
10904
+ },
10905
+ // Two shapes, both objects: the whole index, or one symbol's entry.
10906
+ symbols: {
10907
+ oneOf: [
10908
+ {
10909
+ type: "object",
10910
+ properties: { schemaVersion: { type: "integer" }, defs: anyObj, refs: anyObj },
10911
+ required: ["schemaVersion", "defs"]
10912
+ },
10913
+ {
10914
+ type: "object",
10915
+ properties: { name: { type: "string" }, defs: { type: "array", items: anyObj }, refs: strArr },
10916
+ required: ["name", "defs", "refs"]
10917
+ }
10918
+ ]
10919
+ },
10920
+ // The whole index (symbol name -> entry), one entry, or the not-found notice.
10921
+ callers: {
10922
+ oneOf: [
10923
+ { type: "object", additionalProperties: anyObj },
10924
+ { type: "object", properties: { error: { type: "string" } }, required: ["error"] }
10925
+ ]
10926
+ },
10927
+ workspaces: {
10928
+ type: "object",
10929
+ properties: {
10930
+ packages: { type: "array", items: anyObj },
10931
+ cycle: { type: ["array", "null"], items: { type: "string" } },
10932
+ topoOrder: strArr
10933
+ },
10934
+ required: ["packages", "topoOrder"]
10935
+ },
10936
+ churn: {
10937
+ type: "object",
10938
+ properties: { ok: { type: "boolean" }, churn: { type: "object", additionalProperties: { type: "integer" } } },
10939
+ required: ["ok", "churn"]
10940
+ },
10941
+ find_references: {
10942
+ type: "object",
10943
+ properties: {
10944
+ defs: { type: "array", items: anyObj },
10945
+ callSites: { type: "array", items: anyObj },
10946
+ referencingFiles: strArr
10947
+ },
10948
+ required: ["defs", "callSites", "referencingFiles"]
10949
+ },
10950
+ hotspots: {
10951
+ type: "object",
10952
+ properties: { churnOk: { type: "boolean" }, hotspots: { type: "array", items: anyObj } },
10953
+ required: ["churnOk", "hotspots"]
10954
+ },
10955
+ coupling: {
10956
+ type: "object",
10957
+ properties: { ok: { type: "boolean" }, couplings: { type: "array", items: anyObj } },
10958
+ required: ["ok", "couplings"]
10959
+ },
10960
+ embed_status: {
10961
+ type: "object",
10962
+ properties: {
10963
+ embedVersion: { type: "integer" },
10964
+ mode: { type: "string", enum: ["none", "static", "endpoint"] },
10965
+ model: {},
10966
+ endpoint: {},
10967
+ endpointReachable: { type: "boolean" }
10968
+ },
10969
+ required: ["embedVersion", "mode"]
10970
+ },
10971
+ write_memory: {
10972
+ type: "object",
10973
+ properties: { written: { type: "string" } },
10974
+ required: ["written"]
10975
+ },
10976
+ delete_memory: {
10977
+ type: "object",
10978
+ properties: { deleted: { type: "boolean" } },
10979
+ required: ["deleted"]
10980
+ }
10981
+ };
10982
+ for (const name2 of ["replace_symbol_body", "insert_after_symbol", "insert_before_symbol"]) {
10983
+ OUTPUT_SCHEMAS[name2] = {
10984
+ type: "object",
10985
+ properties: {
10986
+ file: { type: "string" },
10987
+ symbol: { type: "string" },
10988
+ startLine: { type: "integer" },
10989
+ endLine: { type: "integer" }
10990
+ },
10991
+ required: ["file"]
10992
+ };
10993
+ }
10994
+ TOOL_META = {
10995
+ scan_summary: { title: "Scan summary" },
10996
+ graph: { title: "Link graph" },
10997
+ symbols: { title: "Symbol index" },
10998
+ callers: { title: "Caller index" },
10999
+ workspaces: { title: "Monorepo workspaces" },
11000
+ churn: { title: "Git churn" },
11001
+ symbols_overview: { title: "File symbol overview" },
11002
+ find_symbol: { title: "Find symbol" },
11003
+ find_references: { title: "Find references" },
11004
+ repo_map: { title: "Repository map" },
11005
+ hotspots: { title: "Hotspots" },
11006
+ coupling: { title: "Change coupling" },
11007
+ replace_symbol_body: { title: "Replace symbol body", write: true, destructive: true, idempotent: true },
11008
+ insert_after_symbol: { title: "Insert after symbol", write: true, destructive: false, idempotent: false },
11009
+ insert_before_symbol: { title: "Insert before symbol", write: true, destructive: false, idempotent: false },
11010
+ write_memory: { title: "Write memory", write: true, destructive: false, idempotent: true },
11011
+ read_memory: { title: "Read memory" },
11012
+ list_memories: { title: "List memories" },
11013
+ delete_memory: { title: "Delete memory", write: true, destructive: true, idempotent: true },
11014
+ dead_code: { title: "Dead-code candidates" },
11015
+ complexity: { title: "Complexity" },
11016
+ mermaid: { title: "Mermaid module diagram" },
11017
+ grep: { title: "Grep file contents" },
11018
+ search: { title: "Lexical search", openWorld: true },
11019
+ embed_status: { title: "Embedding tier status", openWorld: true },
11020
+ check_rules: { title: "Check architecture rules" }
11021
+ };
11022
+ }
11023
+ });
11024
+
11025
+ // src/mcp/session.ts
11026
+ import { statSync as statSync5 } from "fs";
11027
+ import { join as join17 } from "path";
11028
+ function scanFingerprint(scan2) {
11029
+ return sha1(scan2.files.map((f) => `${f.rel}:${f.hash}`).join("\n"));
11030
+ }
11031
+ async function memoizedEmbeddingIndex(key, build) {
11032
+ const cacheKey = `${key.mode}:${key.identity}:${scanFingerprint(key.scan)}`;
11033
+ if (embeddingIndexCache && embeddingIndexCache.key === cacheKey) return embeddingIndexCache.index;
11034
+ const index = await build();
11035
+ embeddingIndexCache = { key: cacheKey, index };
11036
+ return index;
11037
+ }
11038
+ function memoizedEmbedModel(modelDir) {
11039
+ let stat;
11040
+ try {
11041
+ stat = statSync5(join17(modelDir, "model.json"));
11042
+ } catch {
11043
+ return void 0;
11044
+ }
11045
+ const key = `${modelDir}:${stat.mtimeMs}:${stat.size}`;
11046
+ if (embedModelCache && embedModelCache.key === key) return embedModelCache.model;
11047
+ const model = loadEmbedModel(modelDir);
11048
+ if (model) embedModelCache = { key, model };
11049
+ return model;
11050
+ }
11051
+ function sessionGet(key) {
11052
+ const i2 = sessionCaches.findIndex((e) => e.key === key);
11053
+ if (i2 < 0) return void 0;
11054
+ const [entry] = sessionCaches.splice(i2, 1);
11055
+ sessionCaches.unshift(entry);
11056
+ return entry;
11057
+ }
11058
+ function sessionPut(entry) {
11059
+ const i2 = sessionCaches.findIndex((e) => e.key === entry.key);
11060
+ if (i2 >= 0) sessionCaches.splice(i2, 1);
11061
+ sessionCaches.unshift(entry);
11062
+ sessionCaches.length = Math.min(sessionCaches.length, SESSION_CACHE_MAX);
11063
+ return entry;
11064
+ }
11065
+ function sessionClear() {
11066
+ sessionCaches.length = 0;
11067
+ }
11068
+ function sessionKey(repo, opts) {
11069
+ return repo + "\0" + JSON.stringify({
11070
+ scope: opts.scope,
11071
+ include: opts.include,
11072
+ exclude: opts.exclude,
11073
+ gitignore: opts.gitignore,
11074
+ ignoreDirs: opts.ignoreDirs,
11075
+ maxBytes: opts.maxBytes,
11076
+ maxFiles: opts.maxFiles,
11077
+ maxCallsPerFile: opts.maxCallsPerFile,
11078
+ out: opts.out,
11079
+ fullHash: opts.fullHash
11080
+ });
11081
+ }
11082
+ function getScan(repo, opts = {}, walked) {
11083
+ const key = sessionKey(repo, opts);
11084
+ const hit = sessionGet(key);
11085
+ if (hit) {
11086
+ const fresh = scanRepo(repo, { ...opts, cache: hit.cacheMap, precomputedWalk: walked });
11087
+ if (fresh.contentUnchanged) {
11088
+ if (fresh.cacheDirty) hit.cacheMap = toCacheMap(fresh);
11089
+ if (hit.scan.commit !== fresh.commit) hit.scan.commit = fresh.commit;
11090
+ return hit.scan;
11091
+ }
11092
+ sessionPut({ key, scan: fresh, cacheMap: toCacheMap(fresh) });
11093
+ return fresh;
11094
+ }
11095
+ const preloaded = preloadSession(repo, { ...opts, precomputedWalk: walked });
11096
+ if (preloaded) {
11097
+ sessionPut({ key, scan: preloaded.scan, cacheMap: preloaded.cacheMap, arts: preloaded.arts });
11098
+ return preloaded.scan;
11099
+ }
11100
+ const scan2 = scanRepo(repo, { ...opts, precomputedWalk: walked });
11101
+ sessionPut({ key, scan: scan2, cacheMap: toCacheMap(scan2) });
11102
+ return scan2;
11103
+ }
11104
+ function getScanSummary(repo, opts = {}, walked) {
11105
+ if (sessionCaches.some((e) => e.key === sessionKey(repo, opts))) {
11106
+ const scan2 = getScan(repo, opts, walked);
11107
+ return {
11108
+ root: scan2.root,
11109
+ commit: scan2.commit,
11110
+ fileCount: scan2.files.length,
11111
+ languages: scan2.languages,
11112
+ capped: scan2.capped,
11113
+ excluded: scan2.excluded
11114
+ };
11115
+ }
11116
+ return scanSummary(repo, { ...opts, precomputedWalk: walked });
11117
+ }
11118
+ function getArtifacts(repo, opts = {}, walked) {
11119
+ const scan2 = getScan(repo, opts, walked);
11120
+ const entry = sessionCaches.find((e) => e.scan === scan2);
11121
+ if (entry) return entry.arts ??= buildArtifactsFromScan(scan2, opts);
11122
+ return buildArtifactsFromScan(scan2, opts);
11123
+ }
11124
+ async function warmGrammarsForRepo(repo) {
11125
+ await warmGrammarsForWalk(walk(repo, {}));
11126
+ }
11127
+ async function warmGrammarsForWalk(walked) {
11128
+ await ensureGrammars(grammarKeysForExts(walked.files.map((f) => f.ext)));
11129
+ }
11130
+ var embeddingIndexCache, embedModelCache, SESSION_CACHE_MAX, sessionCaches;
11131
+ var init_session = __esm({
11132
+ "src/mcp/session.ts"() {
11133
+ "use strict";
11134
+ init_pipeline();
11135
+ init_scan();
11136
+ init_preload();
11137
+ init_walk();
11138
+ init_loader();
11139
+ init_model();
11140
+ init_embed();
11141
+ init_hash();
11142
+ SESSION_CACHE_MAX = 4;
11143
+ sessionCaches = [];
11144
+ }
11145
+ });
11146
+
11147
+ // src/mcp.ts
11148
+ var mcp_exports = {};
11149
+ __export(mcp_exports, {
11150
+ DEFAULT_MAX_RESPONSE_BYTES: () => DEFAULT_MAX_RESPONSE_BYTES,
11151
+ OUTPUT_SCHEMAS: () => OUTPUT_SCHEMAS,
11152
+ PROTOCOL_VERSIONS: () => PROTOCOL_VERSIONS,
11153
+ TOOLS: () => TOOLS,
11154
+ TOOL_META: () => TOOL_META,
11155
+ annotationsFor: () => annotationsFor,
11156
+ capResponse: () => capResponse,
11157
+ getArtifacts: () => getArtifacts,
11158
+ getScan: () => getScan,
11159
+ getScanSummary: () => getScanSummary,
11160
+ memoizedEmbedModel: () => memoizedEmbedModel,
11161
+ memoizedEmbeddingIndex: () => memoizedEmbeddingIndex,
11162
+ negotiateProtocol: () => negotiateProtocol,
11163
+ resourceLinkFor: () => resourceLinkFor,
11164
+ runMcpServer: () => runMcpServer,
11165
+ scanFingerprint: () => scanFingerprint,
11166
+ structuredContentFor: () => structuredContentFor,
11167
+ toCacheMap: () => toCacheMap,
11168
+ toolsFor: () => toolsFor,
11169
+ validateArgs: () => validateArgs,
11170
+ warmGrammarsForRepo: () => warmGrammarsForRepo,
11171
+ warmGrammarsForWalk: () => warmGrammarsForWalk
11172
+ });
11173
+ import { readFileSync as readFileSync8 } from "fs";
11174
+ import { isAbsolute, join as join18 } from "path";
11175
+ import { createInterface } from "readline";
11176
+ function str(v) {
11177
+ return typeof v === "string" && v ? v : void 0;
11178
+ }
11179
+ function strArray(v) {
11180
+ return Array.isArray(v) && v.every((x) => typeof x === "string") && v.length ? v : void 0;
11181
+ }
11182
+ function num(v) {
11183
+ const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
11184
+ return Number.isFinite(n) && n > 0 ? n : void 0;
11185
+ }
11186
+ function errMessage(e) {
11187
+ return e instanceof Error ? e.message : String(e);
11188
+ }
11189
+ async function callTool(name2, args2, defaultRepo) {
11190
+ const repo = str(args2.repo) ?? defaultRepo;
11191
+ if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
11192
+ const scanOpts = { scope: str(args2.scope), include: strArray(args2.include), exclude: strArray(args2.exclude) };
11193
+ let walked;
11194
+ if (!SCANLESS_TOOLS.has(name2)) {
11195
+ walked = walk(repo, {});
11196
+ await warmGrammarsForWalk(walked);
11197
+ }
11198
+ if (name2 === "scan_summary") {
11199
+ const s = getScanSummary(repo, scanOpts, walked);
11200
+ return JSON.stringify(
11201
+ { engineVersion: ENGINE_VERSION, commit: s.commit, fileCount: s.fileCount, languages: s.languages, capped: s.capped },
11202
+ null,
11203
+ 2
11204
+ );
11205
+ }
11206
+ if (name2 === "graph") {
11207
+ return renderGraphJson(getArtifacts(repo, scanOpts, walked).graph);
11208
+ }
11209
+ if (name2 === "symbols") {
11210
+ const { symbols } = getArtifacts(repo, scanOpts, walked);
11211
+ const lookup = str(args2.name);
11212
+ if (lookup) {
11213
+ return JSON.stringify({ name: lookup, defs: symbols.defs[lookup] ?? [], refs: symbols.refs[lookup] ?? [] }, null, 2);
11214
+ }
11215
+ return JSON.stringify(symbols, null, 2);
11216
+ }
11217
+ if (name2 === "callers") {
11218
+ const scan2 = getScan(repo, scanOpts, walked);
11219
+ const index = args2.recall === true ? buildCallerIndex(scan2, void 0, { recall: true }) : callerIndexFor(scan2);
11220
+ const lookup = str(args2.name);
11221
+ if (lookup) {
11222
+ const entry = index.get(lookup);
11223
+ return JSON.stringify(entry ?? { error: `no tracked callers for "${lookup}"` }, null, 2);
11224
+ }
11225
+ const obj = {};
11226
+ for (const [k, v] of index) obj[k] = v;
11227
+ return JSON.stringify(obj, null, 2);
11228
+ }
11229
+ if (name2 === "workspaces") {
11230
+ const info2 = detectWorkspaces(repo);
11231
+ return JSON.stringify({ packages: info2.packages, cycle: info2.cycle ?? null, topoOrder: info2.topoOrder }, null, 2);
11232
+ }
11233
+ if (name2 === "churn") {
11234
+ const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
11235
+ const sorted = {};
11236
+ for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
11237
+ return JSON.stringify({ ok, churn: sorted }, null, 2);
11238
+ }
11239
+ if (name2 === "symbols_overview") {
11240
+ const file = str(args2.file);
11241
+ if (!file) throw new Error("`file` is required");
11242
+ return JSON.stringify(symbolsOverview(getScan(repo, scanOpts, walked), file), null, 2);
11243
+ }
11244
+ if (name2 === "find_symbol") {
11245
+ const namePath = str(args2.namePath);
11246
+ if (!namePath) throw new Error("`namePath` is required");
11247
+ const matches = findSymbol(getScan(repo, scanOpts, walked), namePath, {
11248
+ substring: args2.substring === true,
11249
+ includeBody: args2.includeBody === true,
11250
+ maxResults: num(args2.maxResults)
11251
+ });
11252
+ return JSON.stringify(matches, null, 2);
11253
+ }
11254
+ if (name2 === "find_references") {
11255
+ const symName = str(args2.name);
11256
+ if (!symName) throw new Error("`name` is required");
11257
+ return JSON.stringify(findReferences(getScan(repo, scanOpts, walked), symName), null, 2);
11258
+ }
11259
+ if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
11260
+ const namePath = str(args2.namePath);
11261
+ const body2 = typeof args2.body === "string" ? args2.body : void 0;
11262
+ if (!namePath || body2 === void 0) throw new Error("`namePath` and `body` are required");
11263
+ const scan2 = getScan(repo, scanOpts, walked);
11264
+ const fn = name2 === "replace_symbol_body" ? replaceSymbolBody : name2 === "insert_after_symbol" ? insertAfterSymbol : insertBeforeSymbol;
11265
+ const result = fn(scan2, namePath, body2, str(args2.file));
11266
+ sessionClear();
11267
+ return JSON.stringify(result, null, 2);
11268
+ }
11269
+ if (name2 === "write_memory") {
11270
+ const memName = str(args2.name);
11271
+ const content = typeof args2.content === "string" ? args2.content : void 0;
11272
+ if (!memName || content === void 0) throw new Error("`name` and `content` are required");
11273
+ return JSON.stringify({ written: writeMemory(repo, memName, content) }, null, 2);
11274
+ }
11275
+ if (name2 === "read_memory") {
11276
+ const memName = str(args2.name);
11277
+ if (!memName) throw new Error("`name` is required");
11278
+ const content = readMemory(repo, memName);
11279
+ if (content === void 0) throw new Error(`no memory named "${memName}" \u2014 see list_memories`);
11280
+ return content;
11281
+ }
11282
+ if (name2 === "list_memories") {
11283
+ return JSON.stringify(listMemories(repo), null, 2);
11284
+ }
11285
+ if (name2 === "delete_memory") {
11286
+ const memName = str(args2.name);
11287
+ if (!memName) throw new Error("`name` is required");
11288
+ return JSON.stringify({ deleted: deleteMemory(repo, memName) }, null, 2);
11289
+ }
11290
+ if (name2 === "dead_code") {
11291
+ const all = findDeadCode(getScan(repo, scanOpts, walked));
11292
+ const limit = num(args2.limit);
11293
+ if (limit === void 0 || all.length <= limit) return JSON.stringify(all, null, 2);
11294
+ return JSON.stringify({ total: all.length, shown: limit, truncated: true, candidates: all.slice(0, limit) }, null, 2);
11295
+ }
11296
+ if (name2 === "complexity") {
11297
+ const scan2 = getScan(repo, scanOpts, walked);
11298
+ if (args2.risk === true) {
11299
+ const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
11300
+ return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn, num(args2.top)) }, null, 2);
11301
+ }
11302
+ return JSON.stringify(symbolComplexity(scan2, str(args2.file), num(args2.top)), null, 2);
11303
+ }
11304
+ if (name2 === "mermaid") {
11305
+ const { graph } = getArtifacts(repo, scanOpts, walked);
11306
+ return renderMermaid(graph, { module: str(args2.module), maxEdges: num(args2.maxEdges) });
11307
+ }
11308
+ if (name2 === "repo_map") {
11309
+ const { scan: scan2, graph } = getArtifacts(repo, scanOpts, walked);
11310
+ return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
11311
+ }
11312
+ if (name2 === "hotspots") {
11313
+ const scan2 = getScan(repo, scanOpts, walked);
11314
+ const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
11315
+ return JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2);
11316
+ }
11317
+ if (name2 === "coupling") {
11318
+ const { ok, couplings } = changeCoupling(repo, { since: str(args2.since) });
11319
+ return JSON.stringify({ ok, couplings }, null, 2);
11320
+ }
11321
+ if (name2 === "grep") {
11322
+ const pattern = str(args2.pattern);
11323
+ if (!pattern) throw new Error("`pattern` is required");
11324
+ const scope = str(args2.scope);
11325
+ const globs = strArray(args2.globs);
11326
+ const hits = grepRepo(repo, pattern, {
11327
+ globs: scope ? [...globs ?? [], `${scope.replace(/\/+$/, "")}/**`] : globs,
11328
+ ignoreCase: args2.ignoreCase === true,
11329
+ maxHits: typeof args2.maxHits === "number" ? args2.maxHits : void 0
11330
+ });
11331
+ return JSON.stringify(hits, null, 2);
11332
+ }
11333
+ if (name2 === "search") {
11334
+ const query = str(args2.query);
11335
+ if (!query) throw new Error("`query` is required");
11336
+ const scan2 = getScan(repo, scanOpts, walked);
11337
+ const limit = typeof args2.limit === "number" ? args2.limit : void 0;
11338
+ const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
11339
+ if (args2.semantic === true) {
11340
+ const endpoint = resolveEmbedEndpoint();
11341
+ if (endpoint) {
11342
+ try {
11343
+ const index = await memoizedEmbeddingIndex({ mode: "endpoint", identity: endpoint, scan: scan2 }, () => buildEndpointIndex(scan2));
11344
+ const queryVec = await encodeQueryViaEndpoint(query);
11345
+ const results2 = searchSemantic(scan2, query, index, { queryVec, limit, fuzzy });
11346
+ return JSON.stringify({ results: results2, tier: "endpoint" }, null, 2);
11347
+ } catch (e) {
11348
+ const results2 = searchIndex(scan2, query, { limit, fuzzy });
11349
+ return JSON.stringify(
11350
+ { results: results2, tier: "lexical", degradedReason: `embedding endpoint failed: ${errMessage(e)}` },
11351
+ null,
11352
+ 2
11353
+ );
11354
+ }
11355
+ }
11356
+ const modelDir = resolveEmbedModelDir(repo);
11357
+ const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
11358
+ if (model) {
11359
+ const index = await memoizedEmbeddingIndex(
11360
+ { mode: "static", identity: `${modelDir}#${model.modelId}`, scan: scan2 },
11361
+ () => buildEmbeddingIndex(scan2, model)
11362
+ );
11363
+ const results2 = searchSemantic(scan2, query, index, { model, limit, fuzzy });
11364
+ return JSON.stringify({ results: results2, tier: "static" }, null, 2);
11365
+ }
11366
+ const results = searchIndex(scan2, query, { limit, fuzzy });
11367
+ return JSON.stringify(
11368
+ { results, tier: "lexical", degradedReason: "no embedding endpoint or static model configured \u2014 see embed_status" },
11369
+ null,
11370
+ 2
11371
+ );
11372
+ }
11373
+ return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
11374
+ }
11375
+ if (name2 === "embed_status") {
11376
+ const modelDir = resolveEmbedModelDir(repo);
11377
+ const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
11378
+ const endpoint = resolveEmbedEndpoint();
11379
+ const mode = endpoint ? "endpoint" : model ? "static" : "none";
11380
+ const status = {
11381
+ embedVersion: EMBED_VERSION,
11382
+ mode,
11383
+ model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
11384
+ endpoint: endpoint ?? null
11385
+ };
11386
+ if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
11387
+ return JSON.stringify(status, null, 2);
11388
+ }
11389
+ if (name2 === "check_rules") {
11390
+ const configPath = str(args2.configPath);
11391
+ let payload = args2.rules;
11392
+ if (payload === void 0 && configPath) {
11393
+ const abs = isAbsolute(configPath) ? configPath : join18(repo, configPath);
11394
+ try {
11395
+ payload = JSON.parse(readFileSync8(abs, "utf8"));
11396
+ } catch (e) {
11397
+ throw new Error(`cannot read rules from ${abs}: ${errMessage(e)}`);
11398
+ }
11399
+ }
11400
+ if (payload === void 0) throw new Error("`rules` (or `configPath`) is required");
11401
+ const rules = parseRules(payload);
11402
+ const { graph } = getArtifacts(repo, scanOpts, walked);
11403
+ return JSON.stringify(checkRules(graph, rules), null, 2);
11404
+ }
11405
+ throw new Error(`unknown tool: ${name2}`);
11406
+ }
11407
+ async function runMcpServer(opts = {}) {
11408
+ const serverInfo = {
11409
+ name: opts.serverInfo?.name ?? "codeindex",
11410
+ version: opts.serverInfo?.version ?? ENGINE_VERSION
11411
+ };
11412
+ let protocolVersion = PROTOCOL_VERSIONS[0];
11413
+ let tools = toolsFor(opts.defaultRepo, protocolVersion);
11414
+ const send = (msg) => {
11415
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
11416
+ };
11417
+ const rl = createInterface({ input: process.stdin, terminal: false });
11418
+ for await (const line of rl) {
11419
+ const trimmed = line.trim();
11420
+ if (!trimmed) continue;
11421
+ let parsed;
11422
+ try {
11423
+ parsed = JSON.parse(trimmed);
11424
+ } catch {
11425
+ send({ id: null, error: { code: -32700, message: "parse error" } });
11426
+ continue;
11427
+ }
11428
+ const requests = Array.isArray(parsed) ? parsed : [parsed];
11429
+ for (const req of requests) await handle2(req);
11430
+ }
11431
+ async function handle2(req) {
11432
+ if (req.id === void 0 || req.id === null) return;
11433
+ try {
11434
+ if (req.method === "initialize") {
11435
+ protocolVersion = negotiateProtocol(req.params?.protocolVersion);
11436
+ tools = toolsFor(opts.defaultRepo, protocolVersion);
11437
+ send({
11438
+ id: req.id,
11439
+ result: {
11440
+ protocolVersion,
11441
+ capabilities: { tools: {} },
11442
+ serverInfo
11443
+ }
11444
+ });
11445
+ } else if (req.method === "ping") {
11446
+ send({ id: req.id, result: {} });
11447
+ } else if (req.method === "tools/list") {
11448
+ send({ id: req.id, result: { tools } });
11449
+ } else if (req.method === "tools/call") {
11450
+ const params = req.params ?? {};
11451
+ const name2 = str(params.name) ?? "";
11452
+ const args2 = params.arguments ?? {};
11453
+ try {
11454
+ const decl = tools.find(
11455
+ (t) => t.name === name2
11456
+ );
11457
+ const invalid = decl ? validateArgs(decl.inputSchema, args2) : void 0;
11458
+ if (invalid) throw new Error(invalid);
11459
+ const raw = await callTool(name2, args2, opts.defaultRepo);
11460
+ const repo = str(args2.repo) ?? opts.defaultRepo ?? "";
11461
+ const text = capResponse(raw, name2, repo, opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES);
11462
+ const capped = text !== raw;
11463
+ const link = capped && protocolVersion >= RICH_TOOLS_SINCE ? resourceLinkFor(text, name2) : void 0;
11464
+ const structured = protocolVersion >= RICH_TOOLS_SINCE ? structuredContentFor(text, capped, OUTPUT_SCHEMAS[name2] !== void 0) : void 0;
11465
+ send({
11466
+ id: req.id,
11467
+ result: {
11468
+ content: link ? [{ type: "text", text }, link] : [{ type: "text", text }],
11469
+ ...structured ? { structuredContent: structured } : {}
11287
11470
  }
11288
- },
11289
- required: ["repo"]
11471
+ });
11472
+ } catch (e) {
11473
+ send({
11474
+ id: req.id,
11475
+ result: { content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }], isError: true }
11476
+ });
11290
11477
  }
11478
+ } else {
11479
+ send({ id: req.id, error: { code: -32601, message: `method not found: ${req.method}` } });
11291
11480
  }
11292
- ];
11293
- SESSION_CACHE_MAX = 4;
11294
- sessionCaches = [];
11481
+ } catch (e) {
11482
+ send({ id: req.id, error: { code: -32603, message: e instanceof Error ? e.message : String(e) } });
11483
+ }
11484
+ }
11485
+ }
11486
+ var SCANLESS_TOOLS;
11487
+ var init_mcp = __esm({
11488
+ "src/mcp.ts"() {
11489
+ "use strict";
11490
+ init_types();
11491
+ init_graph_json();
11492
+ init_callers();
11493
+ init_derived();
11494
+ init_workspaces();
11495
+ init_git();
11496
+ init_grep();
11497
+ init_coupling();
11498
+ init_repomap();
11499
+ init_deadcode();
11500
+ init_complexity();
11501
+ init_viz();
11502
+ init_query();
11503
+ init_edit();
11504
+ init_memory();
11505
+ init_bm25();
11506
+ init_rules();
11507
+ init_model();
11508
+ init_embed();
11509
+ init_search();
11510
+ init_endpoint();
11511
+ init_walk();
11512
+ init_tools();
11513
+ init_protocol();
11514
+ init_session();
11515
+ init_tools();
11516
+ init_protocol();
11517
+ init_session();
11295
11518
  SCANLESS_TOOLS = /* @__PURE__ */ new Set([
11296
11519
  "workspaces",
11297
11520
  "churn",
@@ -11307,48 +11530,6 @@ var init_mcp = __esm({
11307
11530
  // already cached getScanSummary reuses it, warm grammars included.
11308
11531
  "scan_summary"
11309
11532
  ]);
11310
- PROTOCOL_VERSIONS = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
11311
- LATEST_PROTOCOL = PROTOCOL_VERSIONS[PROTOCOL_VERSIONS.length - 1];
11312
- ANNOTATIONS_SINCE = "2025-03-26";
11313
- RICH_TOOLS_SINCE = "2025-06-18";
11314
- TOOL_META = {
11315
- scan_summary: { title: "Scan summary" },
11316
- graph: { title: "Link graph" },
11317
- symbols: { title: "Symbol index" },
11318
- callers: { title: "Caller index" },
11319
- workspaces: { title: "Monorepo workspaces" },
11320
- churn: { title: "Git churn" },
11321
- symbols_overview: { title: "File symbol overview" },
11322
- find_symbol: { title: "Find symbol" },
11323
- find_references: { title: "Find references" },
11324
- repo_map: { title: "Repository map" },
11325
- hotspots: { title: "Hotspots" },
11326
- coupling: { title: "Change coupling" },
11327
- replace_symbol_body: { title: "Replace symbol body", write: true, destructive: true, idempotent: true },
11328
- insert_after_symbol: { title: "Insert after symbol", write: true, destructive: false, idempotent: false },
11329
- insert_before_symbol: { title: "Insert before symbol", write: true, destructive: false, idempotent: false },
11330
- write_memory: { title: "Write memory", write: true, destructive: false, idempotent: true },
11331
- read_memory: { title: "Read memory" },
11332
- list_memories: { title: "List memories" },
11333
- delete_memory: { title: "Delete memory", write: true, destructive: true, idempotent: true },
11334
- dead_code: { title: "Dead-code candidates" },
11335
- complexity: { title: "Complexity" },
11336
- mermaid: { title: "Mermaid module diagram" },
11337
- grep: { title: "Grep file contents" },
11338
- search: { title: "Lexical search", openWorld: true },
11339
- embed_status: { title: "Embedding tier status", openWorld: true },
11340
- check_rules: { title: "Check architecture rules" }
11341
- };
11342
- DEFAULT_MAX_RESPONSE_BYTES = 1e6;
11343
- NARROWER = {
11344
- graph: "pass `scope` to a subdirectory, or use repo_map / mermaid for an overview",
11345
- symbols: "pass `name` to look up one symbol, or use find_symbol / symbols_overview",
11346
- callers: "pass `name` to look up one symbol's call sites",
11347
- dead_code: "pass `scope` to a subdirectory",
11348
- find_references: "the symbol is referenced very widely \u2014 narrow with `scope` on a graph query",
11349
- check_rules: "narrow the rule set, or pass `scope` to a subdirectory"
11350
- };
11351
- ARTIFACT_FOR = { graph: "graph.json", symbols: "symbols.json" };
11352
11533
  }
11353
11534
  });
11354
11535
 
@@ -12640,7 +12821,7 @@ init_types();
12640
12821
  init_types();
12641
12822
  init_loader();
12642
12823
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
12643
- import { join as join17, resolve as resolve2 } from "path";
12824
+ import { join as join19, resolve as resolve2 } from "path";
12644
12825
  init_pipeline();
12645
12826
  init_hash();
12646
12827
  init_graph_json();
@@ -12983,7 +13164,7 @@ async function runCli(rawArgv) {
12983
13164
  if (!flags2.out) throw new Error("index needs --out <dir>");
12984
13165
  const outDir = flags2.out;
12985
13166
  mkdirSync3(outDir, { recursive: true });
12986
- const cachePath = join17(outDir, "cache.json");
13167
+ const cachePath = join19(outDir, "cache.json");
12987
13168
  let cache;
12988
13169
  let meta = {};
12989
13170
  try {
@@ -13008,9 +13189,9 @@ async function runCli(rawArgv) {
13008
13189
  });
13009
13190
  const modelDir = resolveEmbedModelDir(flags2.repo);
13010
13191
  const model = modelDir ? loadEmbedModel(modelDir) : void 0;
13011
- const graphPath = join17(outDir, "graph.json");
13012
- const symbolsPath = join17(outDir, "symbols.json");
13013
- const embedPath = join17(outDir, "embeddings.bin");
13192
+ const graphPath = join19(outDir, "graph.json");
13193
+ const symbolsPath = join19(outDir, "symbols.json");
13194
+ const embedPath = join19(outDir, "embeddings.bin");
13014
13195
  const artifactSha = (path) => {
13015
13196
  try {
13016
13197
  return sha1(readFileSync9(path));
@@ -13194,14 +13375,14 @@ async function runCli(rawArgv) {
13194
13375
  mkdirSync3(flags2.out, { recursive: true });
13195
13376
  const scan2 = readScan();
13196
13377
  const index = buildEmbeddingIndex(scan2, model);
13197
- writeFileSync4(join17(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
13378
+ writeFileSync4(join19(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
13198
13379
  process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
13199
13380
  `);
13200
13381
  } else if (sub === "pull") {
13201
13382
  const { url, sha256 } = resolveEmbedPullUrl();
13202
- const destDir = process.env.CODEINDEX_EMBED_DIR ?? join17(flags2.repo, ".codeindex", "models");
13383
+ const destDir = process.env.CODEINDEX_EMBED_DIR ?? join19(flags2.repo, ".codeindex", "models");
13203
13384
  mkdirSync3(destDir, { recursive: true });
13204
- process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join17(destDir, "model.json")}
13385
+ process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join19(destDir, "model.json")}
13205
13386
  `);
13206
13387
  let body2;
13207
13388
  try {
@@ -13222,8 +13403,8 @@ async function runCli(rawArgv) {
13222
13403
  process.exitCode = 1;
13223
13404
  return;
13224
13405
  }
13225
- writeFileSync4(join17(destDir, "model.json"), body2);
13226
- process.stderr.write(`codeindex: model written to ${join17(destDir, "model.json")}
13406
+ writeFileSync4(join19(destDir, "model.json"), body2);
13407
+ process.stderr.write(`codeindex: model written to ${join19(destDir, "model.json")}
13227
13408
  `);
13228
13409
  } else {
13229
13410
  throw new Error("embed needs a subcommand: status | build | pull | serve");
@@ -13233,7 +13414,7 @@ async function runCli(rawArgv) {
13233
13414
  const cacheDir = sharedGrammarsCacheDir();
13234
13415
  if (sub === "status") {
13235
13416
  const info2 = resolveGrammarsTier();
13236
- const runtimePresent = info2.dir ? existsSync7(join17(info2.dir, "web-tree-sitter.wasm")) : false;
13417
+ const runtimePresent = info2.dir ? existsSync7(join19(info2.dir, "web-tree-sitter.wasm")) : false;
13237
13418
  const target = resolveGrammarsPullTarget();
13238
13419
  const status = {
13239
13420
  engineVersion: ENGINE_VERSION,