@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/README.md +552 -125
- package/dist/cli/main.js +1836 -577
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +71 -13
- package/dist/index.js +1263 -807
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-CkND97V_.js → es-DIfCC5I3.js} +1 -1
- package/dist/web/assets/index-QSOOiRQm.js +352 -0
- package/dist/web/assets/index-XjZQJMiV.css +2 -0
- package/dist/web/index.html +17 -11
- package/package.json +1 -1
- package/dist/web/assets/index-DExLzJ89.js +0 -348
- package/dist/web/assets/index-DSIgTcZc.css +0 -2
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
|
|
11
|
-
import
|
|
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
|
|
22
|
-
import { Database
|
|
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 =
|
|
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 (!
|
|
340
|
-
|
|
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:
|
|
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,
|
|
602
|
+
function dfs(name, path40) {
|
|
545
603
|
if (visiting.has(name)) {
|
|
546
|
-
const cycleStart =
|
|
547
|
-
const cycle =
|
|
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
|
-
|
|
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,
|
|
615
|
+
if (dfs(dep, path40)) return true;
|
|
558
616
|
}
|
|
559
617
|
}
|
|
560
618
|
visiting.delete(name);
|
|
561
619
|
visited.add(name);
|
|
562
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
965
|
+
const ext = path39.extname(name);
|
|
901
966
|
if (!extensions.has(ext)) continue;
|
|
902
|
-
const fullPath =
|
|
967
|
+
const fullPath = path39.join(dir, name);
|
|
903
968
|
try {
|
|
904
|
-
const stat =
|
|
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 =
|
|
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:
|
|
1001
|
+
name: path39.basename(filePath),
|
|
937
1002
|
filePath: relativePath,
|
|
938
1003
|
metadata: lang ? { language: lang } : void 0
|
|
939
1004
|
});
|
|
940
|
-
let dir =
|
|
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 =
|
|
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:
|
|
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 =
|
|
1032
|
+
const fileDir = path39.dirname(fileURLToPath(import.meta.url));
|
|
968
1033
|
const candidates = [
|
|
969
|
-
|
|
1034
|
+
path39.join(fileDir, "wasm"),
|
|
970
1035
|
// dist/index.js → dist/wasm/
|
|
971
|
-
|
|
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 =
|
|
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
|
|
1026
|
-
if (!
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2774
|
-
const normalized =
|
|
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 =
|
|
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:
|
|
2999
|
-
if (
|
|
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 &&
|
|
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:
|
|
3073
|
+
metadata: { steps: path40, entryPoint: ep.name }
|
|
3009
3074
|
});
|
|
3010
|
-
for (let i = 0; i <
|
|
3075
|
+
for (let i = 0; i < path40.length; i++) {
|
|
3011
3076
|
graph.addEdge({
|
|
3012
|
-
id: generateEdgeId(
|
|
3013
|
-
source:
|
|
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: [...
|
|
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"] ??
|
|
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
|
-
|
|
3066
|
-
|
|
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 =
|
|
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
|
-
|
|
3301
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
4335
|
+
readOnly;
|
|
4336
|
+
constructor(dbPath, readOnly = false) {
|
|
3992
4337
|
this.dbPath = dbPath;
|
|
4338
|
+
this.readOnly = readOnly;
|
|
3993
4339
|
}
|
|
3994
4340
|
async init() {
|
|
3995
|
-
|
|
3996
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 (!
|
|
4214
|
-
const stat =
|
|
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 (!
|
|
4228
|
-
const stat =
|
|
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
|
-
|
|
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: '${
|
|
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: '${
|
|
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 =
|
|
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 =
|
|
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: '${
|
|
4323
|
-
`name: '${
|
|
4324
|
-
`file_path: '${
|
|
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: '${
|
|
4330
|
-
if (node.metadata) parts.push(`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
|
|
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 =
|
|
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
|
-
|
|
4361
|
-
|
|
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 =
|
|
4381
|
-
REPOS_FILE =
|
|
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 =
|
|
4395
|
-
|
|
4396
|
-
|
|
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 =
|
|
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
|
|
4697
|
+
return path39.join(repoDir, ".code-intel", "graph.db");
|
|
4408
4698
|
}
|
|
4409
4699
|
function getVectorDbPath(repoDir) {
|
|
4410
|
-
return
|
|
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
|
|
4756
|
+
return path39.join(GROUPS_DIR, `${name}.json`);
|
|
4467
4757
|
}
|
|
4468
4758
|
function loadGroup(name) {
|
|
4469
4759
|
try {
|
|
4470
|
-
return JSON.parse(
|
|
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
|
-
|
|
4477
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4788
|
+
fs38.unlinkSync(groupFile(name));
|
|
4499
4789
|
} catch {
|
|
4500
4790
|
}
|
|
4501
4791
|
try {
|
|
4502
|
-
|
|
4792
|
+
fs38.unlinkSync(path39.join(GROUPS_DIR, `${name}.sync.json`));
|
|
4503
4793
|
} catch {
|
|
4504
4794
|
}
|
|
4505
4795
|
}
|
|
4506
4796
|
function groupExists(name) {
|
|
4507
|
-
return
|
|
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
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4658
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4982
|
-
if (!
|
|
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
|
-
|
|
5453
|
+
fs38.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
5164
5454
|
if (process.platform !== "win32") {
|
|
5165
5455
|
try {
|
|
5166
|
-
|
|
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
|
-
|
|
5464
|
+
fs38.chmodSync(file, SECURE_FILE_MODE);
|
|
5175
5465
|
} catch {
|
|
5176
5466
|
}
|
|
5177
5467
|
}
|
|
5178
5468
|
function secureWriteFile(file, data) {
|
|
5179
|
-
secureMkdir(
|
|
5180
|
-
|
|
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 (!
|
|
5186
|
-
for (const name of
|
|
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
|
-
|
|
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"] ??
|
|
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 =
|
|
5509
|
+
const dir = path39.dirname(dbPath);
|
|
5220
5510
|
secureMkdir(dir);
|
|
5221
|
-
this.db = new
|
|
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"] ??
|
|
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 (!
|
|
5539
|
-
const blob =
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
7854
|
-
|
|
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 (
|
|
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 =
|
|
8224
|
+
const mcpFile = path39.resolve(editor.mcpConfigKey);
|
|
7935
8225
|
try {
|
|
7936
8226
|
let existing = {};
|
|
7937
|
-
if (
|
|
7938
|
-
existing = JSON.parse(
|
|
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
|
-
|
|
7948
|
-
|
|
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 =
|
|
8045
|
-
CONFIG_PATH =
|
|
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
|
|
8086
|
-
if (platform === "win32") return
|
|
8087
|
-
return
|
|
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
|
|
8097
|
-
if (platform === "win32") return
|
|
8098
|
-
return
|
|
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
|
|
8107
|
-
return
|
|
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) =>
|
|
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 =
|
|
9006
|
+
const ignoreFile = path39.join(this.workspaceRoot, ".codeintelignore");
|
|
8460
9007
|
try {
|
|
8461
|
-
if (!
|
|
8462
|
-
return
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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
|
|
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 (
|
|
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) =>
|
|
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(
|
|
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
|
|
9154
|
+
return path39.join(workspaceRoot, ".code-intel", "queries");
|
|
8597
9155
|
}
|
|
8598
9156
|
function ensureQueriesDir(workspaceRoot) {
|
|
8599
9157
|
const dir = getQueriesDir(workspaceRoot);
|
|
8600
|
-
if (!
|
|
8601
|
-
|
|
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 =
|
|
8608
|
-
|
|
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 =
|
|
8613
|
-
if (!
|
|
8614
|
-
return
|
|
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 (!
|
|
8619
|
-
const files =
|
|
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 =
|
|
9179
|
+
const filePath = path39.join(dir, f);
|
|
8622
9180
|
const name = f.replace(/\.gql$/, "");
|
|
8623
|
-
const content =
|
|
8624
|
-
const stat =
|
|
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 =
|
|
8636
|
-
if (!
|
|
8637
|
-
|
|
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 =
|
|
8643
|
-
return
|
|
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/
|
|
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 &&
|
|
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 =
|
|
8847
|
-
if (!
|
|
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
|
-
|
|
8890
|
-
this.db = new
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
9171
|
-
if (
|
|
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 =
|
|
9176
|
-
if (
|
|
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 =
|
|
9180
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
9217
|
-
|
|
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 =
|
|
10231
|
+
const fileName = path39.basename(entry.path);
|
|
9245
10232
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
9246
|
-
const body =
|
|
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
|
-
|
|
9264
|
-
|
|
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 (!
|
|
10297
|
+
if (!fs38.existsSync(entry.path)) {
|
|
9311
10298
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
9312
10299
|
}
|
|
9313
|
-
const encrypted =
|
|
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 =
|
|
9328
|
-
|
|
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 =
|
|
10332
|
+
destPath = path39.join(os13.homedir(), ".code-intel", name);
|
|
9346
10333
|
} else {
|
|
9347
|
-
destPath =
|
|
10334
|
+
destPath = path39.join(codeIntelDir, name);
|
|
9348
10335
|
}
|
|
9349
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
10385
|
+
return path39.join(this.backupDir, "index.json");
|
|
9399
10386
|
}
|
|
9400
10387
|
_loadIndex() {
|
|
9401
10388
|
try {
|
|
9402
|
-
return JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
11036
|
+
var __dirname$1 = path39.dirname(fileURLToPath(import.meta.url));
|
|
10050
11037
|
var WEB_DIST = (() => {
|
|
10051
|
-
const bundled =
|
|
10052
|
-
if (
|
|
10053
|
-
return
|
|
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"] ?? `${
|
|
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
|
-
|
|
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
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
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 =
|
|
10585
|
-
if (!
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
10760
|
-
callees: outgoing.filter((e) => e.kind === "calls").map((e) => ({ id: e.target, name:
|
|
10761
|
-
imports: outgoing.filter((e) => e.kind === "imports").map((e) => ({ id: e.target, name:
|
|
10762
|
-
importedBy: incoming.filter((e) => e.kind === "imports").map((e) => ({ id: e.source, name:
|
|
10763
|
-
extends: outgoing.filter((e) => e.kind === "extends").map((e) => ({ id: e.target, name:
|
|
10764
|
-
implementsEdges: outgoing.filter((e) => e.kind === "implements").map((e) => ({ id: e.target, name:
|
|
10765
|
-
members: outgoing.filter((e) => e.kind === "has_member").map((e) => ({ id: e.target, name:
|
|
10766
|
-
cluster: incoming.filter((e) => e.kind === "belongs_to").map((e) =>
|
|
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
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
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 =
|
|
10931
|
-
if (!
|
|
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 =
|
|
10958
|
-
if (
|
|
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
|
|
11008
|
-
if (
|
|
11009
|
-
|
|
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 =
|
|
12146
|
+
const resolvedFile = path39.resolve(rawResolved);
|
|
11012
12147
|
function isInsideDir(fileAbs, dir) {
|
|
11013
|
-
const rel =
|
|
11014
|
-
return !rel.startsWith("..") && !
|
|
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 =
|
|
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 =
|
|
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 (
|
|
12352
|
+
if (fs38.existsSync(WEB_DIST)) {
|
|
11218
12353
|
app.use(express.static(WEB_DIST));
|
|
11219
12354
|
app.get("/{*path}", (_req, res) => {
|
|
11220
|
-
res.sendFile(
|
|
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:
|
|
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, ...
|
|
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 &&
|
|
11764
|
-
callPaths.push(
|
|
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
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
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 +
|
|
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 =
|
|
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
|
-
|
|
13146
|
-
|
|
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 =
|
|
13154
|
-
|
|
13155
|
-
|
|
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(
|
|
13336
|
-
upsertFile(
|
|
13337
|
-
const githubDir =
|
|
13338
|
-
if (!
|
|
13339
|
-
upsertFile(
|
|
13340
|
-
const cursorDir =
|
|
13341
|
-
if (!
|
|
13342
|
-
upsertFile(
|
|
13343
|
-
const kiroDir =
|
|
13344
|
-
if (!
|
|
13345
|
-
upsertFile(
|
|
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 (!
|
|
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
|
-
|
|
14648
|
+
fs38.writeFileSync(filePath, newContent, "utf-8");
|
|
13494
14649
|
return;
|
|
13495
14650
|
}
|
|
13496
|
-
const existing =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 } =
|
|
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 } =
|
|
13578
|
-
snap[
|
|
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) =>
|
|
13590
|
-
const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((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 =
|
|
13618
|
-
if (!
|
|
13619
|
-
return
|
|
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
|
|
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 =
|
|
13633
|
-
if (!
|
|
14787
|
+
const pkgJsonPath = path39.join(dir, "package.json");
|
|
14788
|
+
if (!fs38.existsSync(pkgJsonPath)) continue;
|
|
13634
14789
|
try {
|
|
13635
|
-
const pkgJson = JSON.parse(
|
|
13636
|
-
const name = pkgJson.name ??
|
|
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 =
|
|
13646
|
-
if (
|
|
14800
|
+
const turboJsonPath = path39.join(root, "turbo.json");
|
|
14801
|
+
if (fs38.existsSync(turboJsonPath)) {
|
|
13647
14802
|
let patterns = [];
|
|
13648
|
-
const pkgJsonPath =
|
|
13649
|
-
if (
|
|
14803
|
+
const pkgJsonPath = path39.join(root, "package.json");
|
|
14804
|
+
if (fs38.existsSync(pkgJsonPath)) {
|
|
13650
14805
|
try {
|
|
13651
|
-
const pkgJson = JSON.parse(
|
|
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 =
|
|
13660
|
-
if (
|
|
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 =
|
|
13666
|
-
if (
|
|
14820
|
+
const npmPkgJsonPath = path39.join(root, "package.json");
|
|
14821
|
+
if (fs38.existsSync(npmPkgJsonPath)) {
|
|
13667
14822
|
try {
|
|
13668
|
-
const pkgJson = JSON.parse(
|
|
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 =
|
|
13678
|
-
if (
|
|
14832
|
+
const pnpmYamlPath = path39.join(root, "pnpm-workspace.yaml");
|
|
14833
|
+
if (fs38.existsSync(pnpmYamlPath)) {
|
|
13679
14834
|
const patterns = [];
|
|
13680
14835
|
try {
|
|
13681
|
-
const content =
|
|
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 =
|
|
13702
|
-
if (
|
|
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 =
|
|
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 =
|
|
14869
|
+
const fullPath = path39.join(dir, entry);
|
|
13715
14870
|
try {
|
|
13716
|
-
if (!
|
|
14871
|
+
if (!fs38.statSync(fullPath).isDirectory()) continue;
|
|
13717
14872
|
} catch {
|
|
13718
14873
|
continue;
|
|
13719
14874
|
}
|
|
13720
|
-
const projectJsonPath =
|
|
13721
|
-
if (
|
|
14875
|
+
const projectJsonPath = path39.join(fullPath, "project.json");
|
|
14876
|
+
if (fs38.existsSync(projectJsonPath)) {
|
|
13722
14877
|
try {
|
|
13723
|
-
const proj = JSON.parse(
|
|
13724
|
-
const name = proj.name ??
|
|
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 || !
|
|
13833
|
-
const backupDir =
|
|
13834
|
-
|
|
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 =
|
|
13837
|
-
const backupPath =
|
|
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
|
-
|
|
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 =
|
|
15144
|
+
var GLOBAL_DIR3 = path39.join(os13.homedir(), ".code-intel");
|
|
13990
15145
|
function loadRepoPaths() {
|
|
13991
15146
|
try {
|
|
13992
|
-
const data =
|
|
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 =
|
|
15155
|
+
const groupsDir = path39.join(GLOBAL_DIR3, "groups");
|
|
14001
15156
|
try {
|
|
14002
|
-
return
|
|
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 =
|
|
14264
|
-
const dest =
|
|
14265
|
-
|
|
14266
|
-
|
|
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" ?
|
|
15431
|
+
const rcFile = shell === "zsh" ? path39.join(os13.homedir(), ".zshrc") : path39.join(os13.homedir(), ".bashrc");
|
|
14277
15432
|
try {
|
|
14278
|
-
const existing =
|
|
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
|
-
|
|
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 =
|
|
14304
|
-
var META_PATH =
|
|
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(
|
|
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
|
-
|
|
14316
|
-
|
|
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 =
|
|
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 (
|
|
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 =
|
|
14565
|
-
if (!
|
|
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 (
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 (
|
|
15993
|
+
if (fs38.existsSync(f)) {
|
|
14773
15994
|
const dest = f.replace(dbPathNew, dbPath);
|
|
14774
15995
|
try {
|
|
14775
|
-
|
|
15996
|
+
fs38.renameSync(f, dest);
|
|
14776
15997
|
} catch {
|
|
14777
15998
|
}
|
|
14778
15999
|
}
|
|
14779
16000
|
}
|
|
14780
|
-
|
|
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 (
|
|
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 (
|
|
15019
|
-
existing = JSON.parse(
|
|
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
|
-
|
|
15029
|
-
|
|
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 =
|
|
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 =
|
|
15124
|
-
const repoName =
|
|
16360
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
16361
|
+
const repoName = path39.basename(workspaceRoot);
|
|
15125
16362
|
const dbPath = getDbPath(workspaceRoot);
|
|
15126
|
-
|
|
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 =
|
|
15158
|
-
const repoName =
|
|
16398
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
16399
|
+
const repoName = path39.basename(workspaceRoot);
|
|
15159
16400
|
const dbPath = getDbPath(workspaceRoot);
|
|
15160
|
-
|
|
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
|
|
15165
|
-
|
|
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
|
|
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
|
|
15175
|
-
|
|
15176
|
-
|
|
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
|
-
|
|
15180
|
-
|
|
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 =
|
|
15195
|
-
const repoName =
|
|
16440
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
16441
|
+
const repoName = path39.basename(workspaceRoot);
|
|
15196
16442
|
const dbPath = getDbPath(workspaceRoot);
|
|
15197
|
-
|
|
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
|
-
|
|
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
|
|
15209
|
-
|
|
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
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
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
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
|
|
15237
|
-
|
|
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 =
|
|
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 =
|
|
15308
|
-
if (!
|
|
16566
|
+
const codeIntelDir = path39.join(repoPath, ".code-intel");
|
|
16567
|
+
if (!fs38.existsSync(codeIntelDir)) return;
|
|
15309
16568
|
const trashName = trashDirName();
|
|
15310
|
-
const trashDir =
|
|
16569
|
+
const trashDir = path39.join(repoPath, trashName);
|
|
15311
16570
|
let dest = trashDir;
|
|
15312
16571
|
let counter = 1;
|
|
15313
|
-
while (
|
|
16572
|
+
while (fs38.existsSync(dest)) {
|
|
15314
16573
|
dest = `${trashDir}-${counter++}`;
|
|
15315
16574
|
}
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
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
|
|
16586
|
+
for (const entry of fs38.readdirSync(repoPath)) {
|
|
15328
16587
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
15329
|
-
const fullPath =
|
|
15330
|
-
const metaPath =
|
|
15331
|
-
if (
|
|
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(
|
|
16592
|
+
const meta = JSON.parse(fs38.readFileSync(metaPath, "utf-8"));
|
|
15334
16593
|
if (new Date(meta.deletedAt).getTime() < cutoff) {
|
|
15335
|
-
|
|
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 =
|
|
15362
|
-
if (!
|
|
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
|
|
15370
|
-
const full =
|
|
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 +=
|
|
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 ${
|
|
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(
|
|
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
|
|
16669
|
+
for (const entry of fs38.readdirSync(root)) {
|
|
15411
16670
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
15412
|
-
const fullPath =
|
|
15413
|
-
const metaPath =
|
|
16671
|
+
const fullPath = path39.join(root, entry);
|
|
16672
|
+
const metaPath = path39.join(fullPath, "TRASH_META.json");
|
|
15414
16673
|
let deletedAt = "unknown";
|
|
15415
|
-
if (
|
|
16674
|
+
if (fs38.existsSync(metaPath)) {
|
|
15416
16675
|
try {
|
|
15417
|
-
deletedAt = JSON.parse(
|
|
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 =
|
|
15446
|
-
if (
|
|
15447
|
-
|
|
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 =
|
|
16720
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
15462
16721
|
if (opts.purge) {
|
|
15463
|
-
const codeIntelDir =
|
|
15464
|
-
if (
|
|
15465
|
-
|
|
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 =
|
|
16736
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
15478
16737
|
const dbPath = getDbPath(workspaceRoot);
|
|
15479
|
-
const existingIndex =
|
|
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:
|
|
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 =
|
|
17224
|
+
const metaPath = path39.join(regEntry.path, ".code-intel", "meta.json");
|
|
15966
17225
|
try {
|
|
15967
|
-
const meta = JSON.parse(
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 ??
|
|
16334
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
-
|
|
16457
|
-
|
|
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 =
|
|
16471
|
-
if (!
|
|
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(
|
|
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 =
|
|
16489
|
-
if (
|
|
16490
|
-
|
|
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 =
|
|
16615
|
-
if (!
|
|
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(
|
|
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 =
|
|
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 =
|
|
17920
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
16662
17921
|
const dbPath = getDbPath(workspaceRoot);
|
|
16663
17922
|
const meta = loadMetadata(workspaceRoot);
|
|
16664
|
-
if (!meta || !
|
|
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 =
|
|
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 =
|
|
16809
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 ${
|
|
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
|
|
17331
|
-
const db = new
|
|
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 (
|
|
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 });
|