@vohongtho.infotech/code-intel 0.9.0 → 1.0.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/dist/cli/main.js CHANGED
@@ -7,8 +7,8 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
7
7
  import { SpanStatusCode, trace, context } from '@opentelemetry/api';
8
8
  import winston from 'winston';
9
9
  import DailyRotateFile from 'winston-daily-rotate-file';
10
- import fs37, { readFileSync, existsSync } from 'fs';
11
- import path38, { dirname, join } from 'path';
10
+ import fs38, { readFileSync, existsSync } from 'fs';
11
+ import path39, { dirname, join } from 'path';
12
12
  import os13 from 'os';
13
13
  import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client';
14
14
  import { createRequire } from 'module';
@@ -18,8 +18,8 @@ import { v4 } from 'uuid';
18
18
  import crypto5 from 'crypto';
19
19
  import { Worker } from 'worker_threads';
20
20
  import { EventEmitter } from 'events';
21
- import Database from 'better-sqlite3';
22
- import { Database as Database$1, Connection } from '@ladybugdb/core';
21
+ import Database2 from 'better-sqlite3';
22
+ import { Database, Connection } from '@ladybugdb/core';
23
23
  import { execSync } from 'child_process';
24
24
  import bcrypt from 'bcrypt';
25
25
  import * as oidcClient from 'openid-client';
@@ -327,7 +327,7 @@ var init_logger = __esm({
327
327
  };
328
328
  }
329
329
  /** Global log directory: ~/.code-intel/logs */
330
- static LOG_DIR = path38.join(os13.homedir(), ".code-intel", "logs");
330
+ static LOG_DIR = path39.join(os13.homedir(), ".code-intel", "logs");
331
331
  static getLogger() {
332
332
  if (!_Logger.instance) {
333
333
  const isProduction = process.env.NODE_ENV === "production";
@@ -336,12 +336,12 @@ var init_logger = __esm({
336
336
  transports.push(new winston.transports.Console());
337
337
  if (!isProduction) {
338
338
  try {
339
- if (!fs37.existsSync(_Logger.LOG_DIR)) {
340
- fs37.mkdirSync(_Logger.LOG_DIR, { recursive: true });
339
+ if (!fs38.existsSync(_Logger.LOG_DIR)) {
340
+ fs38.mkdirSync(_Logger.LOG_DIR, { recursive: true });
341
341
  }
342
342
  transports.push(
343
343
  new DailyRotateFile({
344
- filename: path38.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
344
+ filename: path39.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
345
345
  datePattern: "YYYY-MM-DD",
346
346
  maxSize: "20m",
347
347
  maxFiles: "14d"
@@ -518,6 +518,64 @@ var init_knowledge_graph = __esm({
518
518
  }
519
519
  });
520
520
 
521
+ // src/storage/schema.ts
522
+ function getCreateNodeTableDDL(tableName) {
523
+ return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
524
+ id STRING,
525
+ name STRING,
526
+ file_path STRING,
527
+ start_line INT64,
528
+ end_line INT64,
529
+ exported BOOLEAN,
530
+ content STRING,
531
+ metadata STRING,
532
+ PRIMARY KEY (id)
533
+ )`;
534
+ }
535
+ function getCreateEdgeTableDDL() {
536
+ const uniqueTables = ALL_NODE_TABLES;
537
+ const fromToPairs = [];
538
+ for (const from of uniqueTables) {
539
+ for (const to of uniqueTables) {
540
+ fromToPairs.push(`FROM ${from} TO ${to}`);
541
+ }
542
+ }
543
+ return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
544
+ ${fromToPairs.join(",\n ")},
545
+ kind STRING,
546
+ weight DOUBLE,
547
+ label STRING
548
+ )`];
549
+ }
550
+ var NODE_TABLE_MAP, ALL_NODE_TABLES;
551
+ var init_schema = __esm({
552
+ "src/storage/schema.ts"() {
553
+ NODE_TABLE_MAP = {
554
+ file: "file_nodes",
555
+ directory: "dir_nodes",
556
+ function: "func_nodes",
557
+ class: "class_nodes",
558
+ interface: "iface_nodes",
559
+ method: "method_nodes",
560
+ constructor: "ctor_nodes",
561
+ variable: "var_nodes",
562
+ property: "prop_nodes",
563
+ struct: "struct_nodes",
564
+ enum: "enum_nodes",
565
+ trait: "trait_nodes",
566
+ namespace: "ns_nodes",
567
+ module: "mod_nodes",
568
+ type_alias: "type_nodes",
569
+ constant: "const_nodes",
570
+ route: "route_nodes",
571
+ cluster: "cluster_nodes",
572
+ flow: "flow_nodes",
573
+ vulnerability: "vuln_nodes"
574
+ };
575
+ ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
576
+ }
577
+ });
578
+
521
579
  // src/pipeline/dag-validator.ts
522
580
  function validateDAG(phases) {
523
581
  const errors = [];
@@ -541,25 +599,25 @@ function validateDAG(phases) {
541
599
  const visiting = /* @__PURE__ */ new Set();
542
600
  const visited = /* @__PURE__ */ new Set();
543
601
  const phaseMap = new Map(phases.map((p) => [p.name, p]));
544
- function dfs(name, path39) {
602
+ function dfs(name, path40) {
545
603
  if (visiting.has(name)) {
546
- const cycleStart = path39.indexOf(name);
547
- const cycle = path39.slice(cycleStart).concat(name);
604
+ const cycleStart = path40.indexOf(name);
605
+ const cycle = path40.slice(cycleStart).concat(name);
548
606
  errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
549
607
  return true;
550
608
  }
551
609
  if (visited.has(name)) return false;
552
610
  visiting.add(name);
553
- path39.push(name);
611
+ path40.push(name);
554
612
  const phase = phaseMap.get(name);
555
613
  if (phase) {
556
614
  for (const dep of phase.dependencies) {
557
- if (dfs(dep, path39)) return true;
615
+ if (dfs(dep, path40)) return true;
558
616
  }
559
617
  }
560
618
  visiting.delete(name);
561
619
  visited.add(name);
562
- path39.pop();
620
+ path40.pop();
563
621
  return false;
564
622
  }
565
623
  for (const phase of phases) {
@@ -697,6 +755,7 @@ ${errors.map((e) => e.message).join("\n")}`);
697
755
  for (const phase of sorted) {
698
756
  context2.onProgress?.(phase.name, "running");
699
757
  const phaseStart = Date.now();
758
+ const memBefore = context2.profile ? Math.round(process.memoryUsage().heapUsed / 1024 / 1024) : void 0;
700
759
  const runPhase = async () => {
701
760
  const depResults = /* @__PURE__ */ new Map();
702
761
  for (const dep of phase.dependencies) {
@@ -720,6 +779,10 @@ ${errors.map((e) => e.message).join("\n")}`);
720
779
  }
721
780
  const durationSec = (Date.now() - phaseStart) / 1e3;
722
781
  pipelinePhaseDurationSeconds.observe({ phase: phase.name, status: result.status }, durationSec);
782
+ if (memBefore !== void 0) {
783
+ result.memoryBeforeMB = memBefore;
784
+ result.memoryAfterMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
785
+ }
723
786
  results.set(phase.name, result);
724
787
  context2.onProgress?.(phase.name, result.status);
725
788
  if (result.status === "failed") {
@@ -730,7 +793,9 @@ ${errors.map((e) => e.message).join("\n")}`);
730
793
  const result = {
731
794
  status: "failed",
732
795
  duration: Date.now() - phaseStart,
733
- message: err instanceof Error ? err.message : String(err)
796
+ message: err instanceof Error ? err.message : String(err),
797
+ memoryBeforeMB: memBefore,
798
+ memoryAfterMB: memBefore !== void 0 ? Math.round(process.memoryUsage().heapUsed / 1024 / 1024) : void 0
734
799
  };
735
800
  pipelinePhaseDurationSeconds.observe({ phase: phase.name, status: "failed" }, (Date.now() - phaseStart) / 1e3);
736
801
  results.set(phase.name, result);
@@ -824,7 +889,7 @@ var init_id_generator = __esm({
824
889
  });
825
890
  function loadIgnorePatterns(workspaceRoot) {
826
891
  try {
827
- const raw = fs37.readFileSync(path38.join(workspaceRoot, ".codeintelignore"), "utf-8");
892
+ const raw = fs38.readFileSync(path39.join(workspaceRoot, ".codeintelignore"), "utf-8");
828
893
  const extras = /* @__PURE__ */ new Set();
829
894
  for (const line of raw.split("\n")) {
830
895
  const trimmed = line.trim();
@@ -884,7 +949,7 @@ var init_scan_phase = __esm({
884
949
  function walk2(dir) {
885
950
  let entries;
886
951
  try {
887
- entries = fs37.readdirSync(dir, { withFileTypes: true });
952
+ entries = fs38.readdirSync(dir, { withFileTypes: true });
888
953
  } catch {
889
954
  return;
890
955
  }
@@ -893,15 +958,15 @@ var init_scan_phase = __esm({
893
958
  if (entry.name.startsWith(".")) continue;
894
959
  if (IGNORED_DIRS.has(entry.name)) continue;
895
960
  if (extraIgnore.has(entry.name)) continue;
896
- walk2(path38.join(dir, entry.name));
961
+ walk2(path39.join(dir, entry.name));
897
962
  } else if (entry.isFile()) {
898
963
  const name = entry.name;
899
964
  if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
900
- const ext = path38.extname(name);
965
+ const ext = path39.extname(name);
901
966
  if (!extensions.has(ext)) continue;
902
- const fullPath = path38.join(dir, name);
967
+ const fullPath = path39.join(dir, name);
903
968
  try {
904
- const stat = fs37.statSync(fullPath);
969
+ const stat = fs38.statSync(fullPath);
905
970
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
906
971
  } catch {
907
972
  continue;
@@ -928,20 +993,20 @@ var init_scan_phase = __esm({
928
993
  const dirs = /* @__PURE__ */ new Set();
929
994
  let structDone = 0;
930
995
  for (const filePath of context2.filePaths) {
931
- const relativePath = path38.relative(context2.workspaceRoot, filePath);
996
+ const relativePath = path39.relative(context2.workspaceRoot, filePath);
932
997
  const lang = detectLanguage(filePath);
933
998
  context2.graph.addNode({
934
999
  id: generateNodeId("file", relativePath, relativePath),
935
1000
  kind: "file",
936
- name: path38.basename(filePath),
1001
+ name: path39.basename(filePath),
937
1002
  filePath: relativePath,
938
1003
  metadata: lang ? { language: lang } : void 0
939
1004
  });
940
- let dir = path38.dirname(relativePath);
1005
+ let dir = path39.dirname(relativePath);
941
1006
  while (dir && dir !== "." && dir !== "") {
942
1007
  if (dirs.has(dir)) break;
943
1008
  dirs.add(dir);
944
- dir = path38.dirname(dir);
1009
+ dir = path39.dirname(dir);
945
1010
  }
946
1011
  structDone++;
947
1012
  context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
@@ -950,7 +1015,7 @@ var init_scan_phase = __esm({
950
1015
  context2.graph.addNode({
951
1016
  id: generateNodeId("directory", dir, dir),
952
1017
  kind: "directory",
953
- name: path38.basename(dir),
1018
+ name: path39.basename(dir),
954
1019
  filePath: dir
955
1020
  });
956
1021
  }
@@ -964,11 +1029,11 @@ var init_scan_phase = __esm({
964
1029
  }
965
1030
  });
966
1031
  function findBundledWasmDir() {
967
- const fileDir = path38.dirname(fileURLToPath(import.meta.url));
1032
+ const fileDir = path39.dirname(fileURLToPath(import.meta.url));
968
1033
  const candidates = [
969
- path38.join(fileDir, "wasm"),
1034
+ path39.join(fileDir, "wasm"),
970
1035
  // dist/index.js → dist/wasm/
971
- path38.join(fileDir, "../wasm")
1036
+ path39.join(fileDir, "../wasm")
972
1037
  // dist/cli/main.js → dist/wasm/
973
1038
  ];
974
1039
  for (const candidate of candidates) {
@@ -1009,7 +1074,7 @@ function wasmPath(lang) {
1009
1074
  }
1010
1075
  const bundled = BUNDLED_WASM_MAP[lang];
1011
1076
  if (bundled) {
1012
- const bundledPath = path38.join(_bundledWasmDir, bundled);
1077
+ const bundledPath = path39.join(_bundledWasmDir, bundled);
1013
1078
  if (existsSync(bundledPath)) return bundledPath;
1014
1079
  }
1015
1080
  return null;
@@ -1022,14 +1087,14 @@ async function initParser() {
1022
1087
  }
1023
1088
  async function getLanguage(lang) {
1024
1089
  if (languageCache.has(lang)) return languageCache.get(lang);
1025
- const path39 = wasmPath(lang);
1026
- if (!path39) {
1090
+ const path40 = wasmPath(lang);
1091
+ if (!path40) {
1027
1092
  languageCache.set(lang, null);
1028
1093
  return null;
1029
1094
  }
1030
1095
  try {
1031
1096
  await initParser();
1032
- const language = await Language.load(path39);
1097
+ const language = await Language.load(path40);
1033
1098
  languageCache.set(lang, language);
1034
1099
  return language;
1035
1100
  } catch {
@@ -2454,7 +2519,7 @@ var init_parse_phase = __esm({
2454
2519
  const batch = filePaths.slice(i, i + CONCURRENCY);
2455
2520
  await Promise.all(batch.map(async (filePath) => {
2456
2521
  try {
2457
- const source = await fs37.promises.readFile(filePath, "utf-8");
2522
+ const source = await fs38.promises.readFile(filePath, "utf-8");
2458
2523
  context2.fileCache.set(filePath, source);
2459
2524
  } catch {
2460
2525
  }
@@ -2467,14 +2532,14 @@ var init_parse_phase = __esm({
2467
2532
  const lang = detectLanguage(filePath);
2468
2533
  if (!lang) {
2469
2534
  if (context2.verbose) {
2470
- const relativePath2 = path38.relative(context2.workspaceRoot, filePath);
2535
+ const relativePath2 = path39.relative(context2.workspaceRoot, filePath);
2471
2536
  logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
2472
2537
  }
2473
2538
  continue;
2474
2539
  }
2475
2540
  const source = context2.fileCache.get(filePath);
2476
2541
  if (!source) continue;
2477
- const relativePath = path38.relative(context2.workspaceRoot, filePath);
2542
+ const relativePath = path39.relative(context2.workspaceRoot, filePath);
2478
2543
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2479
2544
  const fileNode = context2.graph.getNode(fileNodeId);
2480
2545
  if (fileNode) {
@@ -2720,11 +2785,11 @@ var init_resolve_phase = __esm({
2720
2785
  let heritageEdges = 0;
2721
2786
  const fileIndex = /* @__PURE__ */ new Map();
2722
2787
  for (const fp of filePaths) {
2723
- const rel = path38.relative(workspaceRoot, fp);
2788
+ const rel = path39.relative(workspaceRoot, fp);
2724
2789
  fileIndex.set(rel, fp);
2725
2790
  const noExt = rel.replace(/\.\w+$/, "");
2726
2791
  if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
2727
- const base = path38.basename(rel, path38.extname(rel));
2792
+ const base = path39.basename(rel, path39.extname(rel));
2728
2793
  if (!fileIndex.has(base)) fileIndex.set(base, fp);
2729
2794
  }
2730
2795
  const symbolIndex = /* @__PURE__ */ new Map();
@@ -2755,7 +2820,7 @@ var init_resolve_phase = __esm({
2755
2820
  for (const filePath of filePaths) {
2756
2821
  const lang = detectLanguage(filePath);
2757
2822
  if (!lang) continue;
2758
- const relativePath = path38.relative(workspaceRoot, filePath);
2823
+ const relativePath = path39.relative(workspaceRoot, filePath);
2759
2824
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2760
2825
  const source = fileCache.get(filePath);
2761
2826
  if (!source) continue;
@@ -2768,13 +2833,13 @@ var init_resolve_phase = __esm({
2768
2833
  let resolvedRelPath = null;
2769
2834
  if (cleaned.startsWith(".")) {
2770
2835
  const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
2771
- const fromDir = path38.dirname(relativePath);
2836
+ const fromDir = path39.dirname(relativePath);
2772
2837
  for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
2773
- const candidate = path38.join(fromDir, cleanedNoJs + ext);
2774
- const normalized = path38.normalize(candidate);
2838
+ const candidate = path39.join(fromDir, cleanedNoJs + ext);
2839
+ const normalized = path39.normalize(candidate);
2775
2840
  if (fileIndex.has(normalized)) {
2776
2841
  const absPath = fileIndex.get(normalized);
2777
- resolvedRelPath = path38.relative(workspaceRoot, absPath);
2842
+ resolvedRelPath = path39.relative(workspaceRoot, absPath);
2778
2843
  break;
2779
2844
  }
2780
2845
  }
@@ -2995,22 +3060,22 @@ var init_flow_phase = __esm({
2995
3060
  const queue = [{ nodeId: ep.id, path: [ep.id] }];
2996
3061
  const visited = /* @__PURE__ */ new Set();
2997
3062
  while (queue.length > 0 && flowCount < maxFlows) {
2998
- const { nodeId, path: path39 } = queue.shift();
2999
- if (path39.length > maxDepth) continue;
3063
+ const { nodeId, path: path40 } = queue.shift();
3064
+ if (path40.length > maxDepth) continue;
3000
3065
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
3001
- if (callEdges.length === 0 && path39.length >= 3) {
3066
+ if (callEdges.length === 0 && path40.length >= 3) {
3002
3067
  const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
3003
3068
  graph.addNode({
3004
3069
  id: flowId,
3005
3070
  kind: "flow",
3006
3071
  name: `${ep.name} flow ${flowCount}`,
3007
3072
  filePath: ep.filePath,
3008
- metadata: { steps: path39, entryPoint: ep.name }
3073
+ metadata: { steps: path40, entryPoint: ep.name }
3009
3074
  });
3010
- for (let i = 0; i < path39.length; i++) {
3075
+ for (let i = 0; i < path40.length; i++) {
3011
3076
  graph.addEdge({
3012
- id: generateEdgeId(path39[i], flowId, `step_of_${i}`),
3013
- source: path39[i],
3077
+ id: generateEdgeId(path40[i], flowId, `step_of_${i}`),
3078
+ source: path40[i],
3014
3079
  target: flowId,
3015
3080
  kind: "step_of",
3016
3081
  weight: 1,
@@ -3023,7 +3088,7 @@ var init_flow_phase = __esm({
3023
3088
  for (const edge of callEdges) {
3024
3089
  if (visited.has(edge.target)) continue;
3025
3090
  visited.add(edge.target);
3026
- queue.push({ nodeId: edge.target, path: [...path39, edge.target] });
3091
+ queue.push({ nodeId: edge.target, path: [...path40, edge.target] });
3027
3092
  }
3028
3093
  }
3029
3094
  }
@@ -3046,7 +3111,7 @@ var init_llm_governance = __esm({
3046
3111
  }
3047
3112
  /** Path to the JSONL log file. */
3048
3113
  getLogPath() {
3049
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path38.join(os13.homedir(), ".code-intel", "llm-governance.jsonl");
3114
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path39.join(os13.homedir(), ".code-intel", "llm-governance.jsonl");
3050
3115
  }
3051
3116
  /**
3052
3117
  * Append an entry to the governance log.
@@ -3062,8 +3127,8 @@ var init_llm_governance = __esm({
3062
3127
  ...entry
3063
3128
  };
3064
3129
  const logPath = this.getLogPath();
3065
- fs37.mkdirSync(path38.dirname(logPath), { recursive: true });
3066
- fs37.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
3130
+ fs38.mkdirSync(path39.dirname(logPath), { recursive: true });
3131
+ fs38.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
3067
3132
  } catch {
3068
3133
  }
3069
3134
  }
@@ -3073,7 +3138,7 @@ var init_llm_governance = __esm({
3073
3138
  */
3074
3139
  readLog(limit = 100) {
3075
3140
  try {
3076
- const raw = fs37.readFileSync(this.getLogPath(), "utf-8");
3141
+ const raw = fs38.readFileSync(this.getLogPath(), "utf-8");
3077
3142
  const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
3078
3143
  return lines.map((l) => JSON.parse(l));
3079
3144
  } catch {
@@ -3297,8 +3362,14 @@ function createSummarizePhase(providerOverride) {
3297
3362
  if (providerOverride) {
3298
3363
  provider = providerOverride;
3299
3364
  } else {
3300
- const { createLLMProvider: createLLMProvider2 } = await Promise.resolve().then(() => (init_factory(), factory_exports));
3301
- provider = await createLLMProvider2(llmConfig ?? {});
3365
+ try {
3366
+ const { createLLMProvider: createLLMProvider2 } = await Promise.resolve().then(() => (init_factory(), factory_exports));
3367
+ provider = await createLLMProvider2(llmConfig ?? {});
3368
+ } catch (err) {
3369
+ const msg2 = err instanceof Error ? err.message : String(err);
3370
+ logger_default.warn(`[summarize] LLM provider unavailable: ${msg2}. Skipping summarize phase.`);
3371
+ return { status: "completed", duration: Date.now() - start, message: `Summarize skipped: LLM API unavailable (${msg2})` };
3372
+ }
3302
3373
  }
3303
3374
  const breaker = new CircuitBreaker();
3304
3375
  const batchSize = llmConfig?.batchSize ?? 20;
@@ -3386,6 +3457,7 @@ var init_summarize_phase = __esm({
3386
3457
  "src/pipeline/phases/summarize-phase.ts"() {
3387
3458
  init_llm_governance();
3388
3459
  init_retry();
3460
+ init_logger();
3389
3461
  SUMMARIZABLE_KINDS = /* @__PURE__ */ new Set(["function", "class", "method", "interface"]);
3390
3462
  MAX_SNIPPET_LINES = 200;
3391
3463
  summarizePhase = createSummarizePhase();
@@ -3500,7 +3572,7 @@ var init_worker_pool = __esm({
3500
3572
  });
3501
3573
  function workerScriptPath() {
3502
3574
  const thisFile = fileURLToPath(import.meta.url);
3503
- return path38.join(path38.dirname(thisFile), "parse-worker.js");
3575
+ return path39.join(path39.dirname(thisFile), "parse-worker.js");
3504
3576
  }
3505
3577
  var LANG_QUERIES2, parsePhaseParallel;
3506
3578
  var init_parse_phase_parallel = __esm({
@@ -3540,14 +3612,14 @@ var init_parse_phase_parallel = __esm({
3540
3612
  const batch = filePaths.slice(i, i + CONCURRENCY);
3541
3613
  await Promise.all(batch.map(async (filePath) => {
3542
3614
  try {
3543
- const source = await fs37.promises.readFile(filePath, "utf-8");
3615
+ const source = await fs38.promises.readFile(filePath, "utf-8");
3544
3616
  context2.fileCache.set(filePath, source);
3545
3617
  } catch {
3546
3618
  }
3547
3619
  }));
3548
3620
  }
3549
3621
  const workerScript = workerScriptPath();
3550
- const workerScriptExists = fs37.existsSync(workerScript);
3622
+ const workerScriptExists = fs38.existsSync(workerScript);
3551
3623
  if (!workerScriptExists || workerCount === 1) {
3552
3624
  logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
3553
3625
  const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
@@ -3559,7 +3631,7 @@ var init_parse_phase_parallel = __esm({
3559
3631
  if (!lang) continue;
3560
3632
  const source = context2.fileCache.get(filePath);
3561
3633
  if (!source) continue;
3562
- const relativePath = path38.relative(context2.workspaceRoot, filePath);
3634
+ const relativePath = path39.relative(context2.workspaceRoot, filePath);
3563
3635
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
3564
3636
  const fileNode = context2.graph.getNode(fileNodeId);
3565
3637
  if (fileNode) fileNode.content = source.slice(0, 2e3);
@@ -3602,7 +3674,7 @@ var init_parse_phase_parallel = __esm({
3602
3674
  symbolCount += res.nodes.length;
3603
3675
  if (res.usedTreeSitter) treeSitterCount++;
3604
3676
  else regexCount++;
3605
- const relativePath = path38.relative(context2.workspaceRoot, res.taskId);
3677
+ const relativePath = path39.relative(context2.workspaceRoot, res.taskId);
3606
3678
  const funcs = res.nodes.filter((n) => n.kind === "function" || n.kind === "method").map((n) => ({ id: n.id, startLine: n.startLine ?? 0, endLine: n.endLine })).sort((a, b) => a.startLine - b.startLine);
3607
3679
  if (funcs.length > 0) context2.fileFunctionIndex.set(relativePath, funcs);
3608
3680
  parseDone++;
@@ -3626,7 +3698,7 @@ var init_parse_phase_parallel = __esm({
3626
3698
  });
3627
3699
  function workerScriptPath2() {
3628
3700
  const thisFile = fileURLToPath(import.meta.url);
3629
- return path38.join(path38.dirname(thisFile), "resolve-worker.js");
3701
+ return path39.join(path39.dirname(thisFile), "resolve-worker.js");
3630
3702
  }
3631
3703
  var resolvePhaseParallel;
3632
3704
  var init_resolve_phase_parallel = __esm({
@@ -3644,11 +3716,11 @@ var init_resolve_phase_parallel = __esm({
3644
3716
  const fileFunctionIndex = context2.fileFunctionIndex ?? /* @__PURE__ */ new Map();
3645
3717
  const fileIndex = {};
3646
3718
  for (const fp of filePaths) {
3647
- const rel = path38.relative(workspaceRoot, fp);
3719
+ const rel = path39.relative(workspaceRoot, fp);
3648
3720
  fileIndex[rel] = fp;
3649
3721
  const noExt = rel.replace(/\.\w+$/, "");
3650
3722
  if (!fileIndex[noExt]) fileIndex[noExt] = fp;
3651
- const base = path38.basename(rel, path38.extname(rel));
3723
+ const base = path39.basename(rel, path39.extname(rel));
3652
3724
  if (!fileIndex[base]) fileIndex[base] = fp;
3653
3725
  }
3654
3726
  const symbolIndex = {};
@@ -3662,7 +3734,7 @@ var init_resolve_phase_parallel = __esm({
3662
3734
  }
3663
3735
  const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
3664
3736
  const workerScript = workerScriptPath2();
3665
- const workerScriptExists = fs37.existsSync(workerScript);
3737
+ const workerScriptExists = fs38.existsSync(workerScript);
3666
3738
  const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os13.cpus().length - 1);
3667
3739
  if (!workerScriptExists || workerCount === 1) {
3668
3740
  logger_default.info(`[resolve-parallel] falling back to sequential`);
@@ -3675,7 +3747,7 @@ var init_resolve_phase_parallel = __esm({
3675
3747
  if (!lang) continue;
3676
3748
  const source = fileCache.get(filePath);
3677
3749
  if (!source) continue;
3678
- const relativePath = path38.relative(workspaceRoot, filePath);
3750
+ const relativePath = path39.relative(workspaceRoot, filePath);
3679
3751
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
3680
3752
  const funcList = fileFunctionIndex.get(relativePath) ?? [];
3681
3753
  tasks.push({ taskId: filePath, filePath, relativePath, fileNodeId, source, funcList });
@@ -3823,7 +3895,7 @@ var init_vector_index = __esm({
3823
3895
  this.sqlitePath = sqlitePath;
3824
3896
  }
3825
3897
  async init() {
3826
- this.db = new Database(this.sqlitePath);
3898
+ this.db = new Database2(this.sqlitePath);
3827
3899
  this.db.pragma("journal_mode = WAL");
3828
3900
  this.db.exec(`
3829
3901
  CREATE TABLE IF NOT EXISTS ${EMBED_TABLE} (
@@ -3981,6 +4053,278 @@ var init_embedder = __esm({
3981
4053
  pipelineInstance = null;
3982
4054
  }
3983
4055
  });
4056
+
4057
+ // src/search/bm25-index.ts
4058
+ var bm25_index_exports = {};
4059
+ __export(bm25_index_exports, {
4060
+ Bm25Index: () => Bm25Index,
4061
+ getBm25DbPath: () => getBm25DbPath
4062
+ });
4063
+ function tokenize(text) {
4064
+ return text.toLowerCase().split(/[\s\-_./\\:(){}[\]<>,"'`~!@#$%^&*+=|;?]+/).filter((t) => t.length >= 2 && t.length <= 64);
4065
+ }
4066
+ function nodeToDoc(node) {
4067
+ return [
4068
+ node.name,
4069
+ node.kind,
4070
+ node.filePath,
4071
+ (node.content ?? "").slice(0, 1e3)
4072
+ ].join(" ");
4073
+ }
4074
+ function getBm25DbPath(workspaceRoot) {
4075
+ return path39.join(workspaceRoot, ".code-intel", "bm25.db");
4076
+ }
4077
+ var K1, B, Bm25Index;
4078
+ var init_bm25_index = __esm({
4079
+ "src/search/bm25-index.ts"() {
4080
+ init_logger();
4081
+ K1 = 1.2;
4082
+ B = 0.75;
4083
+ Bm25Index = class {
4084
+ constructor(dbPath) {
4085
+ this.dbPath = dbPath;
4086
+ }
4087
+ dbPath;
4088
+ /** In-memory inverted index (populated after `load()`). */
4089
+ invertedIndex = /* @__PURE__ */ new Map();
4090
+ docLengths = /* @__PURE__ */ new Map();
4091
+ nodeMeta = /* @__PURE__ */ new Map();
4092
+ avgdl = 1;
4093
+ docCount = 0;
4094
+ _loaded = false;
4095
+ get isLoaded() {
4096
+ return this._loaded;
4097
+ }
4098
+ // ── Build ───────────────────────────────────────────────────────────────────
4099
+ /**
4100
+ * Build the inverted index from a KnowledgeGraph and persist to SQLite.
4101
+ * Called once at analysis time after the main pipeline completes.
4102
+ */
4103
+ build(graph) {
4104
+ const nodeTermFreqs = /* @__PURE__ */ new Map();
4105
+ const docLengths = /* @__PURE__ */ new Map();
4106
+ const nodeMeta = /* @__PURE__ */ new Map();
4107
+ for (const node of graph.allNodes()) {
4108
+ if (["directory", "cluster", "flow"].includes(node.kind)) continue;
4109
+ const terms = tokenize(nodeToDoc(node));
4110
+ const tf = /* @__PURE__ */ new Map();
4111
+ for (const t of terms) tf.set(t, (tf.get(t) ?? 0) + 1);
4112
+ nodeTermFreqs.set(node.id, tf);
4113
+ docLengths.set(node.id, terms.length);
4114
+ nodeMeta.set(node.id, {
4115
+ name: node.name,
4116
+ kind: node.kind,
4117
+ filePath: node.filePath,
4118
+ snippet: node.content?.slice(0, 200)
4119
+ });
4120
+ }
4121
+ const docCount = nodeTermFreqs.size;
4122
+ const totalLen = [...docLengths.values()].reduce((a, b) => a + b, 0);
4123
+ const avgdl = docCount > 0 ? totalLen / docCount : 1;
4124
+ const invertedIndex = /* @__PURE__ */ new Map();
4125
+ for (const [nodeId, tf] of nodeTermFreqs) {
4126
+ for (const [term, count] of tf) {
4127
+ let postings = invertedIndex.get(term);
4128
+ if (!postings) {
4129
+ postings = [];
4130
+ invertedIndex.set(term, postings);
4131
+ }
4132
+ postings.push({ nodeId, tf: count });
4133
+ }
4134
+ }
4135
+ fs38.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
4136
+ for (const f of [this.dbPath, `${this.dbPath}-shm`, `${this.dbPath}-wal`]) {
4137
+ try {
4138
+ if (fs38.existsSync(f)) fs38.unlinkSync(f);
4139
+ } catch {
4140
+ }
4141
+ }
4142
+ const db = new Database2(this.dbPath);
4143
+ db.pragma("journal_mode = WAL");
4144
+ db.exec(`
4145
+ CREATE TABLE bm25_index (term TEXT PRIMARY KEY, postings TEXT NOT NULL);
4146
+ CREATE TABLE bm25_doclen (node_id TEXT PRIMARY KEY, doclen INTEGER NOT NULL);
4147
+ CREATE TABLE bm25_nodemeta(node_id TEXT PRIMARY KEY, name TEXT, kind TEXT, file_path TEXT, snippet TEXT);
4148
+ CREATE TABLE bm25_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
4149
+ `);
4150
+ const insPosting = db.prepare("INSERT OR REPLACE INTO bm25_index VALUES (?, ?)");
4151
+ const insDoclen = db.prepare("INSERT OR REPLACE INTO bm25_doclen VALUES (?, ?)");
4152
+ const insNodeMeta = db.prepare("INSERT OR REPLACE INTO bm25_nodemeta VALUES (?, ?, ?, ?, ?)");
4153
+ const insMeta = db.prepare("INSERT OR REPLACE INTO bm25_meta VALUES (?, ?)");
4154
+ db.transaction(() => {
4155
+ for (const [term, postings] of invertedIndex) {
4156
+ insPosting.run(term, JSON.stringify(postings));
4157
+ }
4158
+ for (const [nodeId, len] of docLengths) {
4159
+ insDoclen.run(nodeId, len);
4160
+ }
4161
+ for (const [nodeId, meta] of nodeMeta) {
4162
+ insNodeMeta.run(nodeId, meta.name, meta.kind, meta.filePath, meta.snippet ?? null);
4163
+ }
4164
+ insMeta.run("avgdl", String(avgdl));
4165
+ insMeta.run("docCount", String(docCount));
4166
+ })();
4167
+ db.close();
4168
+ logger_default.info(` [bm25] Index built: ${invertedIndex.size} terms, ${docCount} documents`);
4169
+ }
4170
+ // ── Load into memory ────────────────────────────────────────────────────────
4171
+ /**
4172
+ * Load the full inverted index into memory.
4173
+ * Called once on `serve` startup.
4174
+ */
4175
+ load() {
4176
+ if (!fs38.existsSync(this.dbPath)) return;
4177
+ const db = new Database2(this.dbPath, { readonly: true });
4178
+ try {
4179
+ const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
4180
+ this.avgdl = parseFloat(getMeta.get("avgdl")?.value ?? "1");
4181
+ this.docCount = parseInt(getMeta.get("docCount")?.value ?? "0", 10);
4182
+ this.invertedIndex.clear();
4183
+ const postingRows = db.prepare("SELECT term, postings FROM bm25_index").all();
4184
+ for (const row of postingRows) {
4185
+ this.invertedIndex.set(row.term, JSON.parse(row.postings));
4186
+ }
4187
+ this.docLengths.clear();
4188
+ const dlRows = db.prepare("SELECT node_id, doclen FROM bm25_doclen").all();
4189
+ for (const row of dlRows) {
4190
+ this.docLengths.set(row.node_id, row.doclen);
4191
+ }
4192
+ this.nodeMeta.clear();
4193
+ const metaRows = db.prepare("SELECT node_id, name, kind, file_path, snippet FROM bm25_nodemeta").all();
4194
+ for (const row of metaRows) {
4195
+ this.nodeMeta.set(row.node_id, {
4196
+ name: row.name,
4197
+ kind: row.kind,
4198
+ filePath: row.file_path,
4199
+ snippet: row.snippet ?? void 0
4200
+ });
4201
+ }
4202
+ this._loaded = true;
4203
+ logger_default.info(` [bm25] Index loaded (${this.invertedIndex.size} terms)`);
4204
+ } finally {
4205
+ db.close();
4206
+ }
4207
+ }
4208
+ // ── Search ──────────────────────────────────────────────────────────────────
4209
+ /**
4210
+ * BM25 search. LIMIT pushdown: scores only the posting lists for query terms,
4211
+ * then partial-sorts to return only the top `limit` results.
4212
+ */
4213
+ search(query, limit) {
4214
+ if (!this._loaded || this.invertedIndex.size === 0) return [];
4215
+ const queryTerms = [...new Set(tokenize(query))];
4216
+ if (queryTerms.length === 0) return [];
4217
+ const scores = /* @__PURE__ */ new Map();
4218
+ const N = this.docCount;
4219
+ const avgdl = this.avgdl;
4220
+ for (const term of queryTerms) {
4221
+ const postings = this.invertedIndex.get(term);
4222
+ if (!postings) continue;
4223
+ const df = postings.length;
4224
+ const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
4225
+ for (const { nodeId, tf } of postings) {
4226
+ const dl = this.docLengths.get(nodeId) ?? avgdl;
4227
+ const score = idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (dl / avgdl)));
4228
+ scores.set(nodeId, (scores.get(nodeId) ?? 0) + score);
4229
+ }
4230
+ }
4231
+ const entries = [...scores.entries()];
4232
+ entries.sort((a, b) => b[1] - a[1]);
4233
+ const topK = entries.slice(0, limit);
4234
+ return topK.map(([nodeId, score]) => {
4235
+ const meta = this.nodeMeta.get(nodeId);
4236
+ return {
4237
+ nodeId,
4238
+ name: meta?.name ?? nodeId,
4239
+ kind: meta?.kind ?? "unknown",
4240
+ filePath: meta?.filePath ?? "",
4241
+ score,
4242
+ snippet: meta?.snippet
4243
+ };
4244
+ });
4245
+ }
4246
+ // ── Incremental update ──────────────────────────────────────────────────────
4247
+ /**
4248
+ * Incrementally update index for a set of changed/added nodes.
4249
+ * Only terms that overlap with the changed nodes are rewritten.
4250
+ * Works even if `load()` was not called (reads affected terms directly from DB).
4251
+ */
4252
+ updateNodes(nodes) {
4253
+ if (!fs38.existsSync(this.dbPath)) return;
4254
+ if (nodes.length === 0) return;
4255
+ const changedIds = new Set(nodes.map((n) => n.id));
4256
+ const newTermFreqs = /* @__PURE__ */ new Map();
4257
+ for (const node of nodes) {
4258
+ if (["directory", "cluster", "flow"].includes(node.kind)) continue;
4259
+ const terms = tokenize(nodeToDoc(node));
4260
+ const tf = /* @__PURE__ */ new Map();
4261
+ for (const t of terms) tf.set(t, (tf.get(t) ?? 0) + 1);
4262
+ newTermFreqs.set(node.id, tf);
4263
+ }
4264
+ const newTermSet = new Set([...newTermFreqs.values()].flatMap((m) => [...m.keys()]));
4265
+ const db = new Database2(this.dbPath);
4266
+ db.pragma("journal_mode = WAL");
4267
+ const termsToRewrite = /* @__PURE__ */ new Map();
4268
+ for (const term of newTermSet) {
4269
+ const row = db.prepare("SELECT postings FROM bm25_index WHERE term = ?").get(term);
4270
+ const existing = row ? JSON.parse(row.postings) : [];
4271
+ termsToRewrite.set(term, existing.filter((p) => !changedIds.has(p.nodeId)));
4272
+ }
4273
+ for (const nodeId of changedIds) {
4274
+ const rows = db.prepare("SELECT term, postings FROM bm25_index WHERE postings LIKE ?").all(`%${nodeId}%`);
4275
+ for (const row of rows) {
4276
+ if (termsToRewrite.has(row.term)) continue;
4277
+ const postings = JSON.parse(row.postings);
4278
+ if (postings.some((p) => changedIds.has(p.nodeId))) {
4279
+ termsToRewrite.set(row.term, postings.filter((p) => !changedIds.has(p.nodeId)));
4280
+ }
4281
+ }
4282
+ }
4283
+ for (const [nodeId, tf] of newTermFreqs) {
4284
+ for (const [term, count] of tf) {
4285
+ const postings = termsToRewrite.get(term) ?? [];
4286
+ postings.push({ nodeId, tf: count });
4287
+ termsToRewrite.set(term, postings);
4288
+ }
4289
+ }
4290
+ const upsertPosting = db.prepare("INSERT OR REPLACE INTO bm25_index VALUES (?, ?)");
4291
+ const upsertDoclen = db.prepare("INSERT OR REPLACE INTO bm25_doclen VALUES (?, ?)");
4292
+ const upsertNodeMeta = db.prepare("INSERT OR REPLACE INTO bm25_nodemeta VALUES (?, ?, ?, ?, ?)");
4293
+ db.transaction(() => {
4294
+ for (const [term, postings] of termsToRewrite) {
4295
+ if (postings.length === 0) {
4296
+ db.prepare("DELETE FROM bm25_index WHERE term = ?").run(term);
4297
+ } else {
4298
+ upsertPosting.run(term, JSON.stringify(postings));
4299
+ }
4300
+ }
4301
+ for (const node of nodes) {
4302
+ const terms = tokenize(nodeToDoc(node));
4303
+ upsertDoclen.run(node.id, terms.length);
4304
+ upsertNodeMeta.run(node.id, node.name, node.kind, node.filePath, node.content?.slice(0, 200) ?? null);
4305
+ }
4306
+ })();
4307
+ db.close();
4308
+ if (this._loaded) {
4309
+ for (const [term, postings] of termsToRewrite) {
4310
+ if (postings.length === 0) this.invertedIndex.delete(term);
4311
+ else this.invertedIndex.set(term, postings);
4312
+ }
4313
+ for (const node of nodes) {
4314
+ const terms = tokenize(nodeToDoc(node));
4315
+ this.docLengths.set(node.id, terms.length);
4316
+ this.nodeMeta.set(node.id, {
4317
+ name: node.name,
4318
+ kind: node.kind,
4319
+ filePath: node.filePath,
4320
+ snippet: node.content?.slice(0, 200)
4321
+ });
4322
+ }
4323
+ }
4324
+ }
4325
+ };
4326
+ }
4327
+ });
3984
4328
  var DbManager;
3985
4329
  var init_db_manager = __esm({
3986
4330
  "src/storage/db-manager.ts"() {
@@ -3988,12 +4332,16 @@ var init_db_manager = __esm({
3988
4332
  db = null;
3989
4333
  conn = null;
3990
4334
  dbPath;
3991
- constructor(dbPath) {
4335
+ readOnly;
4336
+ constructor(dbPath, readOnly = false) {
3992
4337
  this.dbPath = dbPath;
4338
+ this.readOnly = readOnly;
3993
4339
  }
3994
4340
  async init() {
3995
- fs37.mkdirSync(path38.dirname(this.dbPath), { recursive: true });
3996
- this.db = new Database$1(this.dbPath);
4341
+ if (!this.readOnly) {
4342
+ fs38.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
4343
+ }
4344
+ this.db = new Database(this.dbPath, 0, true, this.readOnly);
3997
4345
  await this.db.init();
3998
4346
  this.conn = new Connection(this.db);
3999
4347
  await this.conn.init();
@@ -4030,66 +4378,8 @@ var init_db_manager = __esm({
4030
4378
  };
4031
4379
  }
4032
4380
  });
4033
-
4034
- // src/storage/schema.ts
4035
- function getCreateNodeTableDDL(tableName) {
4036
- return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
4037
- id STRING,
4038
- name STRING,
4039
- file_path STRING,
4040
- start_line INT64,
4041
- end_line INT64,
4042
- exported BOOLEAN,
4043
- content STRING,
4044
- metadata STRING,
4045
- PRIMARY KEY (id)
4046
- )`;
4047
- }
4048
- function getCreateEdgeTableDDL() {
4049
- const uniqueTables = ALL_NODE_TABLES;
4050
- const fromToPairs = [];
4051
- for (const from of uniqueTables) {
4052
- for (const to of uniqueTables) {
4053
- fromToPairs.push(`FROM ${from} TO ${to}`);
4054
- }
4055
- }
4056
- return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
4057
- ${fromToPairs.join(",\n ")},
4058
- kind STRING,
4059
- weight DOUBLE,
4060
- label STRING
4061
- )`];
4062
- }
4063
- var NODE_TABLE_MAP, ALL_NODE_TABLES;
4064
- var init_schema = __esm({
4065
- "src/storage/schema.ts"() {
4066
- NODE_TABLE_MAP = {
4067
- file: "file_nodes",
4068
- directory: "dir_nodes",
4069
- function: "func_nodes",
4070
- class: "class_nodes",
4071
- interface: "iface_nodes",
4072
- method: "method_nodes",
4073
- constructor: "ctor_nodes",
4074
- variable: "var_nodes",
4075
- property: "prop_nodes",
4076
- struct: "struct_nodes",
4077
- enum: "enum_nodes",
4078
- trait: "trait_nodes",
4079
- namespace: "ns_nodes",
4080
- module: "mod_nodes",
4081
- type_alias: "type_nodes",
4082
- constant: "const_nodes",
4083
- route: "route_nodes",
4084
- cluster: "cluster_nodes",
4085
- flow: "flow_nodes",
4086
- vulnerability: "vuln_nodes"
4087
- };
4088
- ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
4089
- }
4090
- });
4091
4381
  function writeNodeCSVs(graph, outputDir) {
4092
- fs37.mkdirSync(outputDir, { recursive: true });
4382
+ fs38.mkdirSync(outputDir, { recursive: true });
4093
4383
  const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
4094
4384
  const tableBuffers = /* @__PURE__ */ new Map();
4095
4385
  const tableFilePaths = /* @__PURE__ */ new Map();
@@ -4097,7 +4387,7 @@ function writeNodeCSVs(graph, outputDir) {
4097
4387
  const table = NODE_TABLE_MAP[node.kind];
4098
4388
  if (!tableBuffers.has(table)) {
4099
4389
  tableBuffers.set(table, [header]);
4100
- tableFilePaths.set(table, path38.join(outputDir, `${table}.csv`));
4390
+ tableFilePaths.set(table, path39.join(outputDir, `${table}.csv`));
4101
4391
  }
4102
4392
  tableBuffers.get(table).push(
4103
4393
  csvRow([
@@ -4117,12 +4407,12 @@ function writeNodeCSVs(graph, outputDir) {
4117
4407
  );
4118
4408
  }
4119
4409
  for (const [table, lines] of tableBuffers) {
4120
- fs37.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
4410
+ fs38.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
4121
4411
  }
4122
4412
  return tableFilePaths;
4123
4413
  }
4124
4414
  function writeEdgeCSV(graph, outputDir) {
4125
- fs37.mkdirSync(outputDir, { recursive: true });
4415
+ fs38.mkdirSync(outputDir, { recursive: true });
4126
4416
  const header = "from_id,to_id,kind,weight,label\n";
4127
4417
  const groups = /* @__PURE__ */ new Map();
4128
4418
  for (const edge of graph.allEdges()) {
@@ -4133,7 +4423,7 @@ function writeEdgeCSV(graph, outputDir) {
4133
4423
  const toTable = NODE_TABLE_MAP[targetNode.kind];
4134
4424
  const key = `${fromTable}->${toTable}`;
4135
4425
  if (!groups.has(key)) {
4136
- const filePath = path38.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
4426
+ const filePath = path39.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
4137
4427
  groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
4138
4428
  }
4139
4429
  groups.get(key).lines.push(
@@ -4148,7 +4438,7 @@ function writeEdgeCSV(graph, outputDir) {
4148
4438
  }
4149
4439
  const result = [];
4150
4440
  for (const group of groups.values()) {
4151
- fs37.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
4441
+ fs38.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
4152
4442
  result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
4153
4443
  }
4154
4444
  return result;
@@ -4191,7 +4481,7 @@ async function loadGraphToDB(graph, dbManager) {
4191
4481
  } catch {
4192
4482
  }
4193
4483
  }
4194
- const tmpDir = fs37.mkdtempSync(path38.join(os13.tmpdir(), "code-intel-csv-"));
4484
+ const tmpDir = fs38.mkdtempSync(path39.join(os13.tmpdir(), "code-intel-csv-"));
4195
4485
  try {
4196
4486
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
4197
4487
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -4210,8 +4500,8 @@ async function loadGraphToDB(graph, dbManager) {
4210
4500
  }
4211
4501
  let nodeCount = 0;
4212
4502
  for (const [table, csvPath] of nodeTableFiles) {
4213
- if (!fs37.existsSync(csvPath)) continue;
4214
- const stat = fs37.statSync(csvPath);
4503
+ if (!fs38.existsSync(csvPath)) continue;
4504
+ const stat = fs38.statSync(csvPath);
4215
4505
  if (stat.size < 50) continue;
4216
4506
  try {
4217
4507
  await dbManager.execute(
@@ -4224,8 +4514,8 @@ async function loadGraphToDB(graph, dbManager) {
4224
4514
  }
4225
4515
  let edgeCount = 0;
4226
4516
  for (const group of edgeGroups) {
4227
- if (!fs37.existsSync(group.filePath)) continue;
4228
- const stat = fs37.statSync(group.filePath);
4517
+ if (!fs38.existsSync(group.filePath)) continue;
4518
+ const stat = fs38.statSync(group.filePath);
4229
4519
  if (stat.size < 50) continue;
4230
4520
  try {
4231
4521
  await dbManager.execute(
@@ -4239,7 +4529,7 @@ async function loadGraphToDB(graph, dbManager) {
4239
4529
  return { nodeCount, edgeCount };
4240
4530
  } finally {
4241
4531
  try {
4242
- fs37.rmSync(tmpDir, { recursive: true, force: true });
4532
+ fs38.rmSync(tmpDir, { recursive: true, force: true });
4243
4533
  } catch {
4244
4534
  }
4245
4535
  }
@@ -4267,7 +4557,7 @@ async function loadEdgeGroupFallback(graph, fromTable, toTable, dbManager) {
4267
4557
  if (NODE_TABLE_MAP[targetNode.kind] !== toTable) continue;
4268
4558
  try {
4269
4559
  await dbManager.execute(
4270
- `MATCH (a:${fromTable} {id: '${escCypher(edge.source)}'}), (b:${toTable} {id: '${escCypher(edge.target)}'}) CREATE (a)-[:code_edges {kind: '${edge.kind}', weight: ${edge.weight ?? 1}, label: '${escCypher(edge.label ?? "")}'}]->(b)`
4560
+ `MATCH (a:${fromTable} {id: '${escCypher2(edge.source)}'}), (b:${toTable} {id: '${escCypher2(edge.target)}'}) CREATE (a)-[:code_edges {kind: '${edge.kind}', weight: ${edge.weight ?? 1}, label: '${escCypher2(edge.label ?? "")}'}]->(b)`
4271
4561
  );
4272
4562
  count++;
4273
4563
  } catch {
@@ -4279,7 +4569,7 @@ async function upsertNode(node, dbManager) {
4279
4569
  const table = NODE_TABLE_MAP[node.kind];
4280
4570
  const props = buildNodeProps(node);
4281
4571
  try {
4282
- await dbManager.execute(`MATCH (n:${table} {id: '${escCypher(node.id)}'}) DELETE n`);
4572
+ await dbManager.execute(`MATCH (n:${table} {id: '${escCypher2(node.id)}'}) DELETE n`);
4283
4573
  } catch {
4284
4574
  }
4285
4575
  try {
@@ -4298,7 +4588,7 @@ async function upsertNodes(nodes, dbManager) {
4298
4588
  return count;
4299
4589
  }
4300
4590
  async function removeNodesForFile(filePath, dbManager) {
4301
- const escaped = escCypher(filePath);
4591
+ const escaped = escCypher2(filePath);
4302
4592
  for (const table of ALL_NODE_TABLES) {
4303
4593
  try {
4304
4594
  await dbManager.execute(
@@ -4309,7 +4599,7 @@ async function removeNodesForFile(filePath, dbManager) {
4309
4599
  }
4310
4600
  }
4311
4601
  async function removeEdgesForFile(filePath, dbManager) {
4312
- const escaped = escCypher(filePath);
4602
+ const escaped = escCypher2(filePath);
4313
4603
  try {
4314
4604
  await dbManager.execute(
4315
4605
  `MATCH (a)-[e:code_edges]->(b) WHERE a.file_path = '${escaped}' OR b.file_path = '${escaped}' DELETE e`
@@ -4319,18 +4609,18 @@ async function removeEdgesForFile(filePath, dbManager) {
4319
4609
  }
4320
4610
  function buildNodeProps(node) {
4321
4611
  const parts = [
4322
- `id: '${escCypher(node.id)}'`,
4323
- `name: '${escCypher(node.name)}'`,
4324
- `file_path: '${escCypher(node.filePath)}'`
4612
+ `id: '${escCypher2(node.id)}'`,
4613
+ `name: '${escCypher2(node.name)}'`,
4614
+ `file_path: '${escCypher2(node.filePath)}'`
4325
4615
  ];
4326
4616
  if (node.startLine !== void 0) parts.push(`start_line: ${node.startLine}`);
4327
4617
  if (node.endLine !== void 0) parts.push(`end_line: ${node.endLine}`);
4328
4618
  if (node.exported !== void 0) parts.push(`exported: ${node.exported}`);
4329
- if (node.content) parts.push(`content: '${escCypher(node.content.slice(0, 500))}'`);
4330
- if (node.metadata) parts.push(`metadata: '${escCypher(JSON.stringify(node.metadata))}'`);
4619
+ if (node.content) parts.push(`content: '${escCypher2(node.content.slice(0, 500))}'`);
4620
+ if (node.metadata) parts.push(`metadata: '${escCypher2(JSON.stringify(node.metadata))}'`);
4331
4621
  return `{${parts.join(", ")}}`;
4332
4622
  }
4333
- function escCypher(s) {
4623
+ function escCypher2(s) {
4334
4624
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
4335
4625
  }
4336
4626
  var init_graph_loader = __esm({
@@ -4350,15 +4640,15 @@ __export(repo_registry_exports, {
4350
4640
  });
4351
4641
  function loadRegistry() {
4352
4642
  try {
4353
- const data = fs37.readFileSync(REPOS_FILE, "utf-8");
4643
+ const data = fs38.readFileSync(REPOS_FILE, "utf-8");
4354
4644
  return JSON.parse(data);
4355
4645
  } catch {
4356
4646
  return [];
4357
4647
  }
4358
4648
  }
4359
4649
  function saveRegistry(entries) {
4360
- fs37.mkdirSync(GLOBAL_DIR, { recursive: true });
4361
- fs37.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
4650
+ fs38.mkdirSync(GLOBAL_DIR, { recursive: true });
4651
+ fs38.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
4362
4652
  }
4363
4653
  function upsertRepo(entry) {
4364
4654
  const entries = loadRegistry();
@@ -4377,8 +4667,8 @@ function removeRepo(repoPath) {
4377
4667
  var GLOBAL_DIR, REPOS_FILE;
4378
4668
  var init_repo_registry = __esm({
4379
4669
  "src/storage/repo-registry.ts"() {
4380
- GLOBAL_DIR = path38.join(os13.homedir(), ".code-intel");
4381
- REPOS_FILE = path38.join(GLOBAL_DIR, "repos.json");
4670
+ GLOBAL_DIR = path39.join(os13.homedir(), ".code-intel");
4671
+ REPOS_FILE = path39.join(GLOBAL_DIR, "repos.json");
4382
4672
  }
4383
4673
  });
4384
4674
 
@@ -4391,23 +4681,23 @@ __export(metadata_exports, {
4391
4681
  saveMetadata: () => saveMetadata
4392
4682
  });
4393
4683
  function saveMetadata(repoDir, metadata) {
4394
- const metaDir = path38.join(repoDir, ".code-intel");
4395
- fs37.mkdirSync(metaDir, { recursive: true });
4396
- fs37.writeFileSync(path38.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
4684
+ const metaDir = path39.join(repoDir, ".code-intel");
4685
+ fs38.mkdirSync(metaDir, { recursive: true });
4686
+ fs38.writeFileSync(path39.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
4397
4687
  }
4398
4688
  function loadMetadata(repoDir) {
4399
4689
  try {
4400
- const data = fs37.readFileSync(path38.join(repoDir, ".code-intel", "meta.json"), "utf-8");
4690
+ const data = fs38.readFileSync(path39.join(repoDir, ".code-intel", "meta.json"), "utf-8");
4401
4691
  return JSON.parse(data);
4402
4692
  } catch {
4403
4693
  return null;
4404
4694
  }
4405
4695
  }
4406
4696
  function getDbPath(repoDir) {
4407
- return path38.join(repoDir, ".code-intel", "graph.db");
4697
+ return path39.join(repoDir, ".code-intel", "graph.db");
4408
4698
  }
4409
4699
  function getVectorDbPath(repoDir) {
4410
- return path38.join(repoDir, ".code-intel", "vector.db");
4700
+ return path39.join(repoDir, ".code-intel", "vector.db");
4411
4701
  }
4412
4702
  var init_metadata = __esm({
4413
4703
  "src/storage/metadata.ts"() {
@@ -4463,27 +4753,27 @@ __export(group_registry_exports, {
4463
4753
  saveSyncResult: () => saveSyncResult
4464
4754
  });
4465
4755
  function groupFile(name) {
4466
- return path38.join(GROUPS_DIR, `${name}.json`);
4756
+ return path39.join(GROUPS_DIR, `${name}.json`);
4467
4757
  }
4468
4758
  function loadGroup(name) {
4469
4759
  try {
4470
- return JSON.parse(fs37.readFileSync(groupFile(name), "utf-8"));
4760
+ return JSON.parse(fs38.readFileSync(groupFile(name), "utf-8"));
4471
4761
  } catch {
4472
4762
  return null;
4473
4763
  }
4474
4764
  }
4475
4765
  function saveGroup(group) {
4476
- fs37.mkdirSync(GROUPS_DIR, { recursive: true });
4477
- fs37.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
4766
+ fs38.mkdirSync(GROUPS_DIR, { recursive: true });
4767
+ fs38.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
4478
4768
  }
4479
4769
  function listGroups() {
4480
4770
  const groups = [];
4481
4771
  try {
4482
- for (const file of fs37.readdirSync(GROUPS_DIR)) {
4772
+ for (const file of fs38.readdirSync(GROUPS_DIR)) {
4483
4773
  if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
4484
4774
  try {
4485
4775
  const g = JSON.parse(
4486
- fs37.readFileSync(path38.join(GROUPS_DIR, file), "utf-8")
4776
+ fs38.readFileSync(path39.join(GROUPS_DIR, file), "utf-8")
4487
4777
  );
4488
4778
  groups.push(g);
4489
4779
  } catch {
@@ -4495,16 +4785,16 @@ function listGroups() {
4495
4785
  }
4496
4786
  function deleteGroup(name) {
4497
4787
  try {
4498
- fs37.unlinkSync(groupFile(name));
4788
+ fs38.unlinkSync(groupFile(name));
4499
4789
  } catch {
4500
4790
  }
4501
4791
  try {
4502
- fs37.unlinkSync(path38.join(GROUPS_DIR, `${name}.sync.json`));
4792
+ fs38.unlinkSync(path39.join(GROUPS_DIR, `${name}.sync.json`));
4503
4793
  } catch {
4504
4794
  }
4505
4795
  }
4506
4796
  function groupExists(name) {
4507
- return fs37.existsSync(groupFile(name));
4797
+ return fs38.existsSync(groupFile(name));
4508
4798
  }
4509
4799
  function addMember(groupName, member) {
4510
4800
  const group = loadGroup(groupName);
@@ -4530,16 +4820,16 @@ function removeMember(groupName, groupPath) {
4530
4820
  return group;
4531
4821
  }
4532
4822
  function saveSyncResult(result) {
4533
- fs37.mkdirSync(GROUPS_DIR, { recursive: true });
4534
- fs37.writeFileSync(
4535
- path38.join(GROUPS_DIR, `${result.groupName}.sync.json`),
4823
+ fs38.mkdirSync(GROUPS_DIR, { recursive: true });
4824
+ fs38.writeFileSync(
4825
+ path39.join(GROUPS_DIR, `${result.groupName}.sync.json`),
4536
4826
  JSON.stringify(result, null, 2) + "\n"
4537
4827
  );
4538
4828
  }
4539
4829
  function loadSyncResult(groupName) {
4540
4830
  try {
4541
4831
  return JSON.parse(
4542
- fs37.readFileSync(path38.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
4832
+ fs38.readFileSync(path39.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
4543
4833
  );
4544
4834
  } catch {
4545
4835
  return null;
@@ -4548,7 +4838,7 @@ function loadSyncResult(groupName) {
4548
4838
  var GROUPS_DIR;
4549
4839
  var init_group_registry = __esm({
4550
4840
  "src/multi-repo/group-registry.ts"() {
4551
- GROUPS_DIR = path38.join(os13.homedir(), ".code-intel", "groups");
4841
+ GROUPS_DIR = path39.join(os13.homedir(), ".code-intel", "groups");
4552
4842
  }
4553
4843
  });
4554
4844
 
@@ -4574,7 +4864,7 @@ function parseRow(row, kind) {
4574
4864
  }
4575
4865
  async function loadGraphFromDB(graph, db) {
4576
4866
  for (const table of ALL_NODE_TABLES) {
4577
- const kind = TABLE_TO_KIND[table];
4867
+ const kind = TABLE_TO_KIND2[table];
4578
4868
  if (!kind) continue;
4579
4869
  let rows = [];
4580
4870
  try {
@@ -4618,11 +4908,11 @@ async function loadGraphFromDB(graph, db) {
4618
4908
  } catch {
4619
4909
  }
4620
4910
  }
4621
- var TABLE_TO_KIND;
4911
+ var TABLE_TO_KIND2;
4622
4912
  var init_graph_from_db = __esm({
4623
4913
  "src/multi-repo/graph-from-db.ts"() {
4624
4914
  init_schema();
4625
- TABLE_TO_KIND = Object.fromEntries(
4915
+ TABLE_TO_KIND2 = Object.fromEntries(
4626
4916
  Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
4627
4917
  );
4628
4918
  }
@@ -4633,12 +4923,12 @@ function scanForFiles(root, matcher, maxDepth = 2) {
4633
4923
  if (depth > maxDepth) return;
4634
4924
  let entries;
4635
4925
  try {
4636
- entries = fs37.readdirSync(dir, { withFileTypes: true });
4926
+ entries = fs38.readdirSync(dir, { withFileTypes: true });
4637
4927
  } catch {
4638
4928
  return;
4639
4929
  }
4640
4930
  for (const entry of entries) {
4641
- const full = path38.join(dir, entry.name);
4931
+ const full = path39.join(dir, entry.name);
4642
4932
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
4643
4933
  walk2(full, depth + 1);
4644
4934
  } else if (entry.isFile() && matcher(entry.name)) {
@@ -4654,8 +4944,8 @@ var init_file_scanner = __esm({
4654
4944
  }
4655
4945
  });
4656
4946
  function tryParseFile(filePath) {
4657
- const ext = path38.extname(filePath).toLowerCase();
4658
- const content = fs37.readFileSync(filePath, "utf-8");
4947
+ const ext = path39.extname(filePath).toLowerCase();
4948
+ const content = fs38.readFileSync(filePath, "utf-8");
4659
4949
  if (ext === ".json") {
4660
4950
  try {
4661
4951
  return JSON.parse(content);
@@ -4724,7 +5014,7 @@ async function parseGraphQLContracts(repoRoot) {
4724
5014
  const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
4725
5015
  const contracts = [];
4726
5016
  for (const filePath of files) {
4727
- const content = fs37.readFileSync(filePath, "utf-8");
5017
+ const content = fs38.readFileSync(filePath, "utf-8");
4728
5018
  const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
4729
5019
  let match;
4730
5020
  while ((match = typeRegex.exec(content)) !== null) {
@@ -4760,7 +5050,7 @@ async function parseProtoContracts(repoRoot) {
4760
5050
  const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
4761
5051
  const contracts = [];
4762
5052
  for (const filePath of files) {
4763
- const content = fs37.readFileSync(filePath, "utf-8");
5053
+ const content = fs38.readFileSync(filePath, "utf-8");
4764
5054
  const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
4765
5055
  let serviceMatch;
4766
5056
  while ((serviceMatch = serviceRegex.exec(content)) !== null) {
@@ -4978,13 +5268,13 @@ async function syncGroup(group) {
4978
5268
  logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
4979
5269
  continue;
4980
5270
  }
4981
- const dbPath = path38.join(regEntry.path, ".code-intel", "graph.db");
4982
- if (!fs37.existsSync(dbPath)) {
5271
+ const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
5272
+ if (!fs38.existsSync(dbPath)) {
4983
5273
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
4984
5274
  continue;
4985
5275
  }
4986
5276
  const graph = createKnowledgeGraph();
4987
- const db = new DbManager(dbPath);
5277
+ const db = new DbManager(dbPath, true);
4988
5278
  try {
4989
5279
  await db.init();
4990
5280
  await loadGraphFromDB(graph, db);
@@ -5160,10 +5450,10 @@ var init_codes = __esm({
5160
5450
  }
5161
5451
  });
5162
5452
  function secureMkdir(dir) {
5163
- fs37.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
5453
+ fs38.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
5164
5454
  if (process.platform !== "win32") {
5165
5455
  try {
5166
- fs37.chmodSync(dir, SECURE_DIR_MODE);
5456
+ fs38.chmodSync(dir, SECURE_DIR_MODE);
5167
5457
  } catch {
5168
5458
  }
5169
5459
  }
@@ -5171,22 +5461,22 @@ function secureMkdir(dir) {
5171
5461
  function secureChmodFile(file) {
5172
5462
  if (process.platform === "win32") return;
5173
5463
  try {
5174
- fs37.chmodSync(file, SECURE_FILE_MODE);
5464
+ fs38.chmodSync(file, SECURE_FILE_MODE);
5175
5465
  } catch {
5176
5466
  }
5177
5467
  }
5178
5468
  function secureWriteFile(file, data) {
5179
- secureMkdir(path38.dirname(file));
5180
- fs37.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
5469
+ secureMkdir(path39.dirname(file));
5470
+ fs38.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
5181
5471
  secureChmodFile(file);
5182
5472
  }
5183
5473
  function tightenDbFiles(dir) {
5184
5474
  if (process.platform === "win32") return;
5185
- if (!fs37.existsSync(dir)) return;
5186
- for (const name of fs37.readdirSync(dir)) {
5475
+ if (!fs38.existsSync(dir)) return;
5476
+ for (const name of fs38.readdirSync(dir)) {
5187
5477
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
5188
5478
  try {
5189
- fs37.chmodSync(path38.join(dir, name), SECURE_FILE_MODE);
5479
+ fs38.chmodSync(path39.join(dir, name), SECURE_FILE_MODE);
5190
5480
  } catch {
5191
5481
  }
5192
5482
  }
@@ -5200,7 +5490,7 @@ var init_fs_secure = __esm({
5200
5490
  }
5201
5491
  });
5202
5492
  function getUsersDBPath() {
5203
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path38.join(os13.homedir(), ".code-intel", "users.db");
5493
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path39.join(os13.homedir(), ".code-intel", "users.db");
5204
5494
  }
5205
5495
  function getOrCreateUsersDB() {
5206
5496
  if (!_usersDB) {
@@ -5216,9 +5506,9 @@ var init_users_db = __esm({
5216
5506
  UsersDB = class {
5217
5507
  db;
5218
5508
  constructor(dbPath) {
5219
- const dir = path38.dirname(dbPath);
5509
+ const dir = path39.dirname(dbPath);
5220
5510
  secureMkdir(dir);
5221
- this.db = new Database(dbPath);
5511
+ this.db = new Database2(dbPath);
5222
5512
  this.db.pragma("journal_mode = WAL");
5223
5513
  this.db.pragma("foreign_keys = ON");
5224
5514
  this.createTables();
@@ -5493,7 +5783,7 @@ function getScryptN() {
5493
5783
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
5494
5784
  }
5495
5785
  function getSecretsPath() {
5496
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path38.join(os13.homedir(), ".code-intel", ".secrets");
5786
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path39.join(os13.homedir(), ".code-intel", ".secrets");
5497
5787
  }
5498
5788
  function getMasterPassword() {
5499
5789
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
@@ -5535,12 +5825,12 @@ function decryptSecrets(encrypted) {
5535
5825
  return JSON.parse(plaintext.toString("utf8"));
5536
5826
  }
5537
5827
  function loadSecrets(secretsPath = getSecretsPath()) {
5538
- if (!fs37.existsSync(secretsPath)) return {};
5539
- const blob = fs37.readFileSync(secretsPath);
5828
+ if (!fs38.existsSync(secretsPath)) return {};
5829
+ const blob = fs38.readFileSync(secretsPath);
5540
5830
  return decryptSecrets(blob);
5541
5831
  }
5542
5832
  function saveSecrets(blob, secretsPath = getSecretsPath()) {
5543
- secureMkdir(path38.dirname(secretsPath));
5833
+ secureMkdir(path39.dirname(secretsPath));
5544
5834
  const encrypted = encryptSecrets(blob);
5545
5835
  secureWriteFile(secretsPath, encrypted);
5546
5836
  secureChmodFile(secretsPath);
@@ -6034,7 +6324,7 @@ __export(gql_parser_exports, {
6034
6324
  function isGQLParseError(v) {
6035
6325
  return v.type === "GQLParseError";
6036
6326
  }
6037
- function tokenize(input) {
6327
+ function tokenize2(input) {
6038
6328
  const tokens = [];
6039
6329
  let i = 0;
6040
6330
  const len = input.length;
@@ -6129,7 +6419,7 @@ function tokenize(input) {
6129
6419
  return tokens;
6130
6420
  }
6131
6421
  function parseGQL(input) {
6132
- const tokens = tokenize(input.trim());
6422
+ const tokens = tokenize2(input.trim());
6133
6423
  if (!Array.isArray(tokens)) return tokens;
6134
6424
  const parser = new Parser2(tokens);
6135
6425
  return parser.parse();
@@ -7433,7 +7723,7 @@ function isTestFile(filePath) {
7433
7723
  if (filePath.includes(".test.") || filePath.includes(".spec.")) return true;
7434
7724
  if (filePath.includes("_test.") || filePath.endsWith("_test.go")) return true;
7435
7725
  if (filePath.includes("__tests__")) return true;
7436
- const base = path38.basename(filePath);
7726
+ const base = path39.basename(filePath);
7437
7727
  if (base.startsWith("Test") && filePath.endsWith(".java")) return true;
7438
7728
  return false;
7439
7729
  }
@@ -7478,7 +7768,7 @@ function computeCoverage(graph, scope) {
7478
7768
  }
7479
7769
  const baseNameToTestFiles = /* @__PURE__ */ new Map();
7480
7770
  for (const testPath of testFilePaths) {
7481
- const base = path38.basename(testPath);
7771
+ const base = path39.basename(testPath);
7482
7772
  const stripped = base.replace(/\.test\.[^.]+$/, "").replace(/\.spec\.[^.]+$/, "").replace(/_test\.[^.]+$/, "").replace(/_test$/, "");
7483
7773
  const existing = baseNameToTestFiles.get(stripped) ?? [];
7484
7774
  existing.push(testPath);
@@ -7511,7 +7801,7 @@ function computeCoverage(graph, scope) {
7511
7801
  }
7512
7802
  }
7513
7803
  }
7514
- const nodeBase = path38.basename(node.filePath).replace(/\.[^.]+$/, "");
7804
+ const nodeBase = path39.basename(node.filePath).replace(/\.[^.]+$/, "");
7515
7805
  const matchingTestFiles = baseNameToTestFiles.get(nodeBase) ?? [];
7516
7806
  for (const tf of matchingTestFiles) {
7517
7807
  if (!testFiles.includes(tf)) testFiles.push(tf);
@@ -7581,7 +7871,7 @@ var init_secret_scanner = __esm({
7581
7871
  const ignorePatterns = [...options?.ignorePatterns ?? []];
7582
7872
  if (options?.workspaceRoot) {
7583
7873
  try {
7584
- const raw = fs37.readFileSync(path38.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
7874
+ const raw = fs38.readFileSync(path39.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
7585
7875
  for (const line of raw.split("\n")) {
7586
7876
  const trimmed = line.trim();
7587
7877
  if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
@@ -7839,22 +8129,22 @@ __export(init_wizard_exports, {
7839
8129
  wipeConfig: () => wipeConfig
7840
8130
  });
7841
8131
  function configExists() {
7842
- return fs37.existsSync(CONFIG_PATH);
8132
+ return fs38.existsSync(CONFIG_PATH);
7843
8133
  }
7844
8134
  function loadConfig() {
7845
8135
  if (!configExists()) return null;
7846
8136
  try {
7847
- return JSON.parse(fs37.readFileSync(CONFIG_PATH, "utf-8"));
8137
+ return JSON.parse(fs38.readFileSync(CONFIG_PATH, "utf-8"));
7848
8138
  } catch {
7849
8139
  return null;
7850
8140
  }
7851
8141
  }
7852
8142
  function saveConfig(cfg) {
7853
- fs37.mkdirSync(GLOBAL_DIR2, { recursive: true });
7854
- fs37.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
8143
+ fs38.mkdirSync(GLOBAL_DIR2, { recursive: true });
8144
+ fs38.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
7855
8145
  }
7856
8146
  function wipeConfig() {
7857
- if (fs37.existsSync(CONFIG_PATH)) fs37.unlinkSync(CONFIG_PATH);
8147
+ if (fs38.existsSync(CONFIG_PATH)) fs38.unlinkSync(CONFIG_PATH);
7858
8148
  }
7859
8149
  function commandExists(bin) {
7860
8150
  try {
@@ -7931,11 +8221,11 @@ async function runInitWizard(opts = {}) {
7931
8221
  "code-intel": { type: "stdio", command: "npx", args: ["code-intel", "mcp", "."] }
7932
8222
  }
7933
8223
  };
7934
- const mcpFile = path38.resolve(editor.mcpConfigKey);
8224
+ const mcpFile = path39.resolve(editor.mcpConfigKey);
7935
8225
  try {
7936
8226
  let existing = {};
7937
- if (fs37.existsSync(mcpFile)) {
7938
- existing = JSON.parse(fs37.readFileSync(mcpFile, "utf-8"));
8227
+ if (fs38.existsSync(mcpFile)) {
8228
+ existing = JSON.parse(fs38.readFileSync(mcpFile, "utf-8"));
7939
8229
  }
7940
8230
  const merged = {
7941
8231
  ...existing,
@@ -7944,8 +8234,8 @@ async function runInitWizard(opts = {}) {
7944
8234
  ...mcpConfig.servers
7945
8235
  }
7946
8236
  };
7947
- fs37.mkdirSync(path38.dirname(mcpFile), { recursive: true });
7948
- fs37.writeFileSync(mcpFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8237
+ fs38.mkdirSync(path39.dirname(mcpFile), { recursive: true });
8238
+ fs38.writeFileSync(mcpFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
7949
8239
  console.log(` \u2705 MCP registered for ${name} \u2192 ${editor.mcpConfigKey}`);
7950
8240
  } catch {
7951
8241
  console.log(` \u26A0 Could not write MCP config for ${name} \u2014 do it manually.`);
@@ -8041,8 +8331,8 @@ function printNextSteps() {
8041
8331
  var GLOBAL_DIR2, CONFIG_PATH, DEFAULT_CONFIG, EDITORS;
8042
8332
  var init_init_wizard = __esm({
8043
8333
  "src/cli/init-wizard.ts"() {
8044
- GLOBAL_DIR2 = path38.join(os13.homedir(), ".code-intel");
8045
- CONFIG_PATH = path38.join(GLOBAL_DIR2, "config.json");
8334
+ GLOBAL_DIR2 = path39.join(os13.homedir(), ".code-intel");
8335
+ CONFIG_PATH = path39.join(GLOBAL_DIR2, "config.json");
8046
8336
  DEFAULT_CONFIG = {
8047
8337
  $schema: "https://code-intel.dev/config-schema.json",
8048
8338
  llm: {
@@ -8082,9 +8372,9 @@ var init_init_wizard = __esm({
8082
8372
  binaries: ["code"],
8083
8373
  configFile: (home) => {
8084
8374
  const platform = process.platform;
8085
- if (platform === "darwin") return path38.join(home, "Library", "Application Support", "Code", "User", "settings.json");
8086
- if (platform === "win32") return path38.join(home, "AppData", "Roaming", "Code", "User", "settings.json");
8087
- return path38.join(home, ".config", "Code", "User", "settings.json");
8375
+ if (platform === "darwin") return path39.join(home, "Library", "Application Support", "Code", "User", "settings.json");
8376
+ if (platform === "win32") return path39.join(home, "AppData", "Roaming", "Code", "User", "settings.json");
8377
+ return path39.join(home, ".config", "Code", "User", "settings.json");
8088
8378
  },
8089
8379
  mcpConfigKey: ".vscode/mcp.json"
8090
8380
  },
@@ -8093,9 +8383,9 @@ var init_init_wizard = __esm({
8093
8383
  binaries: ["cursor"],
8094
8384
  configFile: (home) => {
8095
8385
  const platform = process.platform;
8096
- if (platform === "darwin") return path38.join(home, "Library", "Application Support", "Cursor", "User", "settings.json");
8097
- if (platform === "win32") return path38.join(home, "AppData", "Roaming", "Cursor", "User", "settings.json");
8098
- return path38.join(home, ".config", "Cursor", "User", "settings.json");
8386
+ if (platform === "darwin") return path39.join(home, "Library", "Application Support", "Cursor", "User", "settings.json");
8387
+ if (platform === "win32") return path39.join(home, "AppData", "Roaming", "Cursor", "User", "settings.json");
8388
+ return path39.join(home, ".config", "Cursor", "User", "settings.json");
8099
8389
  },
8100
8390
  mcpConfigKey: ".cursor/mcp.json"
8101
8391
  },
@@ -8103,15 +8393,15 @@ var init_init_wizard = __esm({
8103
8393
  name: "Windsurf",
8104
8394
  binaries: ["windsurf"],
8105
8395
  configFile: (home) => {
8106
- if (process.platform === "darwin") return path38.join(home, "Library", "Application Support", "Windsurf", "User", "settings.json");
8107
- return path38.join(home, ".config", "Windsurf", "User", "settings.json");
8396
+ if (process.platform === "darwin") return path39.join(home, "Library", "Application Support", "Windsurf", "User", "settings.json");
8397
+ return path39.join(home, ".config", "Windsurf", "User", "settings.json");
8108
8398
  },
8109
8399
  mcpConfigKey: ".windsurf/mcp.json"
8110
8400
  },
8111
8401
  {
8112
8402
  name: "Zed",
8113
8403
  binaries: ["zed"],
8114
- configFile: (home) => path38.join(home, ".config", "zed", "settings.json"),
8404
+ configFile: (home) => path39.join(home, ".config", "zed", "settings.json"),
8115
8405
  mcpConfigKey: ".zed/mcp.json"
8116
8406
  }
8117
8407
  ];
@@ -8378,6 +8668,263 @@ var init_config_manager = __esm({
8378
8668
  }
8379
8669
  });
8380
8670
 
8671
+ // src/graph/intern-table.ts
8672
+ function internNode(node, table = globalInternTable) {
8673
+ node.filePath = table.get(node.filePath);
8674
+ node.kind = table.get(node.kind);
8675
+ node.name = table.get(node.name);
8676
+ return node;
8677
+ }
8678
+ function internEdge(edge, table = globalInternTable) {
8679
+ edge.kind = table.get(edge.kind);
8680
+ edge.source = table.get(edge.source);
8681
+ edge.target = table.get(edge.target);
8682
+ return edge;
8683
+ }
8684
+ var InternTable, globalInternTable;
8685
+ var init_intern_table = __esm({
8686
+ "src/graph/intern-table.ts"() {
8687
+ InternTable = class {
8688
+ table = /* @__PURE__ */ new Map();
8689
+ /**
8690
+ * Return the canonical (interned) copy of `s`.
8691
+ * First call stores it; subsequent calls return the same reference.
8692
+ */
8693
+ get(s) {
8694
+ let interned = this.table.get(s);
8695
+ if (interned === void 0) {
8696
+ this.table.set(s, s);
8697
+ interned = s;
8698
+ }
8699
+ return interned;
8700
+ }
8701
+ /** Number of unique strings stored. */
8702
+ get size() {
8703
+ return this.table.size;
8704
+ }
8705
+ clear() {
8706
+ this.table.clear();
8707
+ }
8708
+ };
8709
+ globalInternTable = new InternTable();
8710
+ }
8711
+ });
8712
+
8713
+ // src/graph/compact-knowledge-graph.ts
8714
+ var compact_knowledge_graph_exports = {};
8715
+ __export(compact_knowledge_graph_exports, {
8716
+ CompactKnowledgeGraph: () => CompactKnowledgeGraph,
8717
+ createCompactKnowledgeGraph: () => createCompactKnowledgeGraph
8718
+ });
8719
+ function createCompactKnowledgeGraph(maxMemoryMB = 0) {
8720
+ return new CompactKnowledgeGraph(maxMemoryMB);
8721
+ }
8722
+ var CompactKnowledgeGraph;
8723
+ var init_compact_knowledge_graph = __esm({
8724
+ "src/graph/compact-knowledge-graph.ts"() {
8725
+ init_intern_table();
8726
+ init_logger();
8727
+ CompactKnowledgeGraph = class {
8728
+ // ── Storage ────────────────────────────────────────────────────────────────
8729
+ nodes = /* @__PURE__ */ new Map();
8730
+ edges = /* @__PURE__ */ new Map();
8731
+ // Typed adjacency: Int32 edge-index lists per node
8732
+ // We store the edge index (into edgeArray) rather than the edge ID string.
8733
+ edgesFromNode = /* @__PURE__ */ new Map();
8734
+ edgesToNode = /* @__PURE__ */ new Map();
8735
+ edgesByKind = /* @__PURE__ */ new Map();
8736
+ // Flat ordered edge array for O(1) index→edge lookup
8737
+ edgeArray = [];
8738
+ // Float32Array for weights (parallel to edgeArray)
8739
+ weightArray = new Float32Array(1024);
8740
+ // Symbol intern table
8741
+ intern = new InternTable();
8742
+ // --max-memory spill threshold (MB). 0 = disabled.
8743
+ maxMemoryMB;
8744
+ spillCount = 0;
8745
+ constructor(maxMemoryMB = 0) {
8746
+ this.maxMemoryMB = maxMemoryMB;
8747
+ }
8748
+ // ── KnowledgeGraph interface ───────────────────────────────────────────────
8749
+ addNode(node) {
8750
+ internNode(node, this.intern);
8751
+ this.nodes.set(node.id, node);
8752
+ if (this.maxMemoryMB > 0) this._maybeSpill();
8753
+ }
8754
+ addEdge(edge) {
8755
+ internEdge(edge, this.intern);
8756
+ this.edges.set(edge.id, edge);
8757
+ const edgeIdx = this.edgeArray.length;
8758
+ this.edgeArray.push(edge);
8759
+ if (edgeIdx >= this.weightArray.length) {
8760
+ const next = new Float32Array(this.weightArray.length * 2);
8761
+ next.set(this.weightArray);
8762
+ this.weightArray = next;
8763
+ }
8764
+ this.weightArray[edgeIdx] = edge.weight ?? 1;
8765
+ this._appendToList(this.edgesFromNode, edge.source, edgeIdx);
8766
+ this._appendToList(this.edgesToNode, edge.target, edgeIdx);
8767
+ let kindList = this.edgesByKind.get(edge.kind);
8768
+ if (!kindList) {
8769
+ kindList = [];
8770
+ this.edgesByKind.set(edge.kind, kindList);
8771
+ }
8772
+ kindList.push(edgeIdx);
8773
+ }
8774
+ getNode(id) {
8775
+ return this.nodes.get(id);
8776
+ }
8777
+ getEdge(id) {
8778
+ return this.edges.get(id);
8779
+ }
8780
+ *findEdgesByKind(kind) {
8781
+ const idxList = this.edgesByKind.get(kind);
8782
+ if (!idxList) return;
8783
+ for (const idx of idxList) {
8784
+ const edge = this.edgeArray[idx];
8785
+ if (edge) yield edge;
8786
+ }
8787
+ }
8788
+ *findEdgesFrom(sourceId) {
8789
+ const idxList = this.edgesFromNode.get(sourceId);
8790
+ if (!idxList) return;
8791
+ for (const idx of idxList) {
8792
+ const edge = this.edgeArray[idx];
8793
+ if (edge) yield edge;
8794
+ }
8795
+ }
8796
+ *findEdgesTo(targetId) {
8797
+ const idxList = this.edgesToNode.get(targetId);
8798
+ if (!idxList) return;
8799
+ for (const idx of idxList) {
8800
+ const edge = this.edgeArray[idx];
8801
+ if (edge) yield edge;
8802
+ }
8803
+ }
8804
+ removeNodeCascade(id) {
8805
+ const fromIdxs = this.edgesFromNode.get(id);
8806
+ if (fromIdxs) {
8807
+ for (const idx of fromIdxs) {
8808
+ const edge = this.edgeArray[idx];
8809
+ if (edge) {
8810
+ this.edges.delete(edge.id);
8811
+ this.edgeArray[idx] = void 0;
8812
+ this._removeFromList(this.edgesToNode, edge.target, idx);
8813
+ this._removeFromKindList(edge.kind, idx);
8814
+ }
8815
+ }
8816
+ }
8817
+ const toIdxs = this.edgesToNode.get(id);
8818
+ if (toIdxs) {
8819
+ for (const idx of toIdxs) {
8820
+ const edge = this.edgeArray[idx];
8821
+ if (edge) {
8822
+ this.edges.delete(edge.id);
8823
+ this.edgeArray[idx] = void 0;
8824
+ this._removeFromList(this.edgesFromNode, edge.source, idx);
8825
+ this._removeFromKindList(edge.kind, idx);
8826
+ }
8827
+ }
8828
+ }
8829
+ this.edgesFromNode.delete(id);
8830
+ this.edgesToNode.delete(id);
8831
+ this.nodes.delete(id);
8832
+ }
8833
+ removeEdge(id) {
8834
+ const edge = this.edges.get(id);
8835
+ if (!edge) return;
8836
+ this.edges.delete(id);
8837
+ const idx = this.edgeArray.indexOf(edge);
8838
+ if (idx !== -1) {
8839
+ this.edgeArray[idx] = void 0;
8840
+ this._removeFromList(this.edgesFromNode, edge.source, idx);
8841
+ this._removeFromList(this.edgesToNode, edge.target, idx);
8842
+ this._removeFromKindList(edge.kind, idx);
8843
+ }
8844
+ }
8845
+ *allNodes() {
8846
+ yield* this.nodes.values();
8847
+ }
8848
+ *allEdges() {
8849
+ for (const edge of this.edgeArray) {
8850
+ if (edge) yield edge;
8851
+ }
8852
+ }
8853
+ get size() {
8854
+ return { nodes: this.nodes.size, edges: this.edges.size };
8855
+ }
8856
+ clear() {
8857
+ this.nodes.clear();
8858
+ this.edges.clear();
8859
+ this.edgesFromNode.clear();
8860
+ this.edgesToNode.clear();
8861
+ this.edgesByKind.clear();
8862
+ this.edgeArray.length = 0;
8863
+ this.weightArray = new Float32Array(1024);
8864
+ this.intern.clear();
8865
+ this.spillCount = 0;
8866
+ }
8867
+ // ── Diagnostics ────────────────────────────────────────────────────────────
8868
+ /** Number of unique interned strings (useful for memory audit). */
8869
+ get internedStringCount() {
8870
+ return this.intern.size;
8871
+ }
8872
+ /** Number of nodes that had content spilled due to memory pressure. */
8873
+ get spilledNodeCount() {
8874
+ return this.spillCount;
8875
+ }
8876
+ // ── Private helpers ────────────────────────────────────────────────────────
8877
+ _appendToList(map, key, idx) {
8878
+ const existing = map.get(key);
8879
+ if (!existing) {
8880
+ map.set(key, [idx]);
8881
+ } else {
8882
+ existing.push(idx);
8883
+ }
8884
+ }
8885
+ _removeFromList(map, key, idx) {
8886
+ const list = map.get(key);
8887
+ if (!list) return;
8888
+ const arr = list;
8889
+ const pos = arr.indexOf(idx);
8890
+ if (pos !== -1) arr.splice(pos, 1);
8891
+ }
8892
+ _removeFromKindList(kind, idx) {
8893
+ const list = this.edgesByKind.get(kind);
8894
+ if (!list) return;
8895
+ const pos = list.indexOf(idx);
8896
+ if (pos !== -1) list.splice(pos, 1);
8897
+ }
8898
+ /**
8899
+ * Memory-pressure spill: when RSS > maxMemoryMB, clear the `content` field
8900
+ * of nodes that are less likely to be needed (no outgoing call edges = leaf nodes).
8901
+ * This frees the largest string blobs while keeping graph topology intact.
8902
+ */
8903
+ _maybeSpill() {
8904
+ if (this.maxMemoryMB <= 0) return;
8905
+ const rssMB = process.memoryUsage().rss / (1024 * 1024);
8906
+ if (rssMB <= this.maxMemoryMB) return;
8907
+ let spilled = 0;
8908
+ for (const node of this.nodes.values()) {
8909
+ if (node.content === void 0) continue;
8910
+ const outgoing = this.edgesFromNode.get(node.id);
8911
+ if (!outgoing || outgoing.length === 0) {
8912
+ node.content = void 0;
8913
+ spilled++;
8914
+ this.spillCount++;
8915
+ if (spilled >= 100) break;
8916
+ }
8917
+ }
8918
+ if (spilled > 0) {
8919
+ logger_default.warn(
8920
+ ` [compact-graph] Memory pressure (${rssMB.toFixed(0)} MB > ${this.maxMemoryMB} MB): spilled content for ${spilled} nodes`
8921
+ );
8922
+ }
8923
+ }
8924
+ };
8925
+ }
8926
+ });
8927
+
8381
8928
  // src/pipeline/file-watcher.ts
8382
8929
  var file_watcher_exports = {};
8383
8930
  __export(file_watcher_exports, {
@@ -8456,10 +9003,10 @@ var init_file_watcher = __esm({
8456
9003
  }
8457
9004
  // ── private ─────────────────────────────────────────────────────────────────
8458
9005
  readCodeIntelIgnore() {
8459
- const ignoreFile = path38.join(this.workspaceRoot, ".codeintelignore");
9006
+ const ignoreFile = path39.join(this.workspaceRoot, ".codeintelignore");
8460
9007
  try {
8461
- if (!fs37.existsSync(ignoreFile)) return [];
8462
- return fs37.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
9008
+ if (!fs38.existsSync(ignoreFile)) return [];
9009
+ return fs38.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
8463
9010
  } catch {
8464
9011
  return [];
8465
9012
  }
@@ -8482,6 +9029,7 @@ var init_incremental_indexer = __esm({
8482
9029
  init_parse_phase();
8483
9030
  init_resolve_phase();
8484
9031
  init_logger();
9032
+ init_bm25_index();
8485
9033
  IncrementalIndexer = class {
8486
9034
  graph;
8487
9035
  workspaceRoot;
@@ -8511,7 +9059,7 @@ var init_incremental_indexer = __esm({
8511
9059
  }
8512
9060
  const nodeIdsToRemove = /* @__PURE__ */ new Set();
8513
9061
  for (const absPath of changedFiles) {
8514
- const relPath2 = path38.relative(workspaceRoot, absPath);
9062
+ const relPath2 = path39.relative(workspaceRoot, absPath);
8515
9063
  for (const id of nodesByFilePath.get(relPath2) ?? []) nodeIdsToRemove.add(id);
8516
9064
  for (const id of nodesByFilePath.get(absPath) ?? []) nodeIdsToRemove.add(id);
8517
9065
  }
@@ -8519,12 +9067,12 @@ var init_incremental_indexer = __esm({
8519
9067
  graph.removeNodeCascade(id);
8520
9068
  nodesRemoved++;
8521
9069
  }
8522
- if (fs37.existsSync(dbPath)) {
9070
+ if (fs38.existsSync(dbPath)) {
8523
9071
  try {
8524
9072
  const db = new DbManager(dbPath);
8525
9073
  await db.init();
8526
9074
  for (const absPath of changedFiles) {
8527
- const relPath2 = path38.relative(workspaceRoot, absPath);
9075
+ const relPath2 = path39.relative(workspaceRoot, absPath);
8528
9076
  await removeNodesForFile(relPath2, db);
8529
9077
  }
8530
9078
  db.close();
@@ -8534,7 +9082,7 @@ var init_incremental_indexer = __esm({
8534
9082
  }
8535
9083
  const existingFiles = changedFiles.filter((f) => {
8536
9084
  try {
8537
- return fs37.statSync(f).isFile();
9085
+ return fs38.statSync(f).isFile();
8538
9086
  } catch {
8539
9087
  return false;
8540
9088
  }
@@ -8556,16 +9104,26 @@ var init_incremental_indexer = __esm({
8556
9104
  await runPipeline([noopScan, parsePhase, resolvePhase], context2);
8557
9105
  }
8558
9106
  const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
8559
- if (fs37.existsSync(dbPath) && existingFiles.length > 0) {
9107
+ if (fs38.existsSync(dbPath) && existingFiles.length > 0) {
8560
9108
  try {
8561
9109
  const db = new DbManager(dbPath);
8562
9110
  await db.init();
8563
- const changedRelPaths = new Set(changedFiles.map((f) => path38.relative(workspaceRoot, f)));
9111
+ const changedRelPaths = new Set(changedFiles.map((f) => path39.relative(workspaceRoot, f)));
8564
9112
  const nodesToUpsert = [...graph.allNodes()].filter(
8565
- (n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path38.relative(workspaceRoot, n.filePath))
9113
+ (n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path39.relative(workspaceRoot, n.filePath))
8566
9114
  );
8567
9115
  await upsertNodes(nodesToUpsert, db);
8568
9116
  db.close();
9117
+ try {
9118
+ const bm25DbPath = getBm25DbPath(workspaceRoot);
9119
+ if (fs38.existsSync(bm25DbPath)) {
9120
+ const bm25 = new Bm25Index(bm25DbPath);
9121
+ bm25.updateNodes(nodesToUpsert);
9122
+ logger_default.info(`[incremental] BM25 index updated: ${nodesToUpsert.length} nodes`);
9123
+ }
9124
+ } catch (bm25Err) {
9125
+ logger_default.warn(`[incremental] BM25 update failed: ${bm25Err instanceof Error ? bm25Err.message : bm25Err}`);
9126
+ }
8569
9127
  } catch (err) {
8570
9128
  logger_default.warn(`[incremental] DB upsert failed: ${err instanceof Error ? err.message : err}`);
8571
9129
  }
@@ -8593,35 +9151,35 @@ __export(saved_queries_exports, {
8593
9151
  saveQuery: () => saveQuery
8594
9152
  });
8595
9153
  function getQueriesDir(workspaceRoot) {
8596
- return path38.join(workspaceRoot, ".code-intel", "queries");
9154
+ return path39.join(workspaceRoot, ".code-intel", "queries");
8597
9155
  }
8598
9156
  function ensureQueriesDir(workspaceRoot) {
8599
9157
  const dir = getQueriesDir(workspaceRoot);
8600
- if (!fs37.existsSync(dir)) {
8601
- fs37.mkdirSync(dir, { recursive: true });
9158
+ if (!fs38.existsSync(dir)) {
9159
+ fs38.mkdirSync(dir, { recursive: true });
8602
9160
  }
8603
9161
  return dir;
8604
9162
  }
8605
9163
  function saveQuery(workspaceRoot, name, gql) {
8606
9164
  const dir = ensureQueriesDir(workspaceRoot);
8607
- const filePath = path38.join(dir, `${name}.gql`);
8608
- fs37.writeFileSync(filePath, gql, "utf-8");
9165
+ const filePath = path39.join(dir, `${name}.gql`);
9166
+ fs38.writeFileSync(filePath, gql, "utf-8");
8609
9167
  }
8610
9168
  function loadQuery(workspaceRoot, name) {
8611
9169
  const dir = getQueriesDir(workspaceRoot);
8612
- const filePath = path38.join(dir, `${name}.gql`);
8613
- if (!fs37.existsSync(filePath)) return null;
8614
- return fs37.readFileSync(filePath, "utf-8");
9170
+ const filePath = path39.join(dir, `${name}.gql`);
9171
+ if (!fs38.existsSync(filePath)) return null;
9172
+ return fs38.readFileSync(filePath, "utf-8");
8615
9173
  }
8616
9174
  function listQueries(workspaceRoot) {
8617
9175
  const dir = getQueriesDir(workspaceRoot);
8618
- if (!fs37.existsSync(dir)) return [];
8619
- const files = fs37.readdirSync(dir).filter((f) => f.endsWith(".gql"));
9176
+ if (!fs38.existsSync(dir)) return [];
9177
+ const files = fs38.readdirSync(dir).filter((f) => f.endsWith(".gql"));
8620
9178
  return files.map((f) => {
8621
- const filePath = path38.join(dir, f);
9179
+ const filePath = path39.join(dir, f);
8622
9180
  const name = f.replace(/\.gql$/, "");
8623
- const content = fs37.readFileSync(filePath, "utf-8");
8624
- const stat = fs37.statSync(filePath);
9181
+ const content = fs38.readFileSync(filePath, "utf-8");
9182
+ const stat = fs38.statSync(filePath);
8625
9183
  return {
8626
9184
  name,
8627
9185
  content,
@@ -8632,15 +9190,15 @@ function listQueries(workspaceRoot) {
8632
9190
  }
8633
9191
  function deleteQuery(workspaceRoot, name) {
8634
9192
  const dir = getQueriesDir(workspaceRoot);
8635
- const filePath = path38.join(dir, `${name}.gql`);
8636
- if (!fs37.existsSync(filePath)) return false;
8637
- fs37.unlinkSync(filePath);
9193
+ const filePath = path39.join(dir, `${name}.gql`);
9194
+ if (!fs38.existsSync(filePath)) return false;
9195
+ fs38.unlinkSync(filePath);
8638
9196
  return true;
8639
9197
  }
8640
9198
  function queryExists(workspaceRoot, name) {
8641
9199
  const dir = getQueriesDir(workspaceRoot);
8642
- const filePath = path38.join(dir, `${name}.gql`);
8643
- return fs37.existsSync(filePath);
9200
+ const filePath = path39.join(dir, `${name}.gql`);
9201
+ return fs38.existsSync(filePath);
8644
9202
  }
8645
9203
  var init_saved_queries = __esm({
8646
9204
  "src/query/saved-queries.ts"() {
@@ -8711,10 +9269,439 @@ var init_sarif_builder = __esm({
8711
9269
  // src/cli/main.ts
8712
9270
  init_logger();
8713
9271
  init_knowledge_graph();
8714
- init_orchestrator();
8715
- init_phases();
8716
9272
 
8717
- // src/search/text-search.ts
9273
+ // src/graph/lazy-knowledge-graph.ts
9274
+ init_schema();
9275
+ init_logger();
9276
+ var TABLE_TO_KIND = Object.fromEntries(
9277
+ Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
9278
+ );
9279
+ var LRUCache = class {
9280
+ constructor(maxSize) {
9281
+ this.maxSize = maxSize;
9282
+ }
9283
+ maxSize;
9284
+ map = /* @__PURE__ */ new Map();
9285
+ get(key) {
9286
+ const val = this.map.get(key);
9287
+ if (val !== void 0) {
9288
+ this.map.delete(key);
9289
+ this.map.set(key, val);
9290
+ }
9291
+ return val;
9292
+ }
9293
+ set(key, value) {
9294
+ if (this.map.has(key)) this.map.delete(key);
9295
+ this.map.set(key, value);
9296
+ if (this.map.size > this.maxSize) {
9297
+ const lruKey = this.map.keys().next().value;
9298
+ this.map.delete(lruKey);
9299
+ }
9300
+ }
9301
+ has(key) {
9302
+ return this.map.has(key);
9303
+ }
9304
+ get size() {
9305
+ return this.map.size;
9306
+ }
9307
+ values() {
9308
+ return this.map.values();
9309
+ }
9310
+ clear() {
9311
+ this.map.clear();
9312
+ }
9313
+ /** Evict LRU entries until cache is at or below maxSize */
9314
+ evict() {
9315
+ while (this.map.size > this.maxSize) {
9316
+ const lruKey = this.map.keys().next().value;
9317
+ this.map.delete(lruKey);
9318
+ }
9319
+ }
9320
+ };
9321
+ function parseNodeRow(row, kind) {
9322
+ return {
9323
+ id: String(row["id"] ?? ""),
9324
+ kind,
9325
+ name: String(row["name"] ?? ""),
9326
+ filePath: String(row["file_path"] ?? ""),
9327
+ startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
9328
+ endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
9329
+ exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
9330
+ content: row["content"] ? String(row["content"]) : void 0,
9331
+ metadata: row["metadata"] ? (() => {
9332
+ try {
9333
+ return JSON.parse(String(row["metadata"]));
9334
+ } catch {
9335
+ return void 0;
9336
+ }
9337
+ })() : void 0
9338
+ };
9339
+ }
9340
+ function escCypher(s) {
9341
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
9342
+ }
9343
+ var LazyKnowledgeGraph = class {
9344
+ lazy = true;
9345
+ nodeCache;
9346
+ edges = /* @__PURE__ */ new Map();
9347
+ edgesFromNode = /* @__PURE__ */ new Map();
9348
+ edgesToNode = /* @__PURE__ */ new Map();
9349
+ edgesByKind = /* @__PURE__ */ new Map();
9350
+ _nodeCount = 0;
9351
+ _edgeCount = 0;
9352
+ dbManager = null;
9353
+ /**
9354
+ * Per-table row counts, populated lazily on first getNodePage call.
9355
+ * Allows native SKIP/LIMIT at the DB layer instead of streaming-and-discarding.
9356
+ */
9357
+ _tableNodeCounts = null;
9358
+ async getTableNodeCounts() {
9359
+ if (this._tableNodeCounts) return this._tableNodeCounts;
9360
+ if (!this.dbManager) return /* @__PURE__ */ new Map();
9361
+ const counts = /* @__PURE__ */ new Map();
9362
+ await Promise.all(
9363
+ ALL_NODE_TABLES.map(async (table) => {
9364
+ try {
9365
+ const rows = await this.dbManager.query(
9366
+ `MATCH (n:${table}) RETURN count(n) AS cnt`
9367
+ );
9368
+ counts.set(table, Number(rows[0]?.["cnt"] ?? 0));
9369
+ } catch {
9370
+ counts.set(table, 0);
9371
+ }
9372
+ })
9373
+ );
9374
+ this._tableNodeCounts = counts;
9375
+ return counts;
9376
+ }
9377
+ constructor() {
9378
+ const maxSize = parseInt(process.env["GRAPH_CACHE_SIZE"] ?? "5000", 10);
9379
+ this.nodeCache = new LRUCache(maxSize);
9380
+ }
9381
+ // ── Initialization ─────────────────────────────────────────────────────────
9382
+ /**
9383
+ * Init: load ALL edges into memory (lightweight — no content field).
9384
+ * Node counts come from meta.json — no nodes are loaded here.
9385
+ *
9386
+ * @param dbManager Open DB connection (kept alive for lazy node fetches).
9387
+ * @param nodeCount Total node count from meta.json stats.
9388
+ * @param edgeCount Estimated edge count from meta.json stats (updated after load).
9389
+ */
9390
+ async init(dbManager, nodeCount, edgeCount) {
9391
+ this.dbManager = dbManager;
9392
+ if (nodeCount !== void 0) this._nodeCount = nodeCount;
9393
+ if (edgeCount !== void 0) this._edgeCount = edgeCount;
9394
+ await this._loadAllEdges();
9395
+ }
9396
+ // ── Async extensions ───────────────────────────────────────────────────────
9397
+ /** Fetch a single node from DB on cache miss. */
9398
+ async getNodeAsync(id) {
9399
+ const cached = this.nodeCache.get(id);
9400
+ if (cached) return cached;
9401
+ if (!this.dbManager) return void 0;
9402
+ const colonIdx = id.indexOf(":");
9403
+ if (colonIdx === -1) return void 0;
9404
+ const kind = id.slice(0, colonIdx);
9405
+ const table = NODE_TABLE_MAP[kind];
9406
+ if (!table) return void 0;
9407
+ try {
9408
+ const rows = await this.dbManager.query(
9409
+ `MATCH (n:${table} {id: '${escCypher(id)}'}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`
9410
+ );
9411
+ if (rows.length === 0) return void 0;
9412
+ const row = rows[0];
9413
+ const node = parseNodeRow(
9414
+ {
9415
+ id: row["n.id"],
9416
+ name: row["n.name"],
9417
+ file_path: row["n.file_path"],
9418
+ start_line: row["n.start_line"],
9419
+ end_line: row["n.end_line"],
9420
+ exported: row["n.exported"],
9421
+ content: row["n.content"],
9422
+ metadata: row["n.metadata"]
9423
+ },
9424
+ kind
9425
+ );
9426
+ if (node.id && node.name) {
9427
+ this.nodeCache.set(node.id, node);
9428
+ return node;
9429
+ }
9430
+ return void 0;
9431
+ } catch {
9432
+ return void 0;
9433
+ }
9434
+ }
9435
+ /**
9436
+ * Return a page of nodes using native SKIP/LIMIT at the DB layer.
9437
+ * This avoids streaming and discarding rows for high offsets, making
9438
+ * large-offset pages (e.g. offset=2200) as fast as offset=0.
9439
+ */
9440
+ async getNodePage(offset, limit) {
9441
+ if (!this.dbManager) return [];
9442
+ const tableCounts = await this.getTableNodeCounts();
9443
+ const result = [];
9444
+ let tableStart = 0;
9445
+ for (const table of ALL_NODE_TABLES) {
9446
+ if (result.length >= limit) break;
9447
+ const tableCount = tableCounts.get(table) ?? 0;
9448
+ if (tableCount === 0) {
9449
+ tableStart += tableCount;
9450
+ continue;
9451
+ }
9452
+ const tableEnd = tableStart + tableCount;
9453
+ if (offset >= tableEnd) {
9454
+ tableStart = tableEnd;
9455
+ continue;
9456
+ }
9457
+ const kind = TABLE_TO_KIND[table];
9458
+ if (!kind) {
9459
+ tableStart = tableEnd;
9460
+ continue;
9461
+ }
9462
+ const skipInTable = Math.max(0, offset - tableStart);
9463
+ const remaining = limit - result.length;
9464
+ try {
9465
+ const rows = await this.dbManager.query(
9466
+ `MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata SKIP ${skipInTable} LIMIT ${remaining}`
9467
+ );
9468
+ for (const row of rows) {
9469
+ const node = parseNodeRow(
9470
+ {
9471
+ id: row["n.id"],
9472
+ name: row["n.name"],
9473
+ file_path: row["n.file_path"],
9474
+ start_line: row["n.start_line"],
9475
+ end_line: row["n.end_line"],
9476
+ exported: row["n.exported"],
9477
+ content: row["n.content"],
9478
+ metadata: row["n.metadata"]
9479
+ },
9480
+ kind
9481
+ );
9482
+ if (node.id && node.name) {
9483
+ this.nodeCache.set(node.id, node);
9484
+ result.push(node);
9485
+ }
9486
+ }
9487
+ } catch {
9488
+ }
9489
+ tableStart = tableEnd;
9490
+ }
9491
+ return result;
9492
+ }
9493
+ /**
9494
+ * Pre-warm the LRU cache with the top-N highest-blast-radius nodes
9495
+ * (those with the most outgoing edges). Called in background after startup.
9496
+ */
9497
+ async warmTopNodes(topN = 500) {
9498
+ if (!this.dbManager) return;
9499
+ const scored = [];
9500
+ for (const [nodeId, edgeSet] of this.edgesFromNode) {
9501
+ scored.push([nodeId, edgeSet.size]);
9502
+ }
9503
+ scored.sort((a, b) => b[1] - a[1]);
9504
+ const topIds = scored.slice(0, topN).map(([id]) => id);
9505
+ let loaded = 0;
9506
+ for (const id of topIds) {
9507
+ if (!this.nodeCache.has(id)) {
9508
+ await this.getNodeAsync(id);
9509
+ loaded++;
9510
+ }
9511
+ }
9512
+ logger_default.info(
9513
+ ` [lazy-graph] Background warm: ${loaded} high-blast-radius nodes loaded into cache`
9514
+ );
9515
+ }
9516
+ /** Async generator: stream all nodes from DB, caching each one. */
9517
+ async *allNodesAsync() {
9518
+ if (!this.dbManager) return;
9519
+ for (const table of ALL_NODE_TABLES) {
9520
+ const kind = TABLE_TO_KIND[table];
9521
+ if (!kind) continue;
9522
+ try {
9523
+ const rows = await this.dbManager.query(
9524
+ `MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`
9525
+ );
9526
+ for (const row of rows) {
9527
+ const node = parseNodeRow(
9528
+ {
9529
+ id: row["n.id"],
9530
+ name: row["n.name"],
9531
+ file_path: row["n.file_path"],
9532
+ start_line: row["n.start_line"],
9533
+ end_line: row["n.end_line"],
9534
+ exported: row["n.exported"],
9535
+ content: row["n.content"],
9536
+ metadata: row["n.metadata"]
9537
+ },
9538
+ kind
9539
+ );
9540
+ if (node.id && node.name) {
9541
+ this.nodeCache.set(node.id, node);
9542
+ yield node;
9543
+ }
9544
+ }
9545
+ } catch {
9546
+ continue;
9547
+ }
9548
+ }
9549
+ }
9550
+ // ── KnowledgeGraph interface ───────────────────────────────────────────────
9551
+ addNode(node) {
9552
+ this.nodeCache.set(node.id, node);
9553
+ this._nodeCount++;
9554
+ }
9555
+ addEdge(edge) {
9556
+ this.edges.set(edge.id, edge);
9557
+ this._indexEdge(edge);
9558
+ this._edgeCount = this.edges.size;
9559
+ }
9560
+ /**
9561
+ * Sync node lookup — returns from LRU cache only.
9562
+ * Use `getNodeAsync(id)` for a full DB lookup on cache miss.
9563
+ */
9564
+ getNode(id) {
9565
+ return this.nodeCache.get(id);
9566
+ }
9567
+ getEdge(id) {
9568
+ return this.edges.get(id);
9569
+ }
9570
+ *findEdgesByKind(kind) {
9571
+ const ids = this.edgesByKind.get(kind);
9572
+ if (!ids) return;
9573
+ for (const id of ids) {
9574
+ const edge = this.edges.get(id);
9575
+ if (edge) yield edge;
9576
+ }
9577
+ }
9578
+ *findEdgesFrom(sourceId) {
9579
+ const ids = this.edgesFromNode.get(sourceId);
9580
+ if (!ids) return;
9581
+ for (const id of ids) {
9582
+ const edge = this.edges.get(id);
9583
+ if (edge) yield edge;
9584
+ }
9585
+ }
9586
+ *findEdgesTo(targetId) {
9587
+ const ids = this.edgesToNode.get(targetId);
9588
+ if (!ids) return;
9589
+ for (const id of ids) {
9590
+ const edge = this.edges.get(id);
9591
+ if (edge) yield edge;
9592
+ }
9593
+ }
9594
+ removeNodeCascade(id) {
9595
+ for (const edgeId of [...this.edgesFromNode.get(id) ?? []]) {
9596
+ const edge = this.edges.get(edgeId);
9597
+ if (edge) {
9598
+ this._unindexEdge(edge);
9599
+ this.edges.delete(edgeId);
9600
+ }
9601
+ }
9602
+ for (const edgeId of [...this.edgesToNode.get(id) ?? []]) {
9603
+ const edge = this.edges.get(edgeId);
9604
+ if (edge) {
9605
+ this._unindexEdge(edge);
9606
+ this.edges.delete(edgeId);
9607
+ }
9608
+ }
9609
+ this.edgesFromNode.delete(id);
9610
+ this.edgesToNode.delete(id);
9611
+ this._nodeCount = Math.max(0, this._nodeCount - 1);
9612
+ }
9613
+ removeEdge(id) {
9614
+ const edge = this.edges.get(id);
9615
+ if (edge) {
9616
+ this._unindexEdge(edge);
9617
+ this.edges.delete(id);
9618
+ }
9619
+ }
9620
+ /**
9621
+ * Iterates only the cached nodes.
9622
+ * For full graph iteration use `allNodesAsync()`.
9623
+ */
9624
+ *allNodes() {
9625
+ yield* this.nodeCache.values();
9626
+ }
9627
+ *allEdges() {
9628
+ yield* this.edges.values();
9629
+ }
9630
+ get size() {
9631
+ return { nodes: this._nodeCount, edges: this._edgeCount };
9632
+ }
9633
+ clear() {
9634
+ this.nodeCache.clear();
9635
+ this.edges.clear();
9636
+ this.edgesFromNode.clear();
9637
+ this.edgesToNode.clear();
9638
+ this.edgesByKind.clear();
9639
+ this._nodeCount = 0;
9640
+ this._edgeCount = 0;
9641
+ }
9642
+ // ── Private helpers ────────────────────────────────────────────────────────
9643
+ async _loadAllEdges() {
9644
+ if (!this.dbManager) return;
9645
+ try {
9646
+ const edgeRows = await this.dbManager.query(
9647
+ `MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
9648
+ );
9649
+ for (const row of edgeRows) {
9650
+ const sourceId = String(row["a.id"] ?? "");
9651
+ const targetId = String(row["b.id"] ?? "");
9652
+ const kind = String(row["e.kind"] ?? "");
9653
+ if (!sourceId || !targetId || !kind) continue;
9654
+ const edge = {
9655
+ id: `${sourceId}::${kind}::${targetId}`,
9656
+ source: sourceId,
9657
+ target: targetId,
9658
+ kind,
9659
+ weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
9660
+ label: row["e.label"] ? String(row["e.label"]) : void 0
9661
+ };
9662
+ this.edges.set(edge.id, edge);
9663
+ this._indexEdge(edge);
9664
+ }
9665
+ this._edgeCount = this.edges.size;
9666
+ } catch (err) {
9667
+ logger_default.warn("[lazy-graph] Failed to load edges:", err instanceof Error ? err.message : err);
9668
+ }
9669
+ }
9670
+ _indexEdge(edge) {
9671
+ let kindSet = this.edgesByKind.get(edge.kind);
9672
+ if (!kindSet) {
9673
+ kindSet = /* @__PURE__ */ new Set();
9674
+ this.edgesByKind.set(edge.kind, kindSet);
9675
+ }
9676
+ kindSet.add(edge.id);
9677
+ let fromSet = this.edgesFromNode.get(edge.source);
9678
+ if (!fromSet) {
9679
+ fromSet = /* @__PURE__ */ new Set();
9680
+ this.edgesFromNode.set(edge.source, fromSet);
9681
+ }
9682
+ fromSet.add(edge.id);
9683
+ let toSet = this.edgesToNode.get(edge.target);
9684
+ if (!toSet) {
9685
+ toSet = /* @__PURE__ */ new Set();
9686
+ this.edgesToNode.set(edge.target, toSet);
9687
+ }
9688
+ toSet.add(edge.id);
9689
+ }
9690
+ _unindexEdge(edge) {
9691
+ this.edgesByKind.get(edge.kind)?.delete(edge.id);
9692
+ this.edgesFromNode.get(edge.source)?.delete(edge.id);
9693
+ this.edgesToNode.get(edge.target)?.delete(edge.id);
9694
+ }
9695
+ };
9696
+ function isLazyGraph(g) {
9697
+ return "lazy" in g && g.lazy === true;
9698
+ }
9699
+
9700
+ // src/cli/main.ts
9701
+ init_orchestrator();
9702
+ init_phases();
9703
+
9704
+ // src/search/text-search.ts
8718
9705
  function textSearch(graph, query, limit = 20) {
8719
9706
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
8720
9707
  const results = [];
@@ -8774,9 +9761,9 @@ function reciprocalRankFusion(...rankings) {
8774
9761
  init_vector_index();
8775
9762
  init_embedder();
8776
9763
  async function hybridSearch(graph, query, limit, options = {}) {
8777
- const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
8778
- const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
8779
- const hasVectorDb = Boolean(vectorDbPath && fs37.existsSync(vectorDbPath));
9764
+ const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
9765
+ const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
9766
+ const hasVectorDb = Boolean(vectorDbPath && fs38.existsSync(vectorDbPath));
8780
9767
  if (!hasVectorDb) {
8781
9768
  const bm25Results2 = await bm25Promise;
8782
9769
  return {
@@ -8827,8 +9814,8 @@ async function runVectorSearch(vectorDbPath, query, topK) {
8827
9814
  }
8828
9815
 
8829
9816
  // src/http/app.ts
9817
+ init_bm25_index();
8830
9818
  init_storage();
8831
- init_metadata();
8832
9819
  init_vector_index();
8833
9820
  init_group_registry();
8834
9821
  init_group_sync();
@@ -8843,10 +9830,10 @@ async function queryGroup(group, query, limit = 20) {
8843
9830
  for (const member of group.members) {
8844
9831
  const regEntry = registry.find((r) => r.name === member.registryName);
8845
9832
  if (!regEntry) continue;
8846
- const dbPath = path38.join(regEntry.path, ".code-intel", "graph.db");
8847
- if (!fs37.existsSync(dbPath)) continue;
9833
+ const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
9834
+ if (!fs38.existsSync(dbPath)) continue;
8848
9835
  const graph = createKnowledgeGraph();
8849
- const db = new DbManager(dbPath);
9836
+ const db = new DbManager(dbPath, true);
8850
9837
  try {
8851
9838
  await db.init();
8852
9839
  await loadGraphFromDB(graph, db);
@@ -8886,8 +9873,8 @@ var STUCK_THRESHOLD_MINUTES = 30;
8886
9873
  var JobsDB = class {
8887
9874
  db;
8888
9875
  constructor(dbPath) {
8889
- fs37.mkdirSync(path38.dirname(dbPath), { recursive: true });
8890
- this.db = new Database(dbPath);
9876
+ fs38.mkdirSync(path39.dirname(dbPath), { recursive: true });
9877
+ this.db = new Database2(dbPath);
8891
9878
  this.db.pragma("journal_mode = WAL");
8892
9879
  this.db.pragma("foreign_keys = ON");
8893
9880
  this.createTables();
@@ -9028,7 +10015,7 @@ var JobsDB = class {
9028
10015
  }
9029
10016
  };
9030
10017
  function getJobsDBPath() {
9031
- return path38.join(os13.homedir(), ".code-intel", "jobs.db");
10018
+ return path39.join(os13.homedir(), ".code-intel", "jobs.db");
9032
10019
  }
9033
10020
  var _jobsDB = null;
9034
10021
  function getOrCreateJobsDB() {
@@ -9123,7 +10110,7 @@ var BACKUP_VERSION = "1.0";
9123
10110
  var ALGORITHM = "aes-256-gcm";
9124
10111
  var IV_LENGTH = 16;
9125
10112
  function getBackupDir() {
9126
- return path38.join(os13.homedir(), ".code-intel", "backups");
10113
+ return path39.join(os13.homedir(), ".code-intel", "backups");
9127
10114
  }
9128
10115
  function getBackupKey() {
9129
10116
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
@@ -9154,30 +10141,30 @@ var BackupService = class {
9154
10141
  constructor(backupDir) {
9155
10142
  this.backupDir = backupDir ?? getBackupDir();
9156
10143
  this.key = getBackupKey();
9157
- fs37.mkdirSync(this.backupDir, { recursive: true });
10144
+ fs38.mkdirSync(this.backupDir, { recursive: true });
9158
10145
  }
9159
10146
  /**
9160
10147
  * Create a backup for a repository.
9161
10148
  * Returns the backup entry.
9162
10149
  */
9163
10150
  createBackup(repoPath) {
9164
- const codeIntelDir = path38.join(repoPath, ".code-intel");
10151
+ const codeIntelDir = path39.join(repoPath, ".code-intel");
9165
10152
  const id = v4();
9166
10153
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
9167
10154
  const filesToBackup = [];
9168
10155
  const candidates = ["graph.db", "vector.db", "meta.json"];
9169
10156
  for (const f of candidates) {
9170
- const fp = path38.join(codeIntelDir, f);
9171
- if (fs37.existsSync(fp)) {
10157
+ const fp = path39.join(codeIntelDir, f);
10158
+ if (fs38.existsSync(fp)) {
9172
10159
  filesToBackup.push({ name: f, localPath: fp });
9173
10160
  }
9174
10161
  }
9175
- const registryPath = path38.join(os13.homedir(), ".code-intel", "registry.json");
9176
- if (fs37.existsSync(registryPath)) {
10162
+ const registryPath = path39.join(os13.homedir(), ".code-intel", "registry.json");
10163
+ if (fs38.existsSync(registryPath)) {
9177
10164
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
9178
10165
  }
9179
- const usersDbPath = path38.join(os13.homedir(), ".code-intel", "users.db");
9180
- if (fs37.existsSync(usersDbPath)) {
10166
+ const usersDbPath = path39.join(os13.homedir(), ".code-intel", "users.db");
10167
+ if (fs38.existsSync(usersDbPath)) {
9181
10168
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
9182
10169
  }
9183
10170
  if (filesToBackup.length === 0) {
@@ -9188,7 +10175,7 @@ var BackupService = class {
9188
10175
  createdAt,
9189
10176
  version: BACKUP_VERSION,
9190
10177
  files: filesToBackup.map((f) => {
9191
- const data = fs37.readFileSync(f.localPath);
10178
+ const data = fs38.readFileSync(f.localPath);
9192
10179
  return {
9193
10180
  name: f.name,
9194
10181
  sha256: crypto5.createHash("sha256").update(data).digest("hex"),
@@ -9202,7 +10189,7 @@ var BackupService = class {
9202
10189
  manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
9203
10190
  parts.push(manifestLenBuf, manifestBuf);
9204
10191
  for (const f of filesToBackup) {
9205
- const data = fs37.readFileSync(f.localPath);
10192
+ const data = fs38.readFileSync(f.localPath);
9206
10193
  const nameBuf = Buffer.from(f.name, "utf-8");
9207
10194
  const nameLenBuf = Buffer.alloc(2);
9208
10195
  nameLenBuf.writeUInt16BE(nameBuf.length, 0);
@@ -9213,8 +10200,8 @@ var BackupService = class {
9213
10200
  const plaintext = Buffer.concat(parts);
9214
10201
  const encrypted = encryptBuffer(plaintext, this.key);
9215
10202
  const backupFileName = `backup-${id}.cib`;
9216
- const backupPath = path38.join(this.backupDir, backupFileName);
9217
- fs37.writeFileSync(backupPath, encrypted);
10203
+ const backupPath = path39.join(this.backupDir, backupFileName);
10204
+ fs38.writeFileSync(backupPath, encrypted);
9218
10205
  const entry = {
9219
10206
  id,
9220
10207
  createdAt,
@@ -9241,9 +10228,9 @@ var BackupService = class {
9241
10228
  async uploadToS3(entry) {
9242
10229
  const cfg = getS3Config();
9243
10230
  if (!cfg) throw new Error("S3 not configured. Set CODE_INTEL_BACKUP_S3_BUCKET, CODE_INTEL_BACKUP_S3_ACCESS_KEY_ID, CODE_INTEL_BACKUP_S3_SECRET_ACCESS_KEY.");
9244
- const fileName = path38.basename(entry.path);
10231
+ const fileName = path39.basename(entry.path);
9245
10232
  const s3Key = `${cfg.prefix}${fileName}`;
9246
- const body = fs37.readFileSync(entry.path);
10233
+ const body = fs38.readFileSync(entry.path);
9247
10234
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
9248
10235
  if (result.statusCode < 200 || result.statusCode >= 300) {
9249
10236
  throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
@@ -9260,8 +10247,8 @@ var BackupService = class {
9260
10247
  if (result.statusCode < 200 || result.statusCode >= 300) {
9261
10248
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
9262
10249
  }
9263
- fs37.mkdirSync(path38.dirname(destPath), { recursive: true });
9264
- fs37.writeFileSync(destPath, Buffer.from(result.body, "binary"));
10250
+ fs38.mkdirSync(path39.dirname(destPath), { recursive: true });
10251
+ fs38.writeFileSync(destPath, Buffer.from(result.body, "binary"));
9265
10252
  }
9266
10253
  /**
9267
10254
  * List backup objects in S3 with the configured prefix.
@@ -9307,10 +10294,10 @@ var BackupService = class {
9307
10294
  if (!entry) {
9308
10295
  throw new Error(`Backup "${backupId}" not found.`);
9309
10296
  }
9310
- if (!fs37.existsSync(entry.path)) {
10297
+ if (!fs38.existsSync(entry.path)) {
9311
10298
  throw new Error(`Backup file not found at: ${entry.path}`);
9312
10299
  }
9313
- const encrypted = fs37.readFileSync(entry.path);
10300
+ const encrypted = fs38.readFileSync(entry.path);
9314
10301
  let plaintext;
9315
10302
  try {
9316
10303
  plaintext = decryptBuffer(encrypted, this.key);
@@ -9324,8 +10311,8 @@ var BackupService = class {
9324
10311
  offset += manifestLen;
9325
10312
  const manifest = JSON.parse(manifestStr);
9326
10313
  const restoreBase = targetRepoPath ?? entry.repoPath;
9327
- const codeIntelDir = path38.join(restoreBase, ".code-intel");
9328
- fs37.mkdirSync(codeIntelDir, { recursive: true });
10314
+ const codeIntelDir = path39.join(restoreBase, ".code-intel");
10315
+ fs38.mkdirSync(codeIntelDir, { recursive: true });
9329
10316
  for (const fileEntry of manifest.files) {
9330
10317
  const nameLen = plaintext.readUInt16BE(offset);
9331
10318
  offset += 2;
@@ -9342,18 +10329,18 @@ var BackupService = class {
9342
10329
  }
9343
10330
  let destPath;
9344
10331
  if (name === "registry.json" || name === "users.db") {
9345
- destPath = path38.join(os13.homedir(), ".code-intel", name);
10332
+ destPath = path39.join(os13.homedir(), ".code-intel", name);
9346
10333
  } else {
9347
- destPath = path38.join(codeIntelDir, name);
10334
+ destPath = path39.join(codeIntelDir, name);
9348
10335
  }
9349
- fs37.writeFileSync(destPath, data);
10336
+ fs38.writeFileSync(destPath, data);
9350
10337
  }
9351
10338
  }
9352
10339
  /**
9353
10340
  * Apply retention policy: keep N daily, M weekly, L monthly backups.
9354
10341
  */
9355
10342
  applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
9356
- const entries = this._loadIndex().filter((e) => fs37.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
10343
+ const entries = this._loadIndex().filter((e) => fs38.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
9357
10344
  const keep = /* @__PURE__ */ new Set();
9358
10345
  const now = /* @__PURE__ */ new Date();
9359
10346
  const dailyCutoff = new Date(now);
@@ -9383,7 +10370,7 @@ var BackupService = class {
9383
10370
  for (const e of entries) {
9384
10371
  if (!keep.has(e.id)) {
9385
10372
  try {
9386
- fs37.unlinkSync(e.path);
10373
+ fs38.unlinkSync(e.path);
9387
10374
  deleted++;
9388
10375
  } catch {
9389
10376
  }
@@ -9395,17 +10382,17 @@ var BackupService = class {
9395
10382
  }
9396
10383
  // ── Index helpers ──────────────────────────────────────────────────────────
9397
10384
  _indexPath() {
9398
- return path38.join(this.backupDir, "index.json");
10385
+ return path39.join(this.backupDir, "index.json");
9399
10386
  }
9400
10387
  _loadIndex() {
9401
10388
  try {
9402
- return JSON.parse(fs37.readFileSync(this._indexPath(), "utf-8"));
10389
+ return JSON.parse(fs38.readFileSync(this._indexPath(), "utf-8"));
9403
10390
  } catch {
9404
10391
  return [];
9405
10392
  }
9406
10393
  }
9407
10394
  _saveIndex(entries) {
9408
- fs37.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
10395
+ fs38.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
9409
10396
  }
9410
10397
  _appendIndex(entry) {
9411
10398
  const entries = this._loadIndex();
@@ -10046,11 +11033,11 @@ var openApiSpec = {
10046
11033
  };
10047
11034
 
10048
11035
  // src/http/app.ts
10049
- var __dirname$1 = path38.dirname(fileURLToPath(import.meta.url));
11036
+ var __dirname$1 = path39.dirname(fileURLToPath(import.meta.url));
10050
11037
  var WEB_DIST = (() => {
10051
- const bundled = path38.resolve(__dirname$1, "..", "web");
10052
- if (fs37.existsSync(bundled)) return bundled;
10053
- return path38.resolve(__dirname$1, "..", "..", "..", "web", "dist");
11038
+ const bundled = path39.resolve(__dirname$1, "..", "web");
11039
+ if (fs38.existsSync(bundled)) return bundled;
11040
+ return path39.resolve(__dirname$1, "..", "..", "..", "web", "dist");
10054
11041
  })();
10055
11042
  function getAllowedOrigins() {
10056
11043
  const env = process.env["CODE_INTEL_CORS_ORIGINS"];
@@ -10059,13 +11046,17 @@ function getAllowedOrigins() {
10059
11046
  }
10060
11047
  function createDefaultLimiter() {
10061
11048
  const max = parseInt(process.env["CODE_INTEL_RATE_LIMIT_MAX"] ?? "100", 10);
10062
- const windowMs = parseInt(process.env["CODE_INTEL_RATE_LIMIT_WINDOW_MS"] ?? `${15 * 60 * 1e3}`, 10);
11049
+ const windowMs = parseInt(process.env["CODE_INTEL_RATE_LIMIT_WINDOW_MS"] ?? `${60 * 1e3}`, 10);
10063
11050
  return rateLimit({
10064
11051
  windowMs,
10065
11052
  max,
10066
11053
  standardHeaders: true,
10067
11054
  legacyHeaders: false,
10068
- skip: (req) => req.path.startsWith("/health") || req.path === "/metrics",
11055
+ // Skip health checks, metrics, and read-only listing/pagination endpoints.
11056
+ // The node pagination and group/repo listing endpoints are hit many times
11057
+ // when loading a large graph — rate-limiting them only hurts the user's own
11058
+ // session without providing meaningful abuse protection.
11059
+ skip: (req) => req.path.startsWith("/health") || req.path === "/metrics" || req.path === "/api/v1/repos" || req.path === "/api/v1/groups" || req.method === "GET" && req.path.startsWith("/api/v1/groups/") || /^\/api\/v1\/graph\/[^/]+\/nodes$/.test(req.path),
10069
11060
  message: {
10070
11061
  error: {
10071
11062
  code: ErrorCodes.RATE_LIMIT_EXCEEDED,
@@ -10124,12 +11115,31 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10124
11115
  });
10125
11116
  app.use(requestIdMiddleware);
10126
11117
  app.use(authMiddleware);
11118
+ let dbUnavailableSince = null;
10127
11119
  app.use((_req, res, next) => {
10128
11120
  if (workspaceRoot) {
11121
+ const metaFilePath = path39.join(workspaceRoot, ".code-intel", "meta.json");
11122
+ let metaOk = false;
10129
11123
  try {
10130
- const meta = loadMetadata(workspaceRoot);
10131
- if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
10132
- } catch {
11124
+ if (fs38.existsSync(metaFilePath)) {
11125
+ const raw = fs38.readFileSync(metaFilePath, "utf-8");
11126
+ const meta = JSON.parse(raw);
11127
+ if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
11128
+ }
11129
+ metaOk = true;
11130
+ if (dbUnavailableSince !== null) {
11131
+ dbUnavailableSince = null;
11132
+ logger_default.info("[serve] DB back online \u2014 cleared stale flag");
11133
+ }
11134
+ } catch (err) {
11135
+ if (dbUnavailableSince === null) {
11136
+ dbUnavailableSince = (/* @__PURE__ */ new Date()).toISOString();
11137
+ logger_default.warn(`[serve] DB unavailable since ${dbUnavailableSince}: ${err instanceof Error ? err.message : String(err)}`);
11138
+ }
11139
+ }
11140
+ if (!metaOk) {
11141
+ res.setHeader("X-Stale", "true");
11142
+ res.setHeader("X-Stale-Since", dbUnavailableSince);
10133
11143
  }
10134
11144
  }
10135
11145
  next();
@@ -10185,6 +11195,21 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10185
11195
  let vectorIndex = null;
10186
11196
  let vectorIndexBuilding = false;
10187
11197
  let vectorIndexReady = false;
11198
+ let bm25Index = null;
11199
+ function ensureBm25Index() {
11200
+ if (bm25Index) return bm25Index;
11201
+ if (!workspaceRoot) return null;
11202
+ const idx = new Bm25Index(getBm25DbPath(workspaceRoot));
11203
+ idx.load();
11204
+ if (idx.isLoaded) {
11205
+ bm25Index = idx;
11206
+ return idx;
11207
+ }
11208
+ return null;
11209
+ }
11210
+ if (workspaceRoot && process.env["NODE_ENV"] !== "test") {
11211
+ setImmediate(() => ensureBm25Index());
11212
+ }
10188
11213
  async function ensureVectorIndex() {
10189
11214
  if (vectorIndexReady && vectorIndex) return vectorIndex;
10190
11215
  if (!workspaceRoot || vectorIndexBuilding) return null;
@@ -10581,10 +11606,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10581
11606
  const registry = loadRegistry();
10582
11607
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
10583
11608
  if (!entry) return null;
10584
- const dbPath = path38.join(entry.path, ".code-intel", "graph.db");
10585
- if (!fs37.existsSync(dbPath)) return null;
11609
+ const dbPath = path39.join(entry.path, ".code-intel", "graph.db");
11610
+ if (!fs38.existsSync(dbPath)) return null;
10586
11611
  const repoGraph = createKnowledgeGraph();
10587
- const db = new DbManager(dbPath);
11612
+ const db = new DbManager(dbPath, true);
10588
11613
  try {
10589
11614
  await db.init();
10590
11615
  await loadGraphFromDB(repoGraph, db);
@@ -10595,10 +11620,33 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10595
11620
  return null;
10596
11621
  }
10597
11622
  }
11623
+ async function loadGroupGraph(groupName) {
11624
+ const group = loadGroup(groupName);
11625
+ if (!group) return null;
11626
+ const registry = loadRegistry();
11627
+ const mergedGraph = createKnowledgeGraph();
11628
+ for (const member of group.members) {
11629
+ const regEntry = registry.find((r) => r.name === member.registryName);
11630
+ if (!regEntry) continue;
11631
+ const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
11632
+ if (!fs38.existsSync(dbPath)) continue;
11633
+ const db = new DbManager(dbPath, true);
11634
+ try {
11635
+ await db.init();
11636
+ await loadGraphFromDB(mergedGraph, db);
11637
+ db.close();
11638
+ } catch {
11639
+ db.close();
11640
+ }
11641
+ }
11642
+ return mergedGraph.size.nodes > 0 ? mergedGraph : null;
11643
+ }
10598
11644
  async function getGraphForRepo(requestedRepo) {
10599
11645
  if (!requestedRepo || requestedRepo === repoName) return graph;
10600
11646
  const g = await loadRepoGraph(requestedRepo);
10601
- return g ?? graph;
11647
+ if (g) return g;
11648
+ const gg = await loadGroupGraph(requestedRepo);
11649
+ return gg ?? graph;
10602
11650
  }
10603
11651
  app.get("/api/v1/graph/:repo", requireRepoAccess((req) => {
10604
11652
  const p = req.params["repo"];
@@ -10622,11 +11670,56 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10622
11670
  }
10623
11671
  res.json({ nodes: [...g.allNodes()], edges: [...g.allEdges()] });
10624
11672
  });
11673
+ app.get("/api/v1/graph/:repo/nodes", requireRepoAccess((req) => {
11674
+ const p = req.params["repo"];
11675
+ const repo = Array.isArray(p) ? p[0] : p;
11676
+ return repo ? decodeURIComponent(repo) : void 0;
11677
+ }), async (req, res) => {
11678
+ const rawRepo = req.params["repo"];
11679
+ const requestedRepo = decodeURIComponent(Array.isArray(rawRepo) ? rawRepo[0] ?? "" : rawRepo ?? "");
11680
+ const limit = Math.min(parseInt(req.query["limit"] ?? "200", 10), 1e3);
11681
+ const offset = Math.max(parseInt(req.query["offset"] ?? "0", 10), 0);
11682
+ const g = requestedRepo === repoName ? graph : await loadRepoGraph(requestedRepo);
11683
+ if (!g) {
11684
+ res.status(404).json({
11685
+ error: {
11686
+ code: ErrorCodes.NOT_FOUND,
11687
+ message: `Repo "${requestedRepo}" not found or not indexed`,
11688
+ hint: `Run: code-intel analyze <path>`,
11689
+ requestId: req.requestId,
11690
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11691
+ }
11692
+ });
11693
+ return;
11694
+ }
11695
+ let nodes;
11696
+ if (isLazyGraph(g)) {
11697
+ nodes = await g.getNodePage(offset, limit);
11698
+ } else {
11699
+ const eager = g;
11700
+ if (!eager._nodeArray) {
11701
+ eager._nodeArray = [...g.allNodes()];
11702
+ }
11703
+ nodes = eager._nodeArray.slice(offset, offset + limit);
11704
+ }
11705
+ res.json({
11706
+ nodes,
11707
+ offset,
11708
+ limit,
11709
+ total: g.size.nodes,
11710
+ hasMore: offset + nodes.length < g.size.nodes
11711
+ });
11712
+ });
10625
11713
  app.post("/api/v1/search", requireToolScope("search"), async (req, res) => {
10626
11714
  const { query, limit, repo } = req.body;
10627
11715
  const g = await getGraphForRepo(repo);
10628
11716
  const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
10629
- const { results, searchMode } = await hybridSearch(g, query ?? "", limit ?? 20, { vectorDbPath: vdbPath });
11717
+ const bm25 = !repo || repo === repoName ? ensureBm25Index() : null;
11718
+ const bm25Results = bm25 ? bm25.search(query ?? "", (limit ?? 20) * 3) : null;
11719
+ const { results, searchMode } = await hybridSearch(g, query ?? "", limit ?? 20, {
11720
+ vectorDbPath: vdbPath,
11721
+ bm25Results: bm25Results ?? void 0
11722
+ });
10630
11723
  res.json({ results, searchMode });
10631
11724
  });
10632
11725
  app.post("/api/v1/vector-search", async (req, res) => {
@@ -10669,7 +11762,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10669
11762
  return;
10670
11763
  }
10671
11764
  try {
10672
- const content = fs37.readFileSync(file_path, "utf-8");
11765
+ const content = fs38.readFileSync(file_path, "utf-8");
10673
11766
  res.json({ content });
10674
11767
  } catch {
10675
11768
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
@@ -10706,7 +11799,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10706
11799
  if (workspaceRoot) {
10707
11800
  try {
10708
11801
  const dbPath = getDbPath(workspaceRoot);
10709
- const dbm = new DbManager(dbPath);
11802
+ const dbm = new DbManager(dbPath, true);
10710
11803
  await dbm.init();
10711
11804
  const rows = await dbm.query(q);
10712
11805
  dbm.close();
@@ -10747,33 +11840,53 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10747
11840
  app.get("/api/v1/nodes/:id", async (req, res) => {
10748
11841
  const nodeId = decodeURIComponent(req.params.id);
10749
11842
  const g = await getGraphForRepo(req.query["repo"]);
10750
- const node = g.getNode(nodeId);
11843
+ const node = isLazyGraph(g) ? await g.getNodeAsync(nodeId) : g.getNode(nodeId);
10751
11844
  if (!node) {
10752
11845
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Node not found", requestId: req.requestId } });
10753
11846
  return;
10754
11847
  }
10755
11848
  const incoming = [...g.findEdgesTo(nodeId)];
10756
11849
  const outgoing = [...g.findEdgesFrom(nodeId)];
11850
+ const resolveName = isLazyGraph(g) ? async (id) => {
11851
+ const n = g.getNode(id) ?? await g.getNodeAsync(id);
11852
+ return n?.name;
11853
+ } : (id) => Promise.resolve(g.getNode(id)?.name);
11854
+ const resolveKind = isLazyGraph(g) ? async (id) => {
11855
+ const n = g.getNode(id) ?? await g.getNodeAsync(id);
11856
+ return n?.kind;
11857
+ } : (id) => Promise.resolve(g.getNode(id)?.kind);
10757
11858
  res.json({
10758
11859
  node,
10759
- callers: incoming.filter((e) => e.kind === "calls").map((e) => ({ id: e.source, name: g.getNode(e.source)?.name, weight: e.weight })),
10760
- callees: outgoing.filter((e) => e.kind === "calls").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name, weight: e.weight })),
10761
- imports: outgoing.filter((e) => e.kind === "imports").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name })),
10762
- importedBy: incoming.filter((e) => e.kind === "imports").map((e) => ({ id: e.source, name: g.getNode(e.source)?.name })),
10763
- extends: outgoing.filter((e) => e.kind === "extends").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name })),
10764
- implementsEdges: outgoing.filter((e) => e.kind === "implements").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name })),
10765
- members: outgoing.filter((e) => e.kind === "has_member").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name, kind: g.getNode(e.target)?.kind })),
10766
- cluster: incoming.filter((e) => e.kind === "belongs_to").map((e) => g.getNode(e.target)?.name)[0]
11860
+ callers: await Promise.all(incoming.filter((e) => e.kind === "calls").map(async (e) => ({ id: e.source, name: await resolveName(e.source), weight: e.weight }))),
11861
+ callees: await Promise.all(outgoing.filter((e) => e.kind === "calls").map(async (e) => ({ id: e.target, name: await resolveName(e.target), weight: e.weight }))),
11862
+ imports: await Promise.all(outgoing.filter((e) => e.kind === "imports").map(async (e) => ({ id: e.target, name: await resolveName(e.target) }))),
11863
+ importedBy: await Promise.all(incoming.filter((e) => e.kind === "imports").map(async (e) => ({ id: e.source, name: await resolveName(e.source) }))),
11864
+ extends: await Promise.all(outgoing.filter((e) => e.kind === "extends").map(async (e) => ({ id: e.target, name: await resolveName(e.target) }))),
11865
+ implementsEdges: await Promise.all(outgoing.filter((e) => e.kind === "implements").map(async (e) => ({ id: e.target, name: await resolveName(e.target) }))),
11866
+ members: await Promise.all(outgoing.filter((e) => e.kind === "has_member").map(async (e) => ({ id: e.target, name: await resolveName(e.target), kind: await resolveKind(e.target) }))),
11867
+ cluster: (await Promise.all(incoming.filter((e) => e.kind === "belongs_to").map(async (e) => resolveName(e.target))))[0]
10767
11868
  });
10768
11869
  });
10769
11870
  app.post("/api/v1/blast-radius", async (req, res) => {
10770
11871
  const { target, direction = "both", max_hops = 5, repo } = req.body;
10771
11872
  const g = await getGraphForRepo(repo);
10772
11873
  let targetNode = null;
10773
- for (const node of g.allNodes()) {
10774
- if (node.name === target || node.id === target) {
10775
- targetNode = node;
10776
- break;
11874
+ if (isLazyGraph(g) && target) {
11875
+ targetNode = g.getNode(target) ?? await g.getNodeAsync(target) ?? null;
11876
+ if (!targetNode) {
11877
+ for await (const node of g.allNodesAsync()) {
11878
+ if (node.name === target || node.id === target) {
11879
+ targetNode = node;
11880
+ break;
11881
+ }
11882
+ }
11883
+ }
11884
+ } else {
11885
+ for (const node of g.allNodes()) {
11886
+ if (node.name === target || node.id === target) {
11887
+ targetNode = node;
11888
+ break;
11889
+ }
10777
11890
  }
10778
11891
  }
10779
11892
  if (!targetNode) {
@@ -10927,9 +12040,9 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10927
12040
  for (const member of group.members) {
10928
12041
  const regEntry = registry.find((r) => r.name === member.registryName);
10929
12042
  if (!regEntry) continue;
10930
- const dbPath = path38.join(regEntry.path, ".code-intel", "graph.db");
10931
- if (!fs37.existsSync(dbPath)) continue;
10932
- const db = new DbManager(dbPath);
12043
+ const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
12044
+ if (!fs38.existsSync(dbPath)) continue;
12045
+ const db = new DbManager(dbPath, true);
10933
12046
  try {
10934
12047
  await db.init();
10935
12048
  await loadGraphFromDB(mergedGraph, db);
@@ -10954,10 +12067,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10954
12067
  let nodeCount = 0;
10955
12068
  let edgeCount = 0;
10956
12069
  if (regEntry) {
10957
- const dbPath = path38.join(regEntry.path, ".code-intel", "graph.db");
10958
- if (fs37.existsSync(dbPath)) {
12070
+ const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
12071
+ if (fs38.existsSync(dbPath)) {
10959
12072
  try {
10960
- const db = new DbManager(dbPath);
12073
+ const db = new DbManager(dbPath, true);
10961
12074
  await db.init();
10962
12075
  const g = createKnowledgeGraph();
10963
12076
  await loadGraphFromDB(g, db);
@@ -10980,7 +12093,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10980
12093
  res.json({ repos, edges });
10981
12094
  });
10982
12095
  app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
10983
- const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
12096
+ const { file, startLine: startLineStr, endLine: endLineStr, repo } = req.query;
10984
12097
  if (!file) {
10985
12098
  res.status(400).json({
10986
12099
  error: {
@@ -11004,14 +12117,36 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11004
12117
  });
11005
12118
  return;
11006
12119
  }
11007
- let rawResolved = path38.normalize(file);
11008
- if (!path38.isAbsolute(rawResolved) && workspaceRoot) {
11009
- rawResolved = path38.join(workspaceRoot, rawResolved);
12120
+ let baseDir = workspaceRoot;
12121
+ if (repo && repo !== repoName) {
12122
+ const registry = loadRegistry();
12123
+ const entry = registry.find((r) => r.name === repo || r.path === repo);
12124
+ if (entry) {
12125
+ baseDir = entry.path;
12126
+ } else {
12127
+ const group = loadGroup(repo);
12128
+ if (group) {
12129
+ const normalizedFile = path39.normalize(file);
12130
+ for (const member of group.members) {
12131
+ const regEntry = registry.find((r) => r.name === member.registryName);
12132
+ if (!regEntry) continue;
12133
+ const candidate = path39.resolve(path39.join(regEntry.path, normalizedFile));
12134
+ if (fs38.existsSync(candidate)) {
12135
+ baseDir = regEntry.path;
12136
+ break;
12137
+ }
12138
+ }
12139
+ }
12140
+ }
12141
+ }
12142
+ let rawResolved = path39.normalize(file);
12143
+ if (!path39.isAbsolute(rawResolved) && baseDir) {
12144
+ rawResolved = path39.join(baseDir, rawResolved);
11010
12145
  }
11011
- const resolvedFile = path38.resolve(rawResolved);
12146
+ const resolvedFile = path39.resolve(rawResolved);
11012
12147
  function isInsideDir(fileAbs, dir) {
11013
- const rel = path38.relative(path38.resolve(dir), fileAbs);
11014
- return !rel.startsWith("..") && !path38.isAbsolute(rel);
12148
+ const rel = path39.relative(path39.resolve(dir), fileAbs);
12149
+ return !rel.startsWith("..") && !path39.isAbsolute(rel);
11015
12150
  }
11016
12151
  if (workspaceRoot) {
11017
12152
  if (!isInsideDir(resolvedFile, workspaceRoot)) {
@@ -11048,7 +12183,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11048
12183
  }
11049
12184
  let fileContent;
11050
12185
  try {
11051
- fileContent = fs37.readFileSync(resolvedFile, "utf-8");
12186
+ fileContent = fs38.readFileSync(resolvedFile, "utf-8");
11052
12187
  } catch {
11053
12188
  res.status(404).json({
11054
12189
  error: {
@@ -11079,7 +12214,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11079
12214
  const contextStart = Math.max(1, startLine - 20);
11080
12215
  const contextEnd = Math.min(lines.length, endLine + 20);
11081
12216
  const content = lines.slice(contextStart - 1, contextEnd).join("\n");
11082
- const ext = path38.extname(resolvedFile).toLowerCase();
12217
+ const ext = path39.extname(resolvedFile).toLowerCase();
11083
12218
  const languageMap = {
11084
12219
  ".ts": "typescript",
11085
12220
  ".tsx": "typescript",
@@ -11214,10 +12349,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11214
12349
  res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err), requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() } });
11215
12350
  }
11216
12351
  });
11217
- if (fs37.existsSync(WEB_DIST)) {
12352
+ if (fs38.existsSync(WEB_DIST)) {
11218
12353
  app.use(express.static(WEB_DIST));
11219
12354
  app.get("/{*path}", (_req, res) => {
11220
- res.sendFile(path38.join(WEB_DIST, "index.html"));
12355
+ res.sendFile(path39.join(WEB_DIST, "index.html"));
11221
12356
  });
11222
12357
  }
11223
12358
  app.use("/admin", requireRole("admin"));
@@ -11746,22 +12881,22 @@ function suggestTests(graph, symbolName) {
11746
12881
  const callPaths = [];
11747
12882
  const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
11748
12883
  while (pathQueue.length > 0 && callPaths.length < 5) {
11749
- const { id, path: path39, depth } = pathQueue.shift();
12884
+ const { id, path: path40, depth } = pathQueue.shift();
11750
12885
  let hasCallers2 = false;
11751
12886
  for (const edge of graph.findEdgesTo(id)) {
11752
12887
  if (edge.kind !== "calls") continue;
11753
12888
  const callerNode = graph.getNode(edge.source);
11754
12889
  if (!callerNode) continue;
11755
12890
  hasCallers2 = true;
11756
- const newPath = [callerNode.name, ...path39];
12891
+ const newPath = [callerNode.name, ...path40];
11757
12892
  if (depth + 1 >= 3 || callPaths.length >= 5) {
11758
12893
  if (callPaths.length < 5) callPaths.push(newPath);
11759
12894
  continue;
11760
12895
  }
11761
12896
  pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
11762
12897
  }
11763
- if (!hasCallers2 && path39.length > 1) {
11764
- callPaths.push(path39);
12898
+ if (!hasCallers2 && path40.length > 1) {
12899
+ callPaths.push(path40);
11765
12900
  }
11766
12901
  }
11767
12902
  if (callPaths.length === 0) {
@@ -12318,24 +13453,44 @@ function createMcpServer(graph, repoName, workspaceRoot) {
12318
13453
  }
12319
13454
  const startMs = Date.now();
12320
13455
  const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot);
13456
+ const MCP_TIMEOUT_MS = parseInt(process.env["CODE_INTEL_MCP_TIMEOUT_MS"] ?? "30000", 10);
13457
+ let timeoutHandle = null;
13458
+ let timedOut = false;
13459
+ const timeoutPromise = new Promise((_, reject) => {
13460
+ timeoutHandle = setTimeout(() => {
13461
+ timedOut = true;
13462
+ reject(new Error(`MCP tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`));
13463
+ }, MCP_TIMEOUT_MS);
13464
+ });
12321
13465
  let result;
12322
13466
  let status = "success";
12323
13467
  try {
12324
13468
  if (isTracingEnabled()) {
12325
- result = await withSpan(
12326
- `mcp.tool.${name}`,
12327
- sanitizeAttrs({ "mcp.tool": name, "mcp.repo": repoName }),
12328
- dispatch
12329
- );
13469
+ result = await Promise.race([
13470
+ withSpan(
13471
+ `mcp.tool.${name}`,
13472
+ sanitizeAttrs({ "mcp.tool": name, "mcp.repo": repoName }),
13473
+ dispatch
13474
+ ),
13475
+ timeoutPromise
13476
+ ]);
12330
13477
  } else {
12331
- result = await dispatch();
13478
+ result = await Promise.race([dispatch(), timeoutPromise]);
12332
13479
  }
12333
13480
  if (result.isError) status = "error";
12334
13481
  } catch (err) {
12335
13482
  status = "error";
12336
13483
  mcpToolCallsTotal.inc({ tool: name, status });
12337
13484
  mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
13485
+ if (timedOut) {
13486
+ return {
13487
+ content: [{ type: "text", text: JSON.stringify({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
13488
+ isError: false
13489
+ };
13490
+ }
12338
13491
  throw err;
13492
+ } finally {
13493
+ if (timeoutHandle) clearTimeout(timeoutHandle);
12339
13494
  }
12340
13495
  mcpToolCallsTotal.inc({ tool: name, status });
12341
13496
  mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
@@ -12723,7 +13878,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
12723
13878
  for (const { filePath: changedFile, changedLines } of changedFiles) {
12724
13879
  for (const node of graph.allNodes()) {
12725
13880
  if (!node.filePath) continue;
12726
- const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path38.sep, "");
13881
+ const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path39.sep, "");
12727
13882
  const normChanged = changedFile.replace(/^a\/|^b\//, "");
12728
13883
  if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
12729
13884
  if (node.startLine !== void 0 && node.endLine !== void 0) {
@@ -13139,20 +14294,20 @@ function parseDiff(diffText) {
13139
14294
  // src/cli/main.ts
13140
14295
  init_metadata();
13141
14296
  async function writeSkillFiles(graph, workspaceRoot, projectName) {
13142
- const outputDir = path38.join(workspaceRoot, ".claude", "skills", "code-intel");
14297
+ const outputDir = path39.join(workspaceRoot, ".claude", "skills", "code-intel");
13143
14298
  const areas = buildAreaMap(graph, workspaceRoot);
13144
14299
  if (areas.length === 0) return { skills: [], outputDir };
13145
- fs37.rmSync(outputDir, { recursive: true, force: true });
13146
- fs37.mkdirSync(outputDir, { recursive: true });
14300
+ fs38.rmSync(outputDir, { recursive: true, force: true });
14301
+ fs38.mkdirSync(outputDir, { recursive: true });
13147
14302
  const skills = [];
13148
14303
  const usedNames = /* @__PURE__ */ new Set();
13149
14304
  for (const area of areas) {
13150
14305
  const kebab = uniqueKebab(area.label, usedNames);
13151
14306
  usedNames.add(kebab);
13152
14307
  const content = renderSkill(area, projectName, kebab);
13153
- const dir = path38.join(outputDir, kebab);
13154
- fs37.mkdirSync(dir, { recursive: true });
13155
- fs37.writeFileSync(path38.join(dir, "SKILL.md"), content, "utf-8");
14308
+ const dir = path39.join(outputDir, kebab);
14309
+ fs38.mkdirSync(dir, { recursive: true });
14310
+ fs38.writeFileSync(path39.join(dir, "SKILL.md"), content, "utf-8");
13156
14311
  skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
13157
14312
  }
13158
14313
  return { skills, outputDir };
@@ -13332,17 +14487,17 @@ var BLOCK_START = "<!-- code-intel:start -->";
13332
14487
  var BLOCK_END = "<!-- code-intel:end -->";
13333
14488
  function writeContextFiles(workspaceRoot, projectName, stats, skills) {
13334
14489
  const block = buildBlock(projectName, stats, skills);
13335
- upsertFile(path38.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
13336
- upsertFile(path38.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
13337
- const githubDir = path38.join(workspaceRoot, ".github");
13338
- if (!fs37.existsSync(githubDir)) fs37.mkdirSync(githubDir, { recursive: true });
13339
- upsertFile(path38.join(githubDir, "copilot-instructions.md"), block, "copilot-instructions.md");
13340
- const cursorDir = path38.join(workspaceRoot, ".cursor", "rules");
13341
- if (!fs37.existsSync(cursorDir)) fs37.mkdirSync(cursorDir, { recursive: true });
13342
- upsertFile(path38.join(cursorDir, "code-intel.mdc"), block, "code-intel.mdc");
13343
- const kiroDir = path38.join(workspaceRoot, ".kiro", "steering");
13344
- if (!fs37.existsSync(kiroDir)) fs37.mkdirSync(kiroDir, { recursive: true });
13345
- upsertFile(path38.join(kiroDir, "code-intel.md"), block, "code-intel.md");
14490
+ upsertFile(path39.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
14491
+ upsertFile(path39.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
14492
+ const githubDir = path39.join(workspaceRoot, ".github");
14493
+ if (!fs38.existsSync(githubDir)) fs38.mkdirSync(githubDir, { recursive: true });
14494
+ upsertFile(path39.join(githubDir, "copilot-instructions.md"), block, "copilot-instructions.md");
14495
+ const cursorDir = path39.join(workspaceRoot, ".cursor", "rules");
14496
+ if (!fs38.existsSync(cursorDir)) fs38.mkdirSync(cursorDir, { recursive: true });
14497
+ upsertFile(path39.join(cursorDir, "code-intel.mdc"), block, "code-intel.mdc");
14498
+ const kiroDir = path39.join(workspaceRoot, ".kiro", "steering");
14499
+ if (!fs38.existsSync(kiroDir)) fs38.mkdirSync(kiroDir, { recursive: true });
14500
+ upsertFile(path39.join(kiroDir, "code-intel.md"), block, "code-intel.md");
13346
14501
  }
13347
14502
  function buildBlock(projectName, stats, skills) {
13348
14503
  const skillTableRows = skills.map(
@@ -13479,7 +14634,7 @@ ${skillTable}
13479
14634
  ${BLOCK_END}`;
13480
14635
  }
13481
14636
  function upsertFile(filePath, block, fileName) {
13482
- if (!fs37.existsSync(filePath)) {
14637
+ if (!fs38.existsSync(filePath)) {
13483
14638
  const newContent = [
13484
14639
  `# ${fileName}`,
13485
14640
  "",
@@ -13490,17 +14645,17 @@ function upsertFile(filePath, block, fileName) {
13490
14645
  "<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
13491
14646
  ""
13492
14647
  ].join("\n");
13493
- fs37.writeFileSync(filePath, newContent, "utf-8");
14648
+ fs38.writeFileSync(filePath, newContent, "utf-8");
13494
14649
  return;
13495
14650
  }
13496
- const existing = fs37.readFileSync(filePath, "utf-8");
14651
+ const existing = fs38.readFileSync(filePath, "utf-8");
13497
14652
  const startIdx = findLineMarker(existing, BLOCK_START);
13498
14653
  const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
13499
14654
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
13500
14655
  const before = existing.slice(0, startIdx);
13501
14656
  const after = existing.slice(endIdx + BLOCK_END.length);
13502
14657
  const updated = (before + block + after).trimEnd() + "\n";
13503
- fs37.writeFileSync(filePath, updated, "utf-8");
14658
+ fs38.writeFileSync(filePath, updated, "utf-8");
13504
14659
  return;
13505
14660
  }
13506
14661
  const appended = [
@@ -13513,7 +14668,7 @@ function upsertFile(filePath, block, fileName) {
13513
14668
  block,
13514
14669
  ""
13515
14670
  ].join("\n");
13516
- fs37.writeFileSync(filePath, appended, "utf-8");
14671
+ fs38.writeFileSync(filePath, appended, "utf-8");
13517
14672
  }
13518
14673
  function findLineMarker(content, marker, startFrom = 0) {
13519
14674
  let idx = content.indexOf(marker, startFrom);
@@ -13555,14 +14710,14 @@ function getChangedFilesSince(workspaceRoot, baseHash) {
13555
14710
  function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
13556
14711
  const changed = [];
13557
14712
  for (const absPath of allFilePaths) {
13558
- const rel = path38.relative(workspaceRoot, absPath);
14713
+ const rel = path39.relative(workspaceRoot, absPath);
13559
14714
  const stored = storedMtimes[rel];
13560
14715
  if (stored === void 0) {
13561
14716
  changed.push(absPath);
13562
14717
  continue;
13563
14718
  }
13564
14719
  try {
13565
- const { mtimeMs } = fs37.statSync(absPath);
14720
+ const { mtimeMs } = fs38.statSync(absPath);
13566
14721
  if (mtimeMs > stored) changed.push(absPath);
13567
14722
  } catch {
13568
14723
  changed.push(absPath);
@@ -13574,8 +14729,8 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
13574
14729
  const snap = {};
13575
14730
  for (const absPath of filePaths) {
13576
14731
  try {
13577
- const { mtimeMs } = fs37.statSync(absPath);
13578
- snap[path38.relative(workspaceRoot, absPath)] = mtimeMs;
14732
+ const { mtimeMs } = fs38.statSync(absPath);
14733
+ snap[path39.relative(workspaceRoot, absPath)] = mtimeMs;
13579
14734
  } catch {
13580
14735
  }
13581
14736
  }
@@ -13586,8 +14741,8 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
13586
14741
  if (prevCommitHash) {
13587
14742
  const changed = getChangedFilesSince(workspaceRoot, prevCommitHash);
13588
14743
  if (changed !== null) {
13589
- const scanSet = new Set(allFilePaths.map((p) => path38.relative(workspaceRoot, p)));
13590
- const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path38.join(workspaceRoot, rel));
14744
+ const scanSet = new Set(allFilePaths.map((p) => path39.relative(workspaceRoot, p)));
14745
+ const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path39.join(workspaceRoot, rel));
13591
14746
  if (total > 0 && changedInScan.length / total > 0.2) {
13592
14747
  return { incremental: false, fallbackReason: `changed files (${changedInScan.length}) > 20% of total (${total})` };
13593
14748
  }
@@ -13614,11 +14769,11 @@ init_group_sync();
13614
14769
  function expandGlob(root, pattern) {
13615
14770
  const parts = pattern.replace(/\/\*\*?$/, "").split("/").filter(Boolean);
13616
14771
  if (parts.length === 0) return [];
13617
- const dir = path38.join(root, ...parts);
13618
- if (!fs37.existsSync(dir)) return [];
13619
- return fs37.readdirSync(dir).map((entry) => path38.join(dir, entry)).filter((p) => {
14772
+ const dir = path39.join(root, ...parts);
14773
+ if (!fs38.existsSync(dir)) return [];
14774
+ return fs38.readdirSync(dir).map((entry) => path39.join(dir, entry)).filter((p) => {
13620
14775
  try {
13621
- return fs37.statSync(p).isDirectory();
14776
+ return fs38.statSync(p).isDirectory();
13622
14777
  } catch {
13623
14778
  return false;
13624
14779
  }
@@ -13629,11 +14784,11 @@ function resolvePackages(root, patterns) {
13629
14784
  for (const pattern of patterns) {
13630
14785
  const dirs = expandGlob(root, pattern);
13631
14786
  for (const dir of dirs) {
13632
- const pkgJsonPath = path38.join(dir, "package.json");
13633
- if (!fs37.existsSync(pkgJsonPath)) continue;
14787
+ const pkgJsonPath = path39.join(dir, "package.json");
14788
+ if (!fs38.existsSync(pkgJsonPath)) continue;
13634
14789
  try {
13635
- const pkgJson = JSON.parse(fs37.readFileSync(pkgJsonPath, "utf-8"));
13636
- const name = pkgJson.name ?? path38.basename(dir);
14790
+ const pkgJson = JSON.parse(fs38.readFileSync(pkgJsonPath, "utf-8"));
14791
+ const name = pkgJson.name ?? path39.basename(dir);
13637
14792
  packages.push({ name, path: dir });
13638
14793
  } catch {
13639
14794
  }
@@ -13642,13 +14797,13 @@ function resolvePackages(root, patterns) {
13642
14797
  return packages;
13643
14798
  }
13644
14799
  async function detectWorkspace(root) {
13645
- const turboJsonPath = path38.join(root, "turbo.json");
13646
- if (fs37.existsSync(turboJsonPath)) {
14800
+ const turboJsonPath = path39.join(root, "turbo.json");
14801
+ if (fs38.existsSync(turboJsonPath)) {
13647
14802
  let patterns = [];
13648
- const pkgJsonPath = path38.join(root, "package.json");
13649
- if (fs37.existsSync(pkgJsonPath)) {
14803
+ const pkgJsonPath = path39.join(root, "package.json");
14804
+ if (fs38.existsSync(pkgJsonPath)) {
13650
14805
  try {
13651
- const pkgJson = JSON.parse(fs37.readFileSync(pkgJsonPath, "utf-8"));
14806
+ const pkgJson = JSON.parse(fs38.readFileSync(pkgJsonPath, "utf-8"));
13652
14807
  if (pkgJson.workspaces) {
13653
14808
  patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
13654
14809
  }
@@ -13656,16 +14811,16 @@ async function detectWorkspace(root) {
13656
14811
  }
13657
14812
  }
13658
14813
  if (patterns.length === 0) {
13659
- const fallbackDir = path38.join(root, "packages");
13660
- if (fs37.existsSync(fallbackDir)) patterns = ["packages/*"];
14814
+ const fallbackDir = path39.join(root, "packages");
14815
+ if (fs38.existsSync(fallbackDir)) patterns = ["packages/*"];
13661
14816
  }
13662
14817
  const packages = resolvePackages(root, patterns);
13663
14818
  return { type: "turborepo", root, packages };
13664
14819
  }
13665
- const npmPkgJsonPath = path38.join(root, "package.json");
13666
- if (fs37.existsSync(npmPkgJsonPath)) {
14820
+ const npmPkgJsonPath = path39.join(root, "package.json");
14821
+ if (fs38.existsSync(npmPkgJsonPath)) {
13667
14822
  try {
13668
- const pkgJson = JSON.parse(fs37.readFileSync(npmPkgJsonPath, "utf-8"));
14823
+ const pkgJson = JSON.parse(fs38.readFileSync(npmPkgJsonPath, "utf-8"));
13669
14824
  if (pkgJson.workspaces) {
13670
14825
  const patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
13671
14826
  const packages = resolvePackages(root, patterns);
@@ -13674,11 +14829,11 @@ async function detectWorkspace(root) {
13674
14829
  } catch {
13675
14830
  }
13676
14831
  }
13677
- const pnpmYamlPath = path38.join(root, "pnpm-workspace.yaml");
13678
- if (fs37.existsSync(pnpmYamlPath)) {
14832
+ const pnpmYamlPath = path39.join(root, "pnpm-workspace.yaml");
14833
+ if (fs38.existsSync(pnpmYamlPath)) {
13679
14834
  const patterns = [];
13680
14835
  try {
13681
- const content = fs37.readFileSync(pnpmYamlPath, "utf-8");
14836
+ const content = fs38.readFileSync(pnpmYamlPath, "utf-8");
13682
14837
  let inPackages = false;
13683
14838
  for (const line of content.split("\n")) {
13684
14839
  if (/^packages\s*:/.test(line)) {
@@ -13698,30 +14853,30 @@ async function detectWorkspace(root) {
13698
14853
  const packages = resolvePackages(root, patterns);
13699
14854
  return { type: "pnpm", root, packages };
13700
14855
  }
13701
- const nxJsonPath = path38.join(root, "nx.json");
13702
- if (fs37.existsSync(nxJsonPath)) {
14856
+ const nxJsonPath = path39.join(root, "nx.json");
14857
+ if (fs38.existsSync(nxJsonPath)) {
13703
14858
  const packages = [];
13704
14859
  const scanForProjects = (dir, depth) => {
13705
14860
  if (depth > 2) return;
13706
14861
  let entries;
13707
14862
  try {
13708
- entries = fs37.readdirSync(dir);
14863
+ entries = fs38.readdirSync(dir);
13709
14864
  } catch {
13710
14865
  return;
13711
14866
  }
13712
14867
  for (const entry of entries) {
13713
14868
  if (entry === "node_modules" || entry.startsWith(".")) continue;
13714
- const fullPath = path38.join(dir, entry);
14869
+ const fullPath = path39.join(dir, entry);
13715
14870
  try {
13716
- if (!fs37.statSync(fullPath).isDirectory()) continue;
14871
+ if (!fs38.statSync(fullPath).isDirectory()) continue;
13717
14872
  } catch {
13718
14873
  continue;
13719
14874
  }
13720
- const projectJsonPath = path38.join(fullPath, "project.json");
13721
- if (fs37.existsSync(projectJsonPath)) {
14875
+ const projectJsonPath = path39.join(fullPath, "project.json");
14876
+ if (fs38.existsSync(projectJsonPath)) {
13722
14877
  try {
13723
- const proj = JSON.parse(fs37.readFileSync(projectJsonPath, "utf-8"));
13724
- const name = proj.name ?? path38.basename(fullPath);
14878
+ const proj = JSON.parse(fs38.readFileSync(projectJsonPath, "utf-8"));
14879
+ const name = proj.name ?? path39.basename(fullPath);
13725
14880
  packages.push({ name, path: fullPath });
13726
14881
  } catch {
13727
14882
  }
@@ -13829,17 +14984,17 @@ var MigrationRunner = class {
13829
14984
  autoBackupBeforeMigration() {
13830
14985
  try {
13831
14986
  const dbFile = this.db.name;
13832
- if (!dbFile || !fs37.existsSync(dbFile)) return;
13833
- const backupDir = path38.join(os13.homedir(), ".code-intel", "backups", "pre-migration");
13834
- fs37.mkdirSync(backupDir, { recursive: true });
14987
+ if (!dbFile || !fs38.existsSync(dbFile)) return;
14988
+ const backupDir = path39.join(os13.homedir(), ".code-intel", "backups", "pre-migration");
14989
+ fs38.mkdirSync(backupDir, { recursive: true });
13835
14990
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
13836
- const baseName = path38.basename(dbFile, ".db");
13837
- const backupPath = path38.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
14991
+ const baseName = path39.basename(dbFile, ".db");
14992
+ const backupPath = path39.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
13838
14993
  try {
13839
14994
  this.db.pragma("wal_checkpoint(TRUNCATE)");
13840
14995
  } catch {
13841
14996
  }
13842
- fs37.copyFileSync(dbFile, backupPath);
14997
+ fs38.copyFileSync(dbFile, backupPath);
13843
14998
  } catch {
13844
14999
  }
13845
15000
  }
@@ -13986,10 +15141,10 @@ init_tracing();
13986
15141
  init_init_wizard();
13987
15142
  init_config_manager();
13988
15143
  init_codes();
13989
- var GLOBAL_DIR3 = path38.join(os13.homedir(), ".code-intel");
15144
+ var GLOBAL_DIR3 = path39.join(os13.homedir(), ".code-intel");
13990
15145
  function loadRepoPaths() {
13991
15146
  try {
13992
- const data = fs37.readFileSync(path38.join(GLOBAL_DIR3, "repos.json"), "utf-8");
15147
+ const data = fs38.readFileSync(path39.join(GLOBAL_DIR3, "repos.json"), "utf-8");
13993
15148
  const repos = JSON.parse(data);
13994
15149
  return repos.map((r) => r.path);
13995
15150
  } catch {
@@ -13997,9 +15152,9 @@ function loadRepoPaths() {
13997
15152
  }
13998
15153
  }
13999
15154
  function loadGroupNames() {
14000
- const groupsDir = path38.join(GLOBAL_DIR3, "groups");
15155
+ const groupsDir = path39.join(GLOBAL_DIR3, "groups");
14001
15156
  try {
14002
- return fs37.readdirSync(groupsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
15157
+ return fs38.readdirSync(groupsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
14003
15158
  } catch {
14004
15159
  return [];
14005
15160
  }
@@ -14260,10 +15415,10 @@ function autoInstallCompletion() {
14260
15415
  }
14261
15416
  console.log(` Detected shell: ${shell}`);
14262
15417
  if (shell === "fish") {
14263
- const dir = path38.join(os13.homedir(), ".config", "fish", "completions");
14264
- const dest = path38.join(dir, "code-intel.fish");
14265
- fs37.mkdirSync(dir, { recursive: true });
14266
- fs37.writeFileSync(dest, fishCompletion(), "utf-8");
15418
+ const dir = path39.join(os13.homedir(), ".config", "fish", "completions");
15419
+ const dest = path39.join(dir, "code-intel.fish");
15420
+ fs38.mkdirSync(dir, { recursive: true });
15421
+ fs38.writeFileSync(dest, fishCompletion(), "utf-8");
14267
15422
  console.log(` \u2705 Fish completion installed \u2192 ${dest}
14268
15423
  `);
14269
15424
  return;
@@ -14273,15 +15428,15 @@ source <(code-intel completion zsh)
14273
15428
  ` : `
14274
15429
  source <(code-intel completion bash)
14275
15430
  `;
14276
- const rcFile = shell === "zsh" ? path38.join(os13.homedir(), ".zshrc") : path38.join(os13.homedir(), ".bashrc");
15431
+ const rcFile = shell === "zsh" ? path39.join(os13.homedir(), ".zshrc") : path39.join(os13.homedir(), ".bashrc");
14277
15432
  try {
14278
- const existing = fs37.existsSync(rcFile) ? fs37.readFileSync(rcFile, "utf-8") : "";
15433
+ const existing = fs38.existsSync(rcFile) ? fs38.readFileSync(rcFile, "utf-8") : "";
14279
15434
  if (existing.includes("code-intel completion")) {
14280
15435
  console.log(` \u2139 Completion already configured in ${rcFile}
14281
15436
  `);
14282
15437
  return;
14283
15438
  }
14284
- fs37.appendFileSync(rcFile, script, "utf-8");
15439
+ fs38.appendFileSync(rcFile, script, "utf-8");
14285
15440
  console.log(` \u2705 ${shell} completion added to ${rcFile}`);
14286
15441
  console.log(` Restart your shell or run: source ${rcFile}
14287
15442
  `);
@@ -14300,20 +15455,20 @@ function generateCompletion(shell) {
14300
15455
  return fishCompletion();
14301
15456
  }
14302
15457
  }
14303
- var GLOBAL_DIR4 = path38.join(os13.homedir(), ".code-intel");
14304
- var META_PATH = path38.join(GLOBAL_DIR4, "update-meta.json");
15458
+ var GLOBAL_DIR4 = path39.join(os13.homedir(), ".code-intel");
15459
+ var META_PATH = path39.join(GLOBAL_DIR4, "update-meta.json");
14305
15460
  var PACKAGE_NAME = "code-intel";
14306
15461
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
14307
15462
  function loadMeta() {
14308
15463
  try {
14309
- return JSON.parse(fs37.readFileSync(META_PATH, "utf-8"));
15464
+ return JSON.parse(fs38.readFileSync(META_PATH, "utf-8"));
14310
15465
  } catch {
14311
15466
  return null;
14312
15467
  }
14313
15468
  }
14314
15469
  function saveMeta(meta) {
14315
- fs37.mkdirSync(GLOBAL_DIR4, { recursive: true });
14316
- fs37.writeFileSync(META_PATH, JSON.stringify(meta, null, 2) + "\n", "utf-8");
15470
+ fs38.mkdirSync(GLOBAL_DIR4, { recursive: true });
15471
+ fs38.writeFileSync(META_PATH, JSON.stringify(meta, null, 2) + "\n", "utf-8");
14317
15472
  }
14318
15473
  function isNewer(current, candidate) {
14319
15474
  const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
@@ -14534,7 +15689,17 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
14534
15689
  Docs: https://github.com/vohongtho/code-intel-platform
14535
15690
  `);
14536
15691
  async function analyzeWorkspace(targetPath, options) {
14537
- const workspaceRoot = path38.resolve(targetPath);
15692
+ const workspaceRoot = path39.resolve(targetPath);
15693
+ if (!fs38.existsSync(workspaceRoot)) {
15694
+ logger_default.error(`Path does not exist: ${workspaceRoot}`);
15695
+ console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
15696
+ process.exit(1);
15697
+ }
15698
+ if (!fs38.statSync(workspaceRoot).isDirectory()) {
15699
+ logger_default.error(`Path is not a directory: ${workspaceRoot}`);
15700
+ console.error(` \u2717 Path is not a directory: ${workspaceRoot}`);
15701
+ process.exit(1);
15702
+ }
14538
15703
  if (!options?.silent) console.log(`Analyzing: ${workspaceRoot}`);
14539
15704
  logger_default.info(`analyze started: ${workspaceRoot}`);
14540
15705
  if (options?.force) {
@@ -14555,18 +15720,19 @@ async function analyzeWorkspace(targetPath, options) {
14555
15720
  ];
14556
15721
  for (const f of wipeFiles) {
14557
15722
  try {
14558
- if (fs37.existsSync(f)) fs37.unlinkSync(f);
15723
+ if (fs38.existsSync(f)) fs38.unlinkSync(f);
14559
15724
  } catch {
14560
15725
  }
14561
15726
  }
14562
15727
  }
14563
15728
  if (!options?.skipGit) {
14564
- const gitDir = path38.join(workspaceRoot, ".git");
14565
- if (!fs37.existsSync(gitDir)) {
15729
+ const gitDir = path39.join(workspaceRoot, ".git");
15730
+ if (!fs38.existsSync(gitDir)) {
14566
15731
  logger_default.warn(`${workspaceRoot} is not a Git repository`);
14567
15732
  }
14568
15733
  }
14569
15734
  const graph = createKnowledgeGraph();
15735
+ let activeGraph = graph;
14570
15736
  const BAR_WIDTH = 30;
14571
15737
  let currentPhase = "";
14572
15738
  function renderBar(phase, done, total) {
@@ -14597,12 +15763,22 @@ async function analyzeWorkspace(targetPath, options) {
14597
15763
  }
14598
15764
  process.stdout.write("\r" + " ".repeat(60) + "\r");
14599
15765
  }
15766
+ const maxMemoryMB = options?.maxMemoryMB ?? (() => {
15767
+ const v = parseInt(process.env["GRAPH_MAX_MEMORY_MB"] ?? "", 10);
15768
+ return Number.isFinite(v) && v >= 1 ? v : 0;
15769
+ })();
15770
+ if (maxMemoryMB > 0) {
15771
+ const { createCompactKnowledgeGraph: createCompactKnowledgeGraph2 } = await Promise.resolve().then(() => (init_compact_knowledge_graph(), compact_knowledge_graph_exports));
15772
+ activeGraph = createCompactKnowledgeGraph2(maxMemoryMB);
15773
+ logger_default.info(` [analyze] Using CompactKnowledgeGraph with ${maxMemoryMB} MB memory limit`);
15774
+ }
14600
15775
  const context2 = {
14601
15776
  workspaceRoot,
14602
- graph,
15777
+ graph: activeGraph,
14603
15778
  filePaths: [],
14604
15779
  verbose: options?.verbose,
14605
15780
  summarize: options?.summarize,
15781
+ profile: options?.profile,
14606
15782
  llmConfig: options?.summarize ? {
14607
15783
  provider: options.llmProvider ?? "ollama",
14608
15784
  model: options.llmModel,
@@ -14671,19 +15847,64 @@ async function analyzeWorkspace(targetPath, options) {
14671
15847
  };
14672
15848
  const phases = isIncremental ? [noopScanPhase, structurePhase, chosenParsePhase, chosenResolvePhase, clusterPhase, flowPhase, summarizePhase] : [scanPhase, structurePhase, chosenParsePhase, chosenResolvePhase, clusterPhase, flowPhase, summarizePhase];
14673
15849
  const result = await runPipeline(phases, context2);
15850
+ if (options?.profile) {
15851
+ const profileEntries = [];
15852
+ for (const [phaseName, pr] of result.results) {
15853
+ const entry = {
15854
+ phase: phaseName,
15855
+ duration: pr.duration,
15856
+ memoryBeforeMB: pr.memoryBeforeMB,
15857
+ memoryAfterMB: pr.memoryAfterMB,
15858
+ memoryDeltaMB: pr.memoryBeforeMB !== void 0 && pr.memoryAfterMB !== void 0 ? pr.memoryAfterMB - pr.memoryBeforeMB : void 0
15859
+ };
15860
+ profileEntries.push(entry);
15861
+ }
15862
+ const profileJson = {
15863
+ profiledAt: (/* @__PURE__ */ new Date()).toISOString(),
15864
+ totalDuration: result.totalDuration,
15865
+ phases: profileEntries
15866
+ };
15867
+ const profilePath = path39.join(workspaceRoot, ".code-intel", "profile.json");
15868
+ try {
15869
+ fs38.mkdirSync(path39.join(workspaceRoot, ".code-intel"), { recursive: true });
15870
+ fs38.writeFileSync(profilePath, JSON.stringify(profileJson, null, 2));
15871
+ if (!options?.silent) console.log(` \u2713 Profile written: ${profilePath}`);
15872
+ } catch (err) {
15873
+ logger_default.warn(`Failed to write profile.json: ${err instanceof Error ? err.message : err}`);
15874
+ }
15875
+ for (const entry of profileEntries) {
15876
+ if (result.totalDuration > 0 && entry.duration / result.totalDuration > 0.5) {
15877
+ logger_default.warn(`[profile] Bottleneck detected: phase '${entry.phase}' took ${entry.duration}ms (${(entry.duration / result.totalDuration * 100).toFixed(0)}% of total ${result.totalDuration}ms)`);
15878
+ if (!options?.silent) {
15879
+ console.warn(` \u26A0 Bottleneck: '${entry.phase}' took ${(entry.duration / result.totalDuration * 100).toFixed(0)}% of total time (${entry.duration}ms)`);
15880
+ }
15881
+ }
15882
+ }
15883
+ }
15884
+ if (options?.verbose && !options?.silent) {
15885
+ console.log("\n Phase timing:");
15886
+ const nameW = 12;
15887
+ const durW = 8;
15888
+ for (const [phaseName, pr] of result.results) {
15889
+ const durStr = pr.duration >= 1e3 ? `${(pr.duration / 1e3).toFixed(2)}s` : `${pr.duration}ms`;
15890
+ const memStr = pr.memoryBeforeMB !== void 0 && pr.memoryAfterMB !== void 0 ? ` mem: ${pr.memoryBeforeMB}\u2192${pr.memoryAfterMB} MB` : "";
15891
+ console.log(` ${phaseName.padEnd(nameW)} ${durStr.padStart(durW)}${memStr}`);
15892
+ }
15893
+ console.log("");
15894
+ }
14674
15895
  if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
14675
15896
  const dbPath = getDbPath(workspaceRoot);
14676
- if (fs37.existsSync(dbPath)) {
15897
+ if (fs38.existsSync(dbPath)) {
14677
15898
  try {
14678
15899
  const db = new DbManager(dbPath);
14679
15900
  await db.init();
14680
15901
  for (const absPath of incrementalChangedFiles) {
14681
- const rel = path38.relative(workspaceRoot, absPath);
15902
+ const rel = path39.relative(workspaceRoot, absPath);
14682
15903
  await removeNodesForFile(rel, db);
14683
15904
  }
14684
15905
  const { upsertNodes: upsertNodesBatch } = await Promise.resolve().then(() => (init_graph_loader(), graph_loader_exports));
14685
15906
  const changedRelPaths = new Set(
14686
- incrementalChangedFiles.map((f) => path38.relative(workspaceRoot, f))
15907
+ incrementalChangedFiles.map((f) => path39.relative(workspaceRoot, f))
14687
15908
  );
14688
15909
  const nodesToUpsert = [...graph.allNodes()].filter(
14689
15910
  (n) => changedRelPaths.has(n.filePath)
@@ -14708,7 +15929,7 @@ async function analyzeWorkspace(targetPath, options) {
14708
15929
  mergedMtimes = newMtimes;
14709
15930
  }
14710
15931
  const currentCommitHash = getCurrentCommitHash(workspaceRoot) ?? void 0;
14711
- const repoName = path38.basename(workspaceRoot);
15932
+ const repoName = path39.basename(workspaceRoot);
14712
15933
  const indexVersion = v4();
14713
15934
  saveMetadata(workspaceRoot, {
14714
15935
  indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -14746,7 +15967,7 @@ async function analyzeWorkspace(targetPath, options) {
14746
15967
  ];
14747
15968
  for (const f of newStaleFiles) {
14748
15969
  try {
14749
- if (fs37.existsSync(f)) fs37.unlinkSync(f);
15970
+ if (fs38.existsSync(f)) fs38.unlinkSync(f);
14750
15971
  } catch {
14751
15972
  }
14752
15973
  }
@@ -14763,21 +15984,21 @@ async function analyzeWorkspace(targetPath, options) {
14763
15984
  ];
14764
15985
  for (const f of staleFiles) {
14765
15986
  try {
14766
- if (fs37.existsSync(f)) fs37.unlinkSync(f);
15987
+ if (fs38.existsSync(f)) fs38.unlinkSync(f);
14767
15988
  } catch {
14768
15989
  }
14769
15990
  }
14770
15991
  for (const f of newStaleFiles) {
14771
15992
  if (f === dbPathNew) continue;
14772
- if (fs37.existsSync(f)) {
15993
+ if (fs38.existsSync(f)) {
14773
15994
  const dest = f.replace(dbPathNew, dbPath);
14774
15995
  try {
14775
- fs37.renameSync(f, dest);
15996
+ fs38.renameSync(f, dest);
14776
15997
  } catch {
14777
15998
  }
14778
15999
  }
14779
16000
  }
14780
- fs37.renameSync(dbPathNew, dbPath);
16001
+ fs38.renameSync(dbPathNew, dbPath);
14781
16002
  stopSpinner();
14782
16003
  logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
14783
16004
  if (!options?.silent) {
@@ -14787,6 +16008,17 @@ async function analyzeWorkspace(targetPath, options) {
14787
16008
  stopSpinner();
14788
16009
  logger_default.warn(`DB persist failed: ${err instanceof Error ? err.message : err}`);
14789
16010
  }
16011
+ startSpinner("Building BM25 inverted index");
16012
+ try {
16013
+ const { Bm25Index: Bm25Index2, getBm25DbPath: getBm25DbPath2 } = await Promise.resolve().then(() => (init_bm25_index(), bm25_index_exports));
16014
+ const bm25 = new Bm25Index2(getBm25DbPath2(workspaceRoot));
16015
+ bm25.build(graph);
16016
+ stopSpinner();
16017
+ if (!options?.silent) console.log(` \u2713 BM25 index built`);
16018
+ } catch (err) {
16019
+ stopSpinner();
16020
+ logger_default.warn(`BM25 index build failed: ${err instanceof Error ? err.message : err}`);
16021
+ }
14790
16022
  const doEmbeddings = options?.embeddings && !options?.skipEmbeddings;
14791
16023
  if (doEmbeddings) {
14792
16024
  startSpinner("Building vector embeddings");
@@ -14798,7 +16030,7 @@ async function analyzeWorkspace(targetPath, options) {
14798
16030
  const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
14799
16031
  for (const f of staleVdb) {
14800
16032
  try {
14801
- if (fs37.existsSync(f)) fs37.unlinkSync(f);
16033
+ if (fs38.existsSync(f)) fs38.unlinkSync(f);
14802
16034
  } catch {
14803
16035
  }
14804
16036
  }
@@ -14894,7 +16126,7 @@ async function analyzeWorkspace(targetPath, options) {
14894
16126
  }
14895
16127
  }
14896
16128
  }
14897
- return { graph, result, repoName, workspaceRoot };
16129
+ return { graph: activeGraph, result, repoName, workspaceRoot };
14898
16130
  }
14899
16131
  program.command("init").description("Interactive setup wizard \u2014 creates ~/.code-intel/config.json").option("--reset", "Wipe existing config and re-run the wizard").option("--yes", "Non-interactive: accept all defaults (skips prompts)").addHelpText("after", `
14900
16132
  Walks you through 5 setup steps:
@@ -15015,8 +16247,8 @@ program.command("setup").description("Configure MCP server for your editors (one
15015
16247
  const configFile = `${configDir}/claude_desktop_config.json`;
15016
16248
  try {
15017
16249
  let existing = {};
15018
- if (fs37.existsSync(configFile)) {
15019
- existing = JSON.parse(fs37.readFileSync(configFile, "utf-8"));
16250
+ if (fs38.existsSync(configFile)) {
16251
+ existing = JSON.parse(fs38.readFileSync(configFile, "utf-8"));
15020
16252
  }
15021
16253
  const merged = {
15022
16254
  ...existing,
@@ -15025,8 +16257,8 @@ program.command("setup").description("Configure MCP server for your editors (one
15025
16257
  ...mcpConfig.mcpServers
15026
16258
  }
15027
16259
  };
15028
- fs37.mkdirSync(configDir, { recursive: true });
15029
- fs37.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
16260
+ fs38.mkdirSync(configDir, { recursive: true });
16261
+ fs38.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
15030
16262
  console.log(`
15031
16263
  \u2705 Written to ${configFile}`);
15032
16264
  } catch (err) {
@@ -15040,7 +16272,7 @@ program.command("setup").description("Configure MCP server for your editors (one
15040
16272
  console.log('\n To verify in VS Code: open Command Palette \u2192 "MCP: List Servers" and confirm code-intel is Running.');
15041
16273
  console.log("\n Next: run `code-intel analyze` inside your project to build the knowledge graph.\n");
15042
16274
  });
15043
- program.command("analyze").description("Index a repository and build the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--force", "Force full re-index, ignoring cached data").option("--incremental", "Only re-parse files changed since last analysis (git diff or mtime)").option("--parallel", "Use worker threads for parse + resolve phases (faster on multi-core)").option("--skills", "Generate .claude/skills/ SKILL.md files from detected clusters").option("--embeddings", "Build vector embeddings for semantic search (slower, recommended)").option("--skip-embeddings", "Skip embedding generation (faster, text-search only)").option("--skip-agents-md", "Preserve any custom edits inside AGENTS.md / CLAUDE.md").option("--skip-git", "Allow indexing directories that are not Git repositories").option("--verbose", "Log every file skipped due to missing parser support").option("--summarize", "Generate AI summaries for function/class/method/interface nodes (opt-in)").option("--llm-provider <provider>", "LLM provider for --summarize: openai | anthropic | ollama (default: ollama)").option("--llm-model <model>", "LLM model name (e.g. gpt-4o-mini, claude-haiku-4-5, llama3)").option("--llm-batch-size <n>", "Concurrent LLM calls per batch (default: 20)", "20").option("--llm-max-nodes <n>", "Max nodes to summarize per run (cost guard)").option("--no-group-sync", "Skip automatic group sync after analysis").option("--dry-run", "Preview files that would be scanned + estimated time; no DB write").addHelpText("after", `
16275
+ program.command("analyze").description("Index a repository and build the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--force", "Force full re-index, ignoring cached data").option("--incremental", "Only re-parse files changed since last analysis (git diff or mtime)").option("--parallel", "Use worker threads for parse + resolve phases (faster on multi-core)").option("--skills", "Generate .claude/skills/ SKILL.md files from detected clusters").option("--embeddings", "Build vector embeddings for semantic search (slower, recommended)").option("--skip-embeddings", "Skip embedding generation (faster, text-search only)").option("--skip-agents-md", "Preserve any custom edits inside AGENTS.md / CLAUDE.md").option("--skip-git", "Allow indexing directories that are not Git repositories").option("--verbose", "Log every file skipped due to missing parser support").option("--summarize", "Generate AI summaries for function/class/method/interface nodes (opt-in)").option("--llm-provider <provider>", "LLM provider for --summarize: openai | anthropic | ollama (default: ollama)").option("--llm-model <model>", "LLM model name (e.g. gpt-4o-mini, claude-haiku-4-5, llama3)").option("--llm-batch-size <n>", "Concurrent LLM calls per batch (default: 20)", "20").option("--llm-max-nodes <n>", "Max nodes to summarize per run (cost guard)").option("--no-group-sync", "Skip automatic group sync after analysis").option("--dry-run", "Preview files that would be scanned + estimated time; no DB write").option("--max-memory <MB>", "Limit graph memory (MB); spill node content to free RAM when exceeded").option("--profile", "Write per-phase profiling data to .code-intel/profile.json").addHelpText("after", `
15044
16276
  Parses your source code with tree-sitter, builds a Knowledge Graph of
15045
16277
  symbols and their relationships, persists it to .code-intel/graph.db,
15046
16278
  and auto-generates AGENTS.md + CLAUDE.md context blocks.
@@ -15063,7 +16295,7 @@ program.command("analyze").description("Index a repository and build the knowled
15063
16295
  $ code-intel analyze --dry-run Preview files that would be scanned
15064
16296
  `).action(async (targetPath, opts) => {
15065
16297
  if (opts.dryRun) {
15066
- const workspaceRoot = path38.resolve(targetPath);
16298
+ const workspaceRoot = path39.resolve(targetPath);
15067
16299
  const { scanPhase: sp } = await Promise.resolve().then(() => (init_phases(), phases_exports));
15068
16300
  const graph = createKnowledgeGraph();
15069
16301
  const context2 = {
@@ -15105,7 +16337,12 @@ program.command("analyze").description("Index a repository and build the knowled
15105
16337
  llmMaxNodes: (() => {
15106
16338
  const v = parseInt(opts.llmMaxNodes ?? "", 10);
15107
16339
  return Number.isFinite(v) && v >= 1 ? v : void 0;
15108
- })()
16340
+ })(),
16341
+ maxMemoryMB: (() => {
16342
+ const v = parseInt(opts.maxMemory ?? "", 10);
16343
+ return Number.isFinite(v) && v >= 1 ? v : void 0;
16344
+ })(),
16345
+ profile: opts.profile
15109
16346
  });
15110
16347
  process.exit(0);
15111
16348
  });
@@ -15120,13 +16357,17 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
15120
16357
  $ code-intel mcp
15121
16358
  $ code-intel mcp ./my-project
15122
16359
  `).action(async (targetPath) => {
15123
- const workspaceRoot = path38.resolve(targetPath);
15124
- const repoName = path38.basename(workspaceRoot);
16360
+ const workspaceRoot = path39.resolve(targetPath);
16361
+ const repoName = path39.basename(workspaceRoot);
15125
16362
  const dbPath = getDbPath(workspaceRoot);
15126
- const existingIndex = fs37.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16363
+ if (!fs38.existsSync(workspaceRoot) || !fs38.statSync(workspaceRoot).isDirectory()) {
16364
+ console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16365
+ process.exit(1);
16366
+ }
16367
+ const existingIndex = fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
15127
16368
  if (existingIndex) {
15128
16369
  const graph = createKnowledgeGraph();
15129
- const db = new DbManager(dbPath);
16370
+ const db = new DbManager(dbPath, true);
15130
16371
  await db.init();
15131
16372
  await loadGraphFromDB(graph, db);
15132
16373
  db.close();
@@ -15154,30 +16395,35 @@ program.command("serve").description("Start the local HTTP server + web UI for g
15154
16395
  $ code-intel serve --port 8080
15155
16396
  $ code-intel serve --force
15156
16397
  `).action(async (targetPath, options) => {
15157
- const workspaceRoot = path38.resolve(targetPath);
15158
- const repoName = path38.basename(workspaceRoot);
16398
+ const workspaceRoot = path39.resolve(targetPath);
16399
+ const repoName = path39.basename(workspaceRoot);
15159
16400
  const dbPath = getDbPath(workspaceRoot);
15160
- const existingIndex = !options.force && fs37.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16401
+ if (!fs38.existsSync(workspaceRoot) || !fs38.statSync(workspaceRoot).isDirectory()) {
16402
+ console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16403
+ process.exit(1);
16404
+ }
16405
+ const existingIndex = !options.force && fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
15161
16406
  if (existingIndex) {
15162
16407
  const meta = loadMetadata(workspaceRoot);
15163
16408
  if (meta.parser === "regex" || meta.parser === void 0) {
15164
- logger_default.warn(` [serve] Index was built with regex parser \u2014 running full re-analysis to upgrade to tree-sitter`);
15165
- console.log(`Re-analyzing with tree-sitter parser: ${workspaceRoot}`);
15166
- const { graph: newGraph, workspaceRoot: root, repoName: name } = await analyzeWorkspace(targetPath, { force: true });
15167
- await startHttpServer(newGraph, name, parseInt(options.port, 10), root);
16409
+ logger_default.warn(` [serve] Index was built with regex parser. Run \`code-intel analyze\` to upgrade to tree-sitter, then re-run \`code-intel serve\`.`);
16410
+ process.exit(1);
15168
16411
  } else {
15169
- console.log(`Loading index: ${workspaceRoot}`);
16412
+ console.log(`Loading index (lazy): ${workspaceRoot}`);
15170
16413
  console.log(` \u25C8 ${meta.stats.nodes} nodes \xB7 ${meta.stats.edges} edges \xB7 ${meta.stats.files} files (indexed ${meta.indexedAt})`);
15171
- const graph = createKnowledgeGraph();
15172
- const db = new DbManager(dbPath);
16414
+ const lazyGraph = new LazyKnowledgeGraph();
16415
+ const db = new DbManager(dbPath, true);
15173
16416
  await db.init();
15174
- await loadGraphFromDB(graph, db);
15175
- db.close();
15176
- await startHttpServer(graph, repoName, parseInt(options.port, 10), workspaceRoot);
16417
+ await lazyGraph.init(db, meta.stats.nodes, meta.stats.edges);
16418
+ logger_default.info(` [serve] Lazy graph ready \u2014 ${lazyGraph.size.edges} edges loaded; nodes fetched on demand`);
16419
+ setImmediate(() => lazyGraph.warmTopNodes(500).catch(() => {
16420
+ }));
16421
+ await startHttpServer(lazyGraph, repoName, parseInt(options.port, 10), workspaceRoot);
15177
16422
  }
15178
16423
  } else {
15179
- const { graph, workspaceRoot: root, repoName: name } = await analyzeWorkspace(targetPath, { force: options.force });
15180
- await startHttpServer(graph, name, parseInt(options.port, 10), root);
16424
+ logger_default.warn(` [serve] No index found for: ${workspaceRoot}`);
16425
+ logger_default.warn(` [serve] Run \`code-intel analyze\` first, then re-run \`code-intel serve\`.`);
16426
+ process.exit(1);
15181
16427
  }
15182
16428
  });
15183
16429
  program.command("watch").description("Start HTTP server + file watcher (auto-reindex on file changes)").argument("[path]", "Path to watch (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").addHelpText("after", `
@@ -15191,10 +16437,14 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
15191
16437
  `).action(async (targetPath, options) => {
15192
16438
  const { FileWatcher: FileWatcher2 } = await Promise.resolve().then(() => (init_file_watcher(), file_watcher_exports));
15193
16439
  const { IncrementalIndexer: IncrementalIndexer2 } = await Promise.resolve().then(() => (init_incremental_indexer(), incremental_indexer_exports));
15194
- const workspaceRoot = path38.resolve(targetPath);
15195
- const repoName = path38.basename(workspaceRoot);
16440
+ const workspaceRoot = path39.resolve(targetPath);
16441
+ const repoName = path39.basename(workspaceRoot);
15196
16442
  const dbPath = getDbPath(workspaceRoot);
15197
- const existingIndex = !options.force && fs37.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16443
+ if (!fs38.existsSync(workspaceRoot) || !fs38.statSync(workspaceRoot).isDirectory()) {
16444
+ console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16445
+ process.exit(1);
16446
+ }
16447
+ const existingIndex = !options.force && fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
15198
16448
  let graph;
15199
16449
  if (existingIndex) {
15200
16450
  const meta = loadMetadata(workspaceRoot);
@@ -15202,13 +16452,15 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
15202
16452
  const result = await analyzeWorkspace(targetPath, { force: true });
15203
16453
  graph = result.graph;
15204
16454
  } else {
15205
- graph = createKnowledgeGraph();
15206
- const db = new DbManager(dbPath);
16455
+ const lazyGraph = new LazyKnowledgeGraph();
16456
+ const db = new DbManager(dbPath, true);
15207
16457
  await db.init();
15208
- await loadGraphFromDB(graph, db);
15209
- db.close();
15210
- console.log(`Loading index: ${workspaceRoot}`);
16458
+ await lazyGraph.init(db, meta.stats.nodes, meta.stats.edges);
16459
+ console.log(`Loading index (lazy): ${workspaceRoot}`);
15211
16460
  console.log(` \u25C8 ${meta.stats.nodes} nodes \xB7 ${meta.stats.edges} edges`);
16461
+ setImmediate(() => lazyGraph.warmTopNodes(500).catch(() => {
16462
+ }));
16463
+ graph = lazyGraph;
15212
16464
  }
15213
16465
  } else {
15214
16466
  const result = await analyzeWorkspace(targetPath, { force: options.force });
@@ -15218,25 +16470,32 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
15218
16470
  const { wsServer } = await startHttpServer(graph, repoName, parseInt(options.port, 10), workspaceRoot, watcherState);
15219
16471
  const indexer = new IncrementalIndexer2(graph, workspaceRoot, dbPath);
15220
16472
  const watcher = new FileWatcher2(workspaceRoot);
15221
- watcher.start(async (changedFiles) => {
15222
- watcherState.lastEventAt = Date.now();
15223
- console.log(`
16473
+ function startWatcher() {
16474
+ watcher.start(async (changedFiles) => {
16475
+ watcherState.lastEventAt = Date.now();
16476
+ console.log(`
15224
16477
  \u27F3 Re-indexing ${changedFiles.length} changed file(s)\u2026`);
15225
- const result = await indexer.patchGraph(changedFiles);
15226
- if (wsServer) {
15227
- const meta = loadMetadata(workspaceRoot);
15228
- wsServer.broadcast({
15229
- type: "graph:updated",
15230
- indexVersion: meta?.indexVersion ?? "unknown",
15231
- stats: { nodes: graph.size.nodes, edges: graph.size.edges },
15232
- changedFiles: changedFiles.map((f) => path38.relative(workspaceRoot, f)),
15233
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
15234
- });
15235
- }
15236
- console.log(
15237
- ` \u2705 Patch done: -${result.nodesRemoved} +${result.nodesAdded} nodes \xB7 ${result.duration}ms`
15238
- );
15239
- });
16478
+ try {
16479
+ const result = await indexer.patchGraph(changedFiles);
16480
+ if (wsServer) {
16481
+ const meta = loadMetadata(workspaceRoot);
16482
+ wsServer.broadcast({
16483
+ type: "graph:updated",
16484
+ indexVersion: meta?.indexVersion ?? "unknown",
16485
+ stats: { nodes: graph.size.nodes, edges: graph.size.edges },
16486
+ changedFiles: changedFiles.map((f) => path39.relative(workspaceRoot, f)),
16487
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16488
+ });
16489
+ }
16490
+ console.log(
16491
+ ` \u2705 Patch done: -${result.nodesRemoved} +${result.nodesAdded} nodes \xB7 ${result.duration}ms`
16492
+ );
16493
+ } catch (err) {
16494
+ logger_default.error(`[watcher] patchGraph error: ${err instanceof Error ? err.message : String(err)}`);
16495
+ }
16496
+ });
16497
+ }
16498
+ startWatcher();
15240
16499
  console.log(`
15241
16500
  \u{1F441} Watching: ${workspaceRoot}`);
15242
16501
  console.log(` Web UI: http://localhost:${options.port}`);
@@ -15280,7 +16539,7 @@ program.command("status").description("Show index freshness and statistics for a
15280
16539
  $ code-intel status
15281
16540
  $ code-intel status ./my-project
15282
16541
  `).action((targetPath) => {
15283
- const workspaceRoot = path38.resolve(targetPath);
16542
+ const workspaceRoot = path39.resolve(targetPath);
15284
16543
  const meta = loadMetadata(workspaceRoot);
15285
16544
  if (!meta) {
15286
16545
  console.log(`
@@ -15304,18 +16563,18 @@ function trashDirName(repoPath) {
15304
16563
  return `.code-intel-trash-${date}`;
15305
16564
  }
15306
16565
  function softDeleteCodeIntel(repoPath) {
15307
- const codeIntelDir = path38.join(repoPath, ".code-intel");
15308
- if (!fs37.existsSync(codeIntelDir)) return;
16566
+ const codeIntelDir = path39.join(repoPath, ".code-intel");
16567
+ if (!fs38.existsSync(codeIntelDir)) return;
15309
16568
  const trashName = trashDirName();
15310
- const trashDir = path38.join(repoPath, trashName);
16569
+ const trashDir = path39.join(repoPath, trashName);
15311
16570
  let dest = trashDir;
15312
16571
  let counter = 1;
15313
- while (fs37.existsSync(dest)) {
16572
+ while (fs38.existsSync(dest)) {
15314
16573
  dest = `${trashDir}-${counter++}`;
15315
16574
  }
15316
- fs37.renameSync(codeIntelDir, dest);
15317
- fs37.writeFileSync(
15318
- path38.join(dest, "TRASH_META.json"),
16575
+ fs38.renameSync(codeIntelDir, dest);
16576
+ fs38.writeFileSync(
16577
+ path39.join(dest, "TRASH_META.json"),
15319
16578
  JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
15320
16579
  );
15321
16580
  console.log(` \u2713 Moved to trash: ${dest}`);
@@ -15324,15 +16583,15 @@ function softDeleteCodeIntel(repoPath) {
15324
16583
  function purgeStaleTrashes(repoPath) {
15325
16584
  const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
15326
16585
  try {
15327
- for (const entry of fs37.readdirSync(repoPath)) {
16586
+ for (const entry of fs38.readdirSync(repoPath)) {
15328
16587
  if (!entry.startsWith(".code-intel-trash-")) continue;
15329
- const fullPath = path38.join(repoPath, entry);
15330
- const metaPath = path38.join(fullPath, "TRASH_META.json");
15331
- if (fs37.existsSync(metaPath)) {
16588
+ const fullPath = path39.join(repoPath, entry);
16589
+ const metaPath = path39.join(fullPath, "TRASH_META.json");
16590
+ if (fs38.existsSync(metaPath)) {
15332
16591
  try {
15333
- const meta = JSON.parse(fs37.readFileSync(metaPath, "utf-8"));
16592
+ const meta = JSON.parse(fs38.readFileSync(metaPath, "utf-8"));
15334
16593
  if (new Date(meta.deletedAt).getTime() < cutoff) {
15335
- fs37.rmSync(fullPath, { recursive: true, force: true });
16594
+ fs38.rmSync(fullPath, { recursive: true, force: true });
15336
16595
  console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
15337
16596
  }
15338
16597
  } catch {
@@ -15358,19 +16617,19 @@ program.command("clean").description("Soft-delete the knowledge graph index for
15358
16617
  `).action((targetPath, opts) => {
15359
16618
  if (opts.dryRun) {
15360
16619
  const showDryRun = (repoPath) => {
15361
- const codeIntelDir = path38.join(path38.resolve(repoPath), ".code-intel");
15362
- if (!fs37.existsSync(codeIntelDir)) {
16620
+ const codeIntelDir = path39.join(path39.resolve(repoPath), ".code-intel");
16621
+ if (!fs38.existsSync(codeIntelDir)) {
15363
16622
  console.log(` (no .code-intel/ found at ${repoPath})`);
15364
16623
  return;
15365
16624
  }
15366
16625
  let totalBytes = 0;
15367
16626
  const countDir = (dir) => {
15368
16627
  try {
15369
- for (const entry of fs37.readdirSync(dir, { withFileTypes: true })) {
15370
- const full = path38.join(dir, entry.name);
16628
+ for (const entry of fs38.readdirSync(dir, { withFileTypes: true })) {
16629
+ const full = path39.join(dir, entry.name);
15371
16630
  if (entry.isDirectory()) countDir(full);
15372
16631
  else try {
15373
- totalBytes += fs37.statSync(full).size;
16632
+ totalBytes += fs38.statSync(full).size;
15374
16633
  } catch {
15375
16634
  }
15376
16635
  }
@@ -15393,7 +16652,7 @@ program.command("clean").description("Soft-delete the knowledge graph index for
15393
16652
  for (const r of repos) showDryRun(r.path);
15394
16653
  } else {
15395
16654
  console.log(`
15396
- \u25C8 Dry run \u2014 clean ${path38.resolve(targetPath)}
16655
+ \u25C8 Dry run \u2014 clean ${path39.resolve(targetPath)}
15397
16656
  `);
15398
16657
  showDryRun(targetPath);
15399
16658
  }
@@ -15403,18 +16662,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
15403
16662
  if (opts.listTrash) {
15404
16663
  const repos = loadRegistry();
15405
16664
  const roots = repos.map((r) => r.path);
15406
- if (roots.length === 0) roots.push(path38.resolve("."));
16665
+ if (roots.length === 0) roots.push(path39.resolve("."));
15407
16666
  let found = 0;
15408
16667
  for (const root of roots) {
15409
16668
  try {
15410
- for (const entry of fs37.readdirSync(root)) {
16669
+ for (const entry of fs38.readdirSync(root)) {
15411
16670
  if (!entry.startsWith(".code-intel-trash-")) continue;
15412
- const fullPath = path38.join(root, entry);
15413
- const metaPath = path38.join(fullPath, "TRASH_META.json");
16671
+ const fullPath = path39.join(root, entry);
16672
+ const metaPath = path39.join(fullPath, "TRASH_META.json");
15414
16673
  let deletedAt = "unknown";
15415
- if (fs37.existsSync(metaPath)) {
16674
+ if (fs38.existsSync(metaPath)) {
15416
16675
  try {
15417
- deletedAt = JSON.parse(fs37.readFileSync(metaPath, "utf-8")).deletedAt;
16676
+ deletedAt = JSON.parse(fs38.readFileSync(metaPath, "utf-8")).deletedAt;
15418
16677
  } catch {
15419
16678
  }
15420
16679
  }
@@ -15442,9 +16701,9 @@ program.command("clean").description("Soft-delete the knowledge graph index for
15442
16701
  }
15443
16702
  for (const r of repos) {
15444
16703
  if (opts.purge) {
15445
- const codeIntelDir = path38.join(r.path, ".code-intel");
15446
- if (fs37.existsSync(codeIntelDir)) {
15447
- fs37.rmSync(codeIntelDir, { recursive: true, force: true });
16704
+ const codeIntelDir = path39.join(r.path, ".code-intel");
16705
+ if (fs38.existsSync(codeIntelDir)) {
16706
+ fs38.rmSync(codeIntelDir, { recursive: true, force: true });
15448
16707
  console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
15449
16708
  }
15450
16709
  } else {
@@ -15458,11 +16717,11 @@ program.command("clean").description("Soft-delete the knowledge graph index for
15458
16717
  `);
15459
16718
  return;
15460
16719
  }
15461
- const workspaceRoot = path38.resolve(targetPath);
16720
+ const workspaceRoot = path39.resolve(targetPath);
15462
16721
  if (opts.purge) {
15463
- const codeIntelDir = path38.join(workspaceRoot, ".code-intel");
15464
- if (fs37.existsSync(codeIntelDir)) {
15465
- fs37.rmSync(codeIntelDir, { recursive: true, force: true });
16722
+ const codeIntelDir = path39.join(workspaceRoot, ".code-intel");
16723
+ if (fs38.existsSync(codeIntelDir)) {
16724
+ fs38.rmSync(codeIntelDir, { recursive: true, force: true });
15466
16725
  console.log(`
15467
16726
  \u2713 Hard-deleted ${codeIntelDir}`);
15468
16727
  }
@@ -15474,16 +16733,16 @@ program.command("clean").description("Soft-delete the knowledge graph index for
15474
16733
  console.log(" Index cleaned.\n");
15475
16734
  });
15476
16735
  async function loadOrAnalyzeWorkspace(targetPath) {
15477
- const workspaceRoot = path38.resolve(targetPath);
16736
+ const workspaceRoot = path39.resolve(targetPath);
15478
16737
  const dbPath = getDbPath(workspaceRoot);
15479
- const existingIndex = fs37.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16738
+ const existingIndex = fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
15480
16739
  if (existingIndex) {
15481
16740
  const graph = createKnowledgeGraph();
15482
- const db = new DbManager(dbPath);
16741
+ const db = new DbManager(dbPath, true);
15483
16742
  await db.init();
15484
16743
  await loadGraphFromDB(graph, db);
15485
16744
  db.close();
15486
- return { graph, workspaceRoot, repoName: path38.basename(workspaceRoot) };
16745
+ return { graph, workspaceRoot, repoName: path39.basename(workspaceRoot) };
15487
16746
  }
15488
16747
  return analyzeWorkspace(targetPath, { silent: true });
15489
16748
  }
@@ -15962,9 +17221,9 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
15962
17221
  console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT IN REGISTRY`);
15963
17222
  continue;
15964
17223
  }
15965
- const metaPath = path38.join(regEntry.path, ".code-intel", "meta.json");
17224
+ const metaPath = path39.join(regEntry.path, ".code-intel", "meta.json");
15966
17225
  try {
15967
- const meta = JSON.parse(fs37.readFileSync(metaPath, "utf-8"));
17226
+ const meta = JSON.parse(fs38.readFileSync(metaPath, "utf-8"));
15968
17227
  const indexedAt = meta.indexedAt;
15969
17228
  const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
15970
17229
  const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
@@ -15981,7 +17240,7 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
15981
17240
  }
15982
17241
  });
15983
17242
  groupCmd.command("init-workspace [path]").description("Auto-discover workspace packages and create a group").option("--name <name>", "Group name (default: workspace root dirname)").option("--no-analyze", "Register packages without running analysis").option("--yes", "Skip confirmation prompt").option("--parallel <n>", "Concurrent analyses (default: 2)", "2").action(async (targetPath, opts) => {
15984
- const root = path38.resolve(targetPath ?? ".");
17243
+ const root = path39.resolve(targetPath ?? ".");
15985
17244
  const ws = await detectWorkspace(root);
15986
17245
  if (!ws) {
15987
17246
  console.error(`
@@ -15990,7 +17249,7 @@ groupCmd.command("init-workspace [path]").description("Auto-discover workspace p
15990
17249
  `);
15991
17250
  process.exit(1);
15992
17251
  }
15993
- const groupName = opts.name ?? path38.basename(root);
17252
+ const groupName = opts.name ?? path39.basename(root);
15994
17253
  console.log(`
15995
17254
  \u25C8 Workspace detected: ${ws.type}`);
15996
17255
  console.log(` Group name : ${groupName}`);
@@ -16269,7 +17528,7 @@ backupCmd.command("create [path]").description("Create an encrypted backup of th
16269
17528
  $ code-intel backup create
16270
17529
  $ code-intel backup create ./my-project
16271
17530
  `).action((targetPath = ".") => {
16272
- const repoPath = path38.resolve(targetPath);
17531
+ const repoPath = path39.resolve(targetPath);
16273
17532
  const svc = new BackupService();
16274
17533
  try {
16275
17534
  const entry = svc.createBackup(repoPath);
@@ -16298,7 +17557,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
16298
17557
  Backups (${entries.length}):
16299
17558
  `);
16300
17559
  for (const e of entries) {
16301
- const exists = fs37.existsSync(e.path);
17560
+ const exists = fs38.existsSync(e.path);
16302
17561
  const status = exists ? "\u2713" : "\u2717 (missing)";
16303
17562
  console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
16304
17563
  }
@@ -16311,7 +17570,7 @@ backupCmd.command("restore <id>").description("Restore a backup by ID").option("
16311
17570
  `).action((id, opts) => {
16312
17571
  const svc = new BackupService();
16313
17572
  try {
16314
- const targetPath = opts.target ? path38.resolve(opts.target) : void 0;
17573
+ const targetPath = opts.target ? path39.resolve(opts.target) : void 0;
16315
17574
  svc.restoreBackup(id, targetPath);
16316
17575
  console.log(`
16317
17576
  \u2705 Backup "${id}" restored successfully.
@@ -16330,15 +17589,15 @@ program.command("migrate").description("Manage database schema migrations").opti
16330
17589
  $ code-intel migrate
16331
17590
  $ code-intel migrate --rollback
16332
17591
  `).action((opts) => {
16333
- const dbPath = opts.db ?? path38.join(os13.homedir(), ".code-intel", "users.db");
16334
- if (!fs37.existsSync(dbPath)) {
17592
+ const dbPath = opts.db ?? path39.join(os13.homedir(), ".code-intel", "users.db");
17593
+ if (!fs38.existsSync(dbPath)) {
16335
17594
  console.error(`
16336
17595
  \u2717 Database not found: ${dbPath}
16337
17596
  Run \`code-intel serve\` or \`code-intel user create\` first.
16338
17597
  `);
16339
17598
  process.exit(1);
16340
17599
  }
16341
- const db = new Database(dbPath);
17600
+ const db = new Database2(dbPath);
16342
17601
  db.pragma("journal_mode = WAL");
16343
17602
  const runner = new MigrationRunner(db);
16344
17603
  try {
@@ -16446,15 +17705,15 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
16446
17705
  }
16447
17706
  try {
16448
17707
  const tokens = await pollDeviceFlow3(config, deviceResponse);
16449
- const tokenPath = path38.join(os13.homedir(), ".code-intel", "oidc-token.json");
17708
+ const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
16450
17709
  const tokenData = {
16451
17710
  accessToken: tokens.accessToken,
16452
17711
  refreshToken: tokens.refreshToken,
16453
17712
  server: serverUrl,
16454
17713
  storedAt: (/* @__PURE__ */ new Date()).toISOString()
16455
17714
  };
16456
- fs37.mkdirSync(path38.dirname(tokenPath), { recursive: true });
16457
- fs37.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
17715
+ fs38.mkdirSync(path39.dirname(tokenPath), { recursive: true });
17716
+ fs38.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
16458
17717
  console.log(` \u2705 Authenticated successfully!`);
16459
17718
  console.log(` Token stored at: ${tokenPath}`);
16460
17719
  console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
@@ -16467,13 +17726,13 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
16467
17726
  }
16468
17727
  });
16469
17728
  authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
16470
- const tokenPath = path38.join(os13.homedir(), ".code-intel", "oidc-token.json");
16471
- if (!fs37.existsSync(tokenPath)) {
17729
+ const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
17730
+ if (!fs38.existsSync(tokenPath)) {
16472
17731
  console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
16473
17732
  return;
16474
17733
  }
16475
17734
  try {
16476
- const data = JSON.parse(fs37.readFileSync(tokenPath, "utf-8"));
17735
+ const data = JSON.parse(fs38.readFileSync(tokenPath, "utf-8"));
16477
17736
  console.log(`
16478
17737
  \u2705 OIDC token stored`);
16479
17738
  console.log(` Server : ${data.server ?? "unknown"}`);
@@ -16485,9 +17744,9 @@ authCmd.command("status").description("Show the current OIDC authentication stat
16485
17744
  }
16486
17745
  });
16487
17746
  authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
16488
- const tokenPath = path38.join(os13.homedir(), ".code-intel", "oidc-token.json");
16489
- if (fs37.existsSync(tokenPath)) {
16490
- fs37.unlinkSync(tokenPath);
17747
+ const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
17748
+ if (fs38.existsSync(tokenPath)) {
17749
+ fs38.unlinkSync(tokenPath);
16491
17750
  console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
16492
17751
  } else {
16493
17752
  console.log("\n No stored token found.\n");
@@ -16611,8 +17870,8 @@ program.command("config-validate <file>").description("Validate a JSON config fi
16611
17870
  $ code-intel config-validate ./config.json
16612
17871
  $ code-intel config-validate ~/.code-intel/config.json
16613
17872
  `).action((file) => {
16614
- const filePath = path38.resolve(file);
16615
- if (!fs37.existsSync(filePath)) {
17873
+ const filePath = path39.resolve(file);
17874
+ if (!fs38.existsSync(filePath)) {
16616
17875
  console.error(`
16617
17876
  \u2717 File not found: ${filePath}
16618
17877
  `);
@@ -16620,7 +17879,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
16620
17879
  }
16621
17880
  let cfg;
16622
17881
  try {
16623
- cfg = JSON.parse(fs37.readFileSync(filePath, "utf-8"));
17882
+ cfg = JSON.parse(fs38.readFileSync(filePath, "utf-8"));
16624
17883
  } catch (err) {
16625
17884
  console.error(`
16626
17885
  \u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
@@ -16641,7 +17900,7 @@ ${err instanceof Error ? err.message : err}
16641
17900
  });
16642
17901
  (function ensurePermissions() {
16643
17902
  try {
16644
- const dir = path38.join(os13.homedir(), ".code-intel");
17903
+ const dir = path39.join(os13.homedir(), ".code-intel");
16645
17904
  secureMkdir(dir);
16646
17905
  tightenDbFiles(dir);
16647
17906
  } catch {
@@ -16658,10 +17917,10 @@ program.command("health").description("Run code health checks: dead code, circul
16658
17917
  $ code-intel health --json
16659
17918
  $ code-intel health --threshold 80
16660
17919
  `).action(async (targetPath, opts) => {
16661
- const workspaceRoot = path38.resolve(targetPath);
17920
+ const workspaceRoot = path39.resolve(targetPath);
16662
17921
  const dbPath = getDbPath(workspaceRoot);
16663
17922
  const meta = loadMetadata(workspaceRoot);
16664
- if (!meta || !fs37.existsSync(dbPath)) {
17923
+ if (!meta || !fs38.existsSync(dbPath)) {
16665
17924
  console.error(`
16666
17925
  \u2717 ${workspaceRoot} is not indexed.`);
16667
17926
  console.error(" Run `code-intel analyze` first to build the index.\n");
@@ -16669,7 +17928,7 @@ program.command("health").description("Run code health checks: dead code, circul
16669
17928
  }
16670
17929
  const { computeHealthReport: computeHealthReport3 } = await Promise.resolve().then(() => (init_health_score(), health_score_exports));
16671
17930
  const graph = createKnowledgeGraph();
16672
- const db = new DbManager(dbPath);
17931
+ const db = new DbManager(dbPath, true);
16673
17932
  await db.init();
16674
17933
  await loadGraphFromDB(graph, db);
16675
17934
  db.close();
@@ -16746,7 +18005,7 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
16746
18005
  $ code-intel query --list
16747
18006
  $ code-intel query --delete auth-search
16748
18007
  `).action(async (gqlArg, opts) => {
16749
- const workspaceRoot = path38.resolve(opts.path);
18008
+ const workspaceRoot = path39.resolve(opts.path);
16750
18009
  const { saveQuery: saveQuery2, loadQuery: loadQuery2, listQueries: listQueries2, deleteQuery: deleteQuery2 } = await Promise.resolve().then(() => (init_saved_queries(), saved_queries_exports));
16751
18010
  if (opts.list) {
16752
18011
  const queries = listQueries2(workspaceRoot);
@@ -16805,14 +18064,14 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
16805
18064
  }
16806
18065
  gqlInput = content;
16807
18066
  } else if (opts.file) {
16808
- const filePath = path38.resolve(opts.file);
16809
- if (!fs37.existsSync(filePath)) {
18067
+ const filePath = path39.resolve(opts.file);
18068
+ if (!fs38.existsSync(filePath)) {
16810
18069
  console.error(`
16811
18070
  \u2717 File not found: ${filePath}
16812
18071
  `);
16813
18072
  process.exit(1);
16814
18073
  }
16815
- gqlInput = fs37.readFileSync(filePath, "utf-8");
18074
+ gqlInput = fs38.readFileSync(filePath, "utf-8");
16816
18075
  } else if (gqlArg) {
16817
18076
  gqlInput = gqlArg;
16818
18077
  } else {
@@ -16935,7 +18194,7 @@ program.command("pr-impact").description("Compute PR blast radius and risk score
16935
18194
  $ code-intel pr-impact --base main --head HEAD --format sarif
16936
18195
  $ code-intel pr-impact --base main --head HEAD --format json
16937
18196
  `).action(async (opts) => {
16938
- const repoPath = path38.resolve(opts.path ?? ".");
18197
+ const repoPath = path39.resolve(opts.path ?? ".");
16939
18198
  const { execSync: execSync5 } = await import('child_process');
16940
18199
  let diff;
16941
18200
  try {
@@ -17235,7 +18494,7 @@ program.command("scan").description("Run security scans: secrets + OWASP vulnera
17235
18494
  const medCount = filtered.filter((f) => f.severity === "MEDIUM").length;
17236
18495
  const lowCount = filtered.filter((f) => f.severity === "LOW").length;
17237
18496
  console.log(`
17238
- \u25C8 Security Scan \u2014 ${path38.resolve(targetPath)}
18497
+ \u25C8 Security Scan \u2014 ${path39.resolve(targetPath)}
17239
18498
  `);
17240
18499
  console.log(` HIGH: ${highCount} MEDIUM: ${medCount} LOW: ${lowCount}
17241
18500
  `);
@@ -17327,8 +18586,8 @@ program.command("doctor").description("Run diagnostics \u2014 check Node.js, git
17327
18586
  const dbPath = getDbPath2(repo.path);
17328
18587
  let dbOk = false;
17329
18588
  try {
17330
- const Database6 = (await import('better-sqlite3')).default;
17331
- const db = new Database6(dbPath, { readonly: true, fileMustExist: true });
18589
+ const Database7 = (await import('better-sqlite3')).default;
18590
+ const db = new Database7(dbPath, { readonly: true, fileMustExist: true });
17332
18591
  db.prepare("SELECT COUNT(*) FROM nodes").get();
17333
18592
  db.close();
17334
18593
  dbOk = true;
@@ -17340,7 +18599,7 @@ program.command("doctor").description("Run diagnostics \u2014 check Node.js, git
17340
18599
  continue;
17341
18600
  }
17342
18601
  const vdbPath = getVectorDbPath2(repo.path);
17343
- if (fs37.existsSync(vdbPath)) {
18602
+ if (fs38.existsSync(vdbPath)) {
17344
18603
  try {
17345
18604
  const Database22 = (await import('better-sqlite3')).default;
17346
18605
  const vdb = new Database22(vdbPath, { readonly: true, fileMustExist: true });