@vohongtho.infotech/code-intel 0.5.0 → 0.7.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 +3 -2
- package/dist/cli/main.js +2550 -933
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +1377 -216
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-CnPQcqTr.js → es-yDTUrrnL.js} +1 -1
- package/dist/web/assets/{index-j-iO6isa.js → index-B4bH2ZP8.js} +4 -4
- package/dist/web/assets/index-DSIgTcZc.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +12 -1
- package/dist/web/assets/index-rprt8Su_.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 fs33, { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import path33, { dirname, join } from 'path';
|
|
12
12
|
import os12 from 'os';
|
|
13
13
|
import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client';
|
|
14
14
|
import { createRequire } from 'module';
|
|
@@ -326,7 +326,7 @@ var init_logger = __esm({
|
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
/** Global log directory: ~/.code-intel/logs */
|
|
329
|
-
static LOG_DIR =
|
|
329
|
+
static LOG_DIR = path33.join(os12.homedir(), ".code-intel", "logs");
|
|
330
330
|
static getLogger() {
|
|
331
331
|
if (!_Logger.instance) {
|
|
332
332
|
const isProduction = process.env.NODE_ENV === "production";
|
|
@@ -335,12 +335,12 @@ var init_logger = __esm({
|
|
|
335
335
|
transports.push(new winston.transports.Console());
|
|
336
336
|
if (!isProduction) {
|
|
337
337
|
try {
|
|
338
|
-
if (!
|
|
339
|
-
|
|
338
|
+
if (!fs33.existsSync(_Logger.LOG_DIR)) {
|
|
339
|
+
fs33.mkdirSync(_Logger.LOG_DIR, { recursive: true });
|
|
340
340
|
}
|
|
341
341
|
transports.push(
|
|
342
342
|
new DailyRotateFile({
|
|
343
|
-
filename:
|
|
343
|
+
filename: path33.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
|
|
344
344
|
datePattern: "YYYY-MM-DD",
|
|
345
345
|
maxSize: "20m",
|
|
346
346
|
maxFiles: "14d"
|
|
@@ -392,6 +392,131 @@ var init_logger = __esm({
|
|
|
392
392
|
}
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
+
// src/graph/knowledge-graph.ts
|
|
396
|
+
function createKnowledgeGraph() {
|
|
397
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
398
|
+
const edges = /* @__PURE__ */ new Map();
|
|
399
|
+
const edgesByKind = /* @__PURE__ */ new Map();
|
|
400
|
+
const edgesFromNode = /* @__PURE__ */ new Map();
|
|
401
|
+
const edgesToNode = /* @__PURE__ */ new Map();
|
|
402
|
+
function indexEdge(edge) {
|
|
403
|
+
let kindSet = edgesByKind.get(edge.kind);
|
|
404
|
+
if (!kindSet) {
|
|
405
|
+
kindSet = /* @__PURE__ */ new Set();
|
|
406
|
+
edgesByKind.set(edge.kind, kindSet);
|
|
407
|
+
}
|
|
408
|
+
kindSet.add(edge.id);
|
|
409
|
+
let fromSet = edgesFromNode.get(edge.source);
|
|
410
|
+
if (!fromSet) {
|
|
411
|
+
fromSet = /* @__PURE__ */ new Set();
|
|
412
|
+
edgesFromNode.set(edge.source, fromSet);
|
|
413
|
+
}
|
|
414
|
+
fromSet.add(edge.id);
|
|
415
|
+
let toSet = edgesToNode.get(edge.target);
|
|
416
|
+
if (!toSet) {
|
|
417
|
+
toSet = /* @__PURE__ */ new Set();
|
|
418
|
+
edgesToNode.set(edge.target, toSet);
|
|
419
|
+
}
|
|
420
|
+
toSet.add(edge.id);
|
|
421
|
+
}
|
|
422
|
+
function unindexEdge(edge) {
|
|
423
|
+
edgesByKind.get(edge.kind)?.delete(edge.id);
|
|
424
|
+
edgesFromNode.get(edge.source)?.delete(edge.id);
|
|
425
|
+
edgesToNode.get(edge.target)?.delete(edge.id);
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
addNode(node) {
|
|
429
|
+
nodes.set(node.id, node);
|
|
430
|
+
},
|
|
431
|
+
addEdge(edge) {
|
|
432
|
+
edges.set(edge.id, edge);
|
|
433
|
+
indexEdge(edge);
|
|
434
|
+
},
|
|
435
|
+
getNode(id) {
|
|
436
|
+
return nodes.get(id);
|
|
437
|
+
},
|
|
438
|
+
getEdge(id) {
|
|
439
|
+
return edges.get(id);
|
|
440
|
+
},
|
|
441
|
+
*findEdgesByKind(kind) {
|
|
442
|
+
const ids = edgesByKind.get(kind);
|
|
443
|
+
if (!ids) return;
|
|
444
|
+
for (const id of ids) {
|
|
445
|
+
const edge = edges.get(id);
|
|
446
|
+
if (edge) yield edge;
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
*findEdgesFrom(sourceId) {
|
|
450
|
+
const ids = edgesFromNode.get(sourceId);
|
|
451
|
+
if (!ids) return;
|
|
452
|
+
for (const id of ids) {
|
|
453
|
+
const edge = edges.get(id);
|
|
454
|
+
if (edge) yield edge;
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
*findEdgesTo(targetId) {
|
|
458
|
+
const ids = edgesToNode.get(targetId);
|
|
459
|
+
if (!ids) return;
|
|
460
|
+
for (const id of ids) {
|
|
461
|
+
const edge = edges.get(id);
|
|
462
|
+
if (edge) yield edge;
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
removeNodeCascade(id) {
|
|
466
|
+
const fromEdges = edgesFromNode.get(id);
|
|
467
|
+
if (fromEdges) {
|
|
468
|
+
for (const edgeId of [...fromEdges]) {
|
|
469
|
+
const edge = edges.get(edgeId);
|
|
470
|
+
if (edge) {
|
|
471
|
+
unindexEdge(edge);
|
|
472
|
+
edges.delete(edgeId);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const toEdges = edgesToNode.get(id);
|
|
477
|
+
if (toEdges) {
|
|
478
|
+
for (const edgeId of [...toEdges]) {
|
|
479
|
+
const edge = edges.get(edgeId);
|
|
480
|
+
if (edge) {
|
|
481
|
+
unindexEdge(edge);
|
|
482
|
+
edges.delete(edgeId);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
edgesFromNode.delete(id);
|
|
487
|
+
edgesToNode.delete(id);
|
|
488
|
+
nodes.delete(id);
|
|
489
|
+
},
|
|
490
|
+
removeEdge(id) {
|
|
491
|
+
const edge = edges.get(id);
|
|
492
|
+
if (edge) {
|
|
493
|
+
unindexEdge(edge);
|
|
494
|
+
edges.delete(id);
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
*allNodes() {
|
|
498
|
+
yield* nodes.values();
|
|
499
|
+
},
|
|
500
|
+
*allEdges() {
|
|
501
|
+
yield* edges.values();
|
|
502
|
+
},
|
|
503
|
+
get size() {
|
|
504
|
+
return { nodes: nodes.size, edges: edges.size };
|
|
505
|
+
},
|
|
506
|
+
clear() {
|
|
507
|
+
nodes.clear();
|
|
508
|
+
edges.clear();
|
|
509
|
+
edgesByKind.clear();
|
|
510
|
+
edgesFromNode.clear();
|
|
511
|
+
edgesToNode.clear();
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
var init_knowledge_graph = __esm({
|
|
516
|
+
"src/graph/knowledge-graph.ts"() {
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
395
520
|
// src/pipeline/dag-validator.ts
|
|
396
521
|
function validateDAG(phases) {
|
|
397
522
|
const errors = [];
|
|
@@ -415,25 +540,25 @@ function validateDAG(phases) {
|
|
|
415
540
|
const visiting = /* @__PURE__ */ new Set();
|
|
416
541
|
const visited = /* @__PURE__ */ new Set();
|
|
417
542
|
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
418
|
-
function dfs(name,
|
|
543
|
+
function dfs(name, path34) {
|
|
419
544
|
if (visiting.has(name)) {
|
|
420
|
-
const cycleStart =
|
|
421
|
-
const cycle =
|
|
545
|
+
const cycleStart = path34.indexOf(name);
|
|
546
|
+
const cycle = path34.slice(cycleStart).concat(name);
|
|
422
547
|
errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
423
548
|
return true;
|
|
424
549
|
}
|
|
425
550
|
if (visited.has(name)) return false;
|
|
426
551
|
visiting.add(name);
|
|
427
|
-
|
|
552
|
+
path34.push(name);
|
|
428
553
|
const phase = phaseMap.get(name);
|
|
429
554
|
if (phase) {
|
|
430
555
|
for (const dep of phase.dependencies) {
|
|
431
|
-
if (dfs(dep,
|
|
556
|
+
if (dfs(dep, path34)) return true;
|
|
432
557
|
}
|
|
433
558
|
}
|
|
434
559
|
visiting.delete(name);
|
|
435
560
|
visited.add(name);
|
|
436
|
-
|
|
561
|
+
path34.pop();
|
|
437
562
|
return false;
|
|
438
563
|
}
|
|
439
564
|
for (const phase of phases) {
|
|
@@ -693,11 +818,11 @@ var init_id_generator = __esm({
|
|
|
693
818
|
}
|
|
694
819
|
});
|
|
695
820
|
function findBundledWasmDir() {
|
|
696
|
-
const fileDir =
|
|
821
|
+
const fileDir = path33.dirname(fileURLToPath(import.meta.url));
|
|
697
822
|
const candidates = [
|
|
698
|
-
|
|
823
|
+
path33.join(fileDir, "wasm"),
|
|
699
824
|
// dist/index.js → dist/wasm/
|
|
700
|
-
|
|
825
|
+
path33.join(fileDir, "../wasm")
|
|
701
826
|
// dist/cli/main.js → dist/wasm/
|
|
702
827
|
];
|
|
703
828
|
for (const candidate of candidates) {
|
|
@@ -738,7 +863,7 @@ function wasmPath(lang) {
|
|
|
738
863
|
}
|
|
739
864
|
const bundled = BUNDLED_WASM_MAP[lang];
|
|
740
865
|
if (bundled) {
|
|
741
|
-
const bundledPath =
|
|
866
|
+
const bundledPath = path33.join(_bundledWasmDir, bundled);
|
|
742
867
|
if (existsSync(bundledPath)) return bundledPath;
|
|
743
868
|
}
|
|
744
869
|
return null;
|
|
@@ -751,14 +876,14 @@ async function initParser() {
|
|
|
751
876
|
}
|
|
752
877
|
async function getLanguage(lang) {
|
|
753
878
|
if (languageCache.has(lang)) return languageCache.get(lang);
|
|
754
|
-
const
|
|
755
|
-
if (!
|
|
879
|
+
const path34 = wasmPath(lang);
|
|
880
|
+
if (!path34) {
|
|
756
881
|
languageCache.set(lang, null);
|
|
757
882
|
return null;
|
|
758
883
|
}
|
|
759
884
|
try {
|
|
760
885
|
await initParser();
|
|
761
|
-
const language = await Language.load(
|
|
886
|
+
const language = await Language.load(path34);
|
|
762
887
|
languageCache.set(lang, language);
|
|
763
888
|
return language;
|
|
764
889
|
} catch {
|
|
@@ -2183,7 +2308,7 @@ var init_parse_phase = __esm({
|
|
|
2183
2308
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
2184
2309
|
await Promise.all(batch.map(async (filePath) => {
|
|
2185
2310
|
try {
|
|
2186
|
-
const source = await
|
|
2311
|
+
const source = await fs33.promises.readFile(filePath, "utf-8");
|
|
2187
2312
|
context2.fileCache.set(filePath, source);
|
|
2188
2313
|
} catch {
|
|
2189
2314
|
}
|
|
@@ -2196,14 +2321,14 @@ var init_parse_phase = __esm({
|
|
|
2196
2321
|
const lang = detectLanguage(filePath);
|
|
2197
2322
|
if (!lang) {
|
|
2198
2323
|
if (context2.verbose) {
|
|
2199
|
-
const relativePath2 =
|
|
2324
|
+
const relativePath2 = path33.relative(context2.workspaceRoot, filePath);
|
|
2200
2325
|
logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
|
|
2201
2326
|
}
|
|
2202
2327
|
continue;
|
|
2203
2328
|
}
|
|
2204
2329
|
const source = context2.fileCache.get(filePath);
|
|
2205
2330
|
if (!source) continue;
|
|
2206
|
-
const relativePath =
|
|
2331
|
+
const relativePath = path33.relative(context2.workspaceRoot, filePath);
|
|
2207
2332
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
2208
2333
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
2209
2334
|
if (fileNode) {
|
|
@@ -2449,11 +2574,11 @@ var init_resolve_phase = __esm({
|
|
|
2449
2574
|
let heritageEdges = 0;
|
|
2450
2575
|
const fileIndex = /* @__PURE__ */ new Map();
|
|
2451
2576
|
for (const fp of filePaths) {
|
|
2452
|
-
const rel =
|
|
2577
|
+
const rel = path33.relative(workspaceRoot, fp);
|
|
2453
2578
|
fileIndex.set(rel, fp);
|
|
2454
2579
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
2455
2580
|
if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
|
|
2456
|
-
const base =
|
|
2581
|
+
const base = path33.basename(rel, path33.extname(rel));
|
|
2457
2582
|
if (!fileIndex.has(base)) fileIndex.set(base, fp);
|
|
2458
2583
|
}
|
|
2459
2584
|
const symbolIndex = /* @__PURE__ */ new Map();
|
|
@@ -2484,7 +2609,7 @@ var init_resolve_phase = __esm({
|
|
|
2484
2609
|
for (const filePath of filePaths) {
|
|
2485
2610
|
const lang = detectLanguage(filePath);
|
|
2486
2611
|
if (!lang) continue;
|
|
2487
|
-
const relativePath =
|
|
2612
|
+
const relativePath = path33.relative(workspaceRoot, filePath);
|
|
2488
2613
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
2489
2614
|
const source = fileCache.get(filePath);
|
|
2490
2615
|
if (!source) continue;
|
|
@@ -2497,13 +2622,13 @@ var init_resolve_phase = __esm({
|
|
|
2497
2622
|
let resolvedRelPath = null;
|
|
2498
2623
|
if (cleaned.startsWith(".")) {
|
|
2499
2624
|
const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
|
|
2500
|
-
const fromDir =
|
|
2625
|
+
const fromDir = path33.dirname(relativePath);
|
|
2501
2626
|
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
|
|
2502
|
-
const candidate =
|
|
2503
|
-
const normalized =
|
|
2627
|
+
const candidate = path33.join(fromDir, cleanedNoJs + ext);
|
|
2628
|
+
const normalized = path33.normalize(candidate);
|
|
2504
2629
|
if (fileIndex.has(normalized)) {
|
|
2505
2630
|
const absPath = fileIndex.get(normalized);
|
|
2506
|
-
resolvedRelPath =
|
|
2631
|
+
resolvedRelPath = path33.relative(workspaceRoot, absPath);
|
|
2507
2632
|
break;
|
|
2508
2633
|
}
|
|
2509
2634
|
}
|
|
@@ -2968,7 +3093,7 @@ var init_db_manager = __esm({
|
|
|
2968
3093
|
this.dbPath = dbPath;
|
|
2969
3094
|
}
|
|
2970
3095
|
async init() {
|
|
2971
|
-
|
|
3096
|
+
fs33.mkdirSync(path33.dirname(this.dbPath), { recursive: true });
|
|
2972
3097
|
this.db = new Database$1(this.dbPath);
|
|
2973
3098
|
await this.db.init();
|
|
2974
3099
|
this.conn = new Connection(this.db);
|
|
@@ -3058,13 +3183,14 @@ var init_schema = __esm({
|
|
|
3058
3183
|
constant: "const_nodes",
|
|
3059
3184
|
route: "route_nodes",
|
|
3060
3185
|
cluster: "cluster_nodes",
|
|
3061
|
-
flow: "flow_nodes"
|
|
3186
|
+
flow: "flow_nodes",
|
|
3187
|
+
vulnerability: "vuln_nodes"
|
|
3062
3188
|
};
|
|
3063
3189
|
ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
|
|
3064
3190
|
}
|
|
3065
3191
|
});
|
|
3066
3192
|
function writeNodeCSVs(graph, outputDir) {
|
|
3067
|
-
|
|
3193
|
+
fs33.mkdirSync(outputDir, { recursive: true });
|
|
3068
3194
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
3069
3195
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
3070
3196
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -3072,7 +3198,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
3072
3198
|
const table = NODE_TABLE_MAP[node.kind];
|
|
3073
3199
|
if (!tableBuffers.has(table)) {
|
|
3074
3200
|
tableBuffers.set(table, [header]);
|
|
3075
|
-
tableFilePaths.set(table,
|
|
3201
|
+
tableFilePaths.set(table, path33.join(outputDir, `${table}.csv`));
|
|
3076
3202
|
}
|
|
3077
3203
|
tableBuffers.get(table).push(
|
|
3078
3204
|
csvRow([
|
|
@@ -3092,12 +3218,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
3092
3218
|
);
|
|
3093
3219
|
}
|
|
3094
3220
|
for (const [table, lines] of tableBuffers) {
|
|
3095
|
-
|
|
3221
|
+
fs33.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
3096
3222
|
}
|
|
3097
3223
|
return tableFilePaths;
|
|
3098
3224
|
}
|
|
3099
3225
|
function writeEdgeCSV(graph, outputDir) {
|
|
3100
|
-
|
|
3226
|
+
fs33.mkdirSync(outputDir, { recursive: true });
|
|
3101
3227
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
3102
3228
|
const groups = /* @__PURE__ */ new Map();
|
|
3103
3229
|
for (const edge of graph.allEdges()) {
|
|
@@ -3108,7 +3234,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
3108
3234
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
3109
3235
|
const key = `${fromTable}->${toTable}`;
|
|
3110
3236
|
if (!groups.has(key)) {
|
|
3111
|
-
const filePath =
|
|
3237
|
+
const filePath = path33.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
3112
3238
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
3113
3239
|
}
|
|
3114
3240
|
groups.get(key).lines.push(
|
|
@@ -3123,7 +3249,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
3123
3249
|
}
|
|
3124
3250
|
const result = [];
|
|
3125
3251
|
for (const group of groups.values()) {
|
|
3126
|
-
|
|
3252
|
+
fs33.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
3127
3253
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
3128
3254
|
}
|
|
3129
3255
|
return result;
|
|
@@ -3166,7 +3292,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3166
3292
|
} catch {
|
|
3167
3293
|
}
|
|
3168
3294
|
}
|
|
3169
|
-
const tmpDir =
|
|
3295
|
+
const tmpDir = fs33.mkdtempSync(path33.join(os12.tmpdir(), "code-intel-csv-"));
|
|
3170
3296
|
try {
|
|
3171
3297
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
3172
3298
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -3185,8 +3311,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3185
3311
|
}
|
|
3186
3312
|
let nodeCount = 0;
|
|
3187
3313
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
3188
|
-
if (!
|
|
3189
|
-
const stat =
|
|
3314
|
+
if (!fs33.existsSync(csvPath)) continue;
|
|
3315
|
+
const stat = fs33.statSync(csvPath);
|
|
3190
3316
|
if (stat.size < 50) continue;
|
|
3191
3317
|
try {
|
|
3192
3318
|
await dbManager.execute(
|
|
@@ -3199,8 +3325,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3199
3325
|
}
|
|
3200
3326
|
let edgeCount = 0;
|
|
3201
3327
|
for (const group of edgeGroups) {
|
|
3202
|
-
if (!
|
|
3203
|
-
const stat =
|
|
3328
|
+
if (!fs33.existsSync(group.filePath)) continue;
|
|
3329
|
+
const stat = fs33.statSync(group.filePath);
|
|
3204
3330
|
if (stat.size < 50) continue;
|
|
3205
3331
|
try {
|
|
3206
3332
|
await dbManager.execute(
|
|
@@ -3214,7 +3340,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3214
3340
|
return { nodeCount, edgeCount };
|
|
3215
3341
|
} finally {
|
|
3216
3342
|
try {
|
|
3217
|
-
|
|
3343
|
+
fs33.rmSync(tmpDir, { recursive: true, force: true });
|
|
3218
3344
|
} catch {
|
|
3219
3345
|
}
|
|
3220
3346
|
}
|
|
@@ -3316,15 +3442,15 @@ var init_graph_loader = __esm({
|
|
|
3316
3442
|
});
|
|
3317
3443
|
function loadRegistry() {
|
|
3318
3444
|
try {
|
|
3319
|
-
const data =
|
|
3445
|
+
const data = fs33.readFileSync(REPOS_FILE, "utf-8");
|
|
3320
3446
|
return JSON.parse(data);
|
|
3321
3447
|
} catch {
|
|
3322
3448
|
return [];
|
|
3323
3449
|
}
|
|
3324
3450
|
}
|
|
3325
3451
|
function saveRegistry(entries) {
|
|
3326
|
-
|
|
3327
|
-
|
|
3452
|
+
fs33.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
3453
|
+
fs33.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
3328
3454
|
}
|
|
3329
3455
|
function upsertRepo(entry) {
|
|
3330
3456
|
const entries = loadRegistry();
|
|
@@ -3343,28 +3469,28 @@ function removeRepo(repoPath) {
|
|
|
3343
3469
|
var GLOBAL_DIR, REPOS_FILE;
|
|
3344
3470
|
var init_repo_registry = __esm({
|
|
3345
3471
|
"src/storage/repo-registry.ts"() {
|
|
3346
|
-
GLOBAL_DIR =
|
|
3347
|
-
REPOS_FILE =
|
|
3472
|
+
GLOBAL_DIR = path33.join(os12.homedir(), ".code-intel");
|
|
3473
|
+
REPOS_FILE = path33.join(GLOBAL_DIR, "repos.json");
|
|
3348
3474
|
}
|
|
3349
3475
|
});
|
|
3350
3476
|
function saveMetadata(repoDir, metadata) {
|
|
3351
|
-
const metaDir =
|
|
3352
|
-
|
|
3353
|
-
|
|
3477
|
+
const metaDir = path33.join(repoDir, ".code-intel");
|
|
3478
|
+
fs33.mkdirSync(metaDir, { recursive: true });
|
|
3479
|
+
fs33.writeFileSync(path33.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
3354
3480
|
}
|
|
3355
3481
|
function loadMetadata(repoDir) {
|
|
3356
3482
|
try {
|
|
3357
|
-
const data =
|
|
3483
|
+
const data = fs33.readFileSync(path33.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
3358
3484
|
return JSON.parse(data);
|
|
3359
3485
|
} catch {
|
|
3360
3486
|
return null;
|
|
3361
3487
|
}
|
|
3362
3488
|
}
|
|
3363
3489
|
function getDbPath(repoDir) {
|
|
3364
|
-
return
|
|
3490
|
+
return path33.join(repoDir, ".code-intel", "graph.db");
|
|
3365
3491
|
}
|
|
3366
3492
|
function getVectorDbPath(repoDir) {
|
|
3367
|
-
return
|
|
3493
|
+
return path33.join(repoDir, ".code-intel", "vector.db");
|
|
3368
3494
|
}
|
|
3369
3495
|
var init_metadata = __esm({
|
|
3370
3496
|
"src/storage/metadata.ts"() {
|
|
@@ -3420,27 +3546,27 @@ __export(group_registry_exports, {
|
|
|
3420
3546
|
saveSyncResult: () => saveSyncResult
|
|
3421
3547
|
});
|
|
3422
3548
|
function groupFile(name) {
|
|
3423
|
-
return
|
|
3549
|
+
return path33.join(GROUPS_DIR, `${name}.json`);
|
|
3424
3550
|
}
|
|
3425
3551
|
function loadGroup(name) {
|
|
3426
3552
|
try {
|
|
3427
|
-
return JSON.parse(
|
|
3553
|
+
return JSON.parse(fs33.readFileSync(groupFile(name), "utf-8"));
|
|
3428
3554
|
} catch {
|
|
3429
3555
|
return null;
|
|
3430
3556
|
}
|
|
3431
3557
|
}
|
|
3432
3558
|
function saveGroup(group) {
|
|
3433
|
-
|
|
3434
|
-
|
|
3559
|
+
fs33.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
3560
|
+
fs33.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
3435
3561
|
}
|
|
3436
3562
|
function listGroups() {
|
|
3437
3563
|
const groups = [];
|
|
3438
3564
|
try {
|
|
3439
|
-
for (const file of
|
|
3565
|
+
for (const file of fs33.readdirSync(GROUPS_DIR)) {
|
|
3440
3566
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
3441
3567
|
try {
|
|
3442
3568
|
const g = JSON.parse(
|
|
3443
|
-
|
|
3569
|
+
fs33.readFileSync(path33.join(GROUPS_DIR, file), "utf-8")
|
|
3444
3570
|
);
|
|
3445
3571
|
groups.push(g);
|
|
3446
3572
|
} catch {
|
|
@@ -3452,16 +3578,16 @@ function listGroups() {
|
|
|
3452
3578
|
}
|
|
3453
3579
|
function deleteGroup(name) {
|
|
3454
3580
|
try {
|
|
3455
|
-
|
|
3581
|
+
fs33.unlinkSync(groupFile(name));
|
|
3456
3582
|
} catch {
|
|
3457
3583
|
}
|
|
3458
3584
|
try {
|
|
3459
|
-
|
|
3585
|
+
fs33.unlinkSync(path33.join(GROUPS_DIR, `${name}.sync.json`));
|
|
3460
3586
|
} catch {
|
|
3461
3587
|
}
|
|
3462
3588
|
}
|
|
3463
3589
|
function groupExists(name) {
|
|
3464
|
-
return
|
|
3590
|
+
return fs33.existsSync(groupFile(name));
|
|
3465
3591
|
}
|
|
3466
3592
|
function addMember(groupName, member) {
|
|
3467
3593
|
const group = loadGroup(groupName);
|
|
@@ -3487,16 +3613,16 @@ function removeMember(groupName, groupPath) {
|
|
|
3487
3613
|
return group;
|
|
3488
3614
|
}
|
|
3489
3615
|
function saveSyncResult(result) {
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3616
|
+
fs33.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
3617
|
+
fs33.writeFileSync(
|
|
3618
|
+
path33.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
3493
3619
|
JSON.stringify(result, null, 2) + "\n"
|
|
3494
3620
|
);
|
|
3495
3621
|
}
|
|
3496
3622
|
function loadSyncResult(groupName) {
|
|
3497
3623
|
try {
|
|
3498
3624
|
return JSON.parse(
|
|
3499
|
-
|
|
3625
|
+
fs33.readFileSync(path33.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
3500
3626
|
);
|
|
3501
3627
|
} catch {
|
|
3502
3628
|
return null;
|
|
@@ -3505,90 +3631,597 @@ function loadSyncResult(groupName) {
|
|
|
3505
3631
|
var GROUPS_DIR;
|
|
3506
3632
|
var init_group_registry = __esm({
|
|
3507
3633
|
"src/multi-repo/group-registry.ts"() {
|
|
3508
|
-
GROUPS_DIR =
|
|
3634
|
+
GROUPS_DIR = path33.join(os12.homedir(), ".code-intel", "groups");
|
|
3509
3635
|
}
|
|
3510
3636
|
});
|
|
3511
3637
|
|
|
3512
|
-
// src/
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
AppError = class extends Error {
|
|
3529
|
-
constructor(code, message, hint, statusCode = 500, docs) {
|
|
3530
|
-
super(message);
|
|
3531
|
-
this.code = code;
|
|
3532
|
-
this.hint = hint;
|
|
3533
|
-
this.statusCode = statusCode;
|
|
3534
|
-
this.docs = docs;
|
|
3535
|
-
this.name = "AppError";
|
|
3638
|
+
// src/multi-repo/graph-from-db.ts
|
|
3639
|
+
function parseRow(row, kind) {
|
|
3640
|
+
return {
|
|
3641
|
+
id: String(row["id"] ?? ""),
|
|
3642
|
+
kind,
|
|
3643
|
+
name: String(row["name"] ?? ""),
|
|
3644
|
+
filePath: String(row["file_path"] ?? ""),
|
|
3645
|
+
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
3646
|
+
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
3647
|
+
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
3648
|
+
content: row["content"] ? String(row["content"]) : void 0,
|
|
3649
|
+
metadata: row["metadata"] ? (() => {
|
|
3650
|
+
try {
|
|
3651
|
+
return JSON.parse(String(row["metadata"]));
|
|
3652
|
+
} catch {
|
|
3653
|
+
return void 0;
|
|
3536
3654
|
}
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
fs28.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
3546
|
-
if (process.platform !== "win32") {
|
|
3655
|
+
})() : void 0
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
async function loadGraphFromDB(graph, db) {
|
|
3659
|
+
for (const table of ALL_NODE_TABLES) {
|
|
3660
|
+
const kind = TABLE_TO_KIND[table];
|
|
3661
|
+
if (!kind) continue;
|
|
3662
|
+
let rows = [];
|
|
3547
3663
|
try {
|
|
3548
|
-
|
|
3664
|
+
rows = await db.query(`MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`);
|
|
3549
3665
|
} catch {
|
|
3666
|
+
continue;
|
|
3667
|
+
}
|
|
3668
|
+
for (const row of rows) {
|
|
3669
|
+
const node = parseRow({
|
|
3670
|
+
id: row["n.id"],
|
|
3671
|
+
name: row["n.name"],
|
|
3672
|
+
file_path: row["n.file_path"],
|
|
3673
|
+
start_line: row["n.start_line"],
|
|
3674
|
+
end_line: row["n.end_line"],
|
|
3675
|
+
exported: row["n.exported"],
|
|
3676
|
+
content: row["n.content"],
|
|
3677
|
+
metadata: row["n.metadata"]
|
|
3678
|
+
}, kind);
|
|
3679
|
+
if (node.id && node.name) graph.addNode(node);
|
|
3550
3680
|
}
|
|
3551
3681
|
}
|
|
3552
|
-
}
|
|
3553
|
-
function secureChmodFile(file) {
|
|
3554
|
-
if (process.platform === "win32") return;
|
|
3555
3682
|
try {
|
|
3556
|
-
|
|
3683
|
+
const edgeRows = await db.query(
|
|
3684
|
+
`MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
|
|
3685
|
+
);
|
|
3686
|
+
for (const row of edgeRows) {
|
|
3687
|
+
const sourceId = String(row["a.id"] ?? "");
|
|
3688
|
+
const targetId = String(row["b.id"] ?? "");
|
|
3689
|
+
const kind = String(row["e.kind"] ?? "");
|
|
3690
|
+
if (!sourceId || !targetId || !kind) continue;
|
|
3691
|
+
const edge = {
|
|
3692
|
+
id: `${sourceId}::${kind}::${targetId}`,
|
|
3693
|
+
source: sourceId,
|
|
3694
|
+
target: targetId,
|
|
3695
|
+
kind,
|
|
3696
|
+
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
3697
|
+
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
3698
|
+
};
|
|
3699
|
+
graph.addEdge(edge);
|
|
3700
|
+
}
|
|
3557
3701
|
} catch {
|
|
3558
3702
|
}
|
|
3559
3703
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3704
|
+
var TABLE_TO_KIND;
|
|
3705
|
+
var init_graph_from_db = __esm({
|
|
3706
|
+
"src/multi-repo/graph-from-db.ts"() {
|
|
3707
|
+
init_schema();
|
|
3708
|
+
TABLE_TO_KIND = Object.fromEntries(
|
|
3709
|
+
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
});
|
|
3713
|
+
function scanForFiles(root, matcher, maxDepth = 2) {
|
|
3714
|
+
const results = [];
|
|
3715
|
+
function walk2(dir, depth) {
|
|
3716
|
+
if (depth > maxDepth) return;
|
|
3717
|
+
let entries;
|
|
3718
|
+
try {
|
|
3719
|
+
entries = fs33.readdirSync(dir, { withFileTypes: true });
|
|
3720
|
+
} catch {
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
for (const entry of entries) {
|
|
3724
|
+
const full = path33.join(dir, entry.name);
|
|
3725
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
3726
|
+
walk2(full, depth + 1);
|
|
3727
|
+
} else if (entry.isFile() && matcher(entry.name)) {
|
|
3728
|
+
results.push(full);
|
|
3573
3729
|
}
|
|
3574
3730
|
}
|
|
3575
3731
|
}
|
|
3732
|
+
walk2(root, 0);
|
|
3733
|
+
return results;
|
|
3576
3734
|
}
|
|
3577
|
-
var
|
|
3578
|
-
|
|
3579
|
-
"src/shared/fs-secure.ts"() {
|
|
3580
|
-
SECURE_DIR_MODE = 448;
|
|
3581
|
-
SECURE_FILE_MODE = 384;
|
|
3735
|
+
var init_file_scanner = __esm({
|
|
3736
|
+
"src/multi-repo/schema-parsers/file-scanner.ts"() {
|
|
3582
3737
|
}
|
|
3583
3738
|
});
|
|
3584
|
-
function
|
|
3585
|
-
|
|
3739
|
+
function tryParseFile(filePath) {
|
|
3740
|
+
const ext = path33.extname(filePath).toLowerCase();
|
|
3741
|
+
const content = fs33.readFileSync(filePath, "utf-8");
|
|
3742
|
+
if (ext === ".json") {
|
|
3743
|
+
try {
|
|
3744
|
+
return JSON.parse(content);
|
|
3745
|
+
} catch {
|
|
3746
|
+
return null;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
return null;
|
|
3586
3750
|
}
|
|
3587
|
-
function
|
|
3588
|
-
|
|
3589
|
-
|
|
3751
|
+
async function parseOpenAPIContracts(repoRoot) {
|
|
3752
|
+
const files = scanForFiles(repoRoot, (name) => OPENAPI_FILENAMES.has(name));
|
|
3753
|
+
const contracts = [];
|
|
3754
|
+
for (const filePath of files) {
|
|
3755
|
+
const spec = tryParseFile(filePath);
|
|
3756
|
+
if (!spec) continue;
|
|
3757
|
+
const paths = spec["paths"];
|
|
3758
|
+
if (!paths || typeof paths !== "object") continue;
|
|
3759
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
3760
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
3761
|
+
const ops = pathItem;
|
|
3762
|
+
for (const method of HTTP_METHODS) {
|
|
3763
|
+
if (!(method in ops)) continue;
|
|
3764
|
+
const operation = ops[method];
|
|
3765
|
+
if (!operation) continue;
|
|
3766
|
+
const requestBody = operation["requestBody"];
|
|
3767
|
+
const requestSchema = requestBody?.["content"] ? requestBody["content"]["application/json"]?.["schema"] : void 0;
|
|
3768
|
+
const responses = operation["responses"];
|
|
3769
|
+
const ok200 = responses?.["200"];
|
|
3770
|
+
const responseSchema = ok200?.["content"] ? ok200["content"]["application/json"]?.["schema"] : void 0;
|
|
3771
|
+
contracts.push({
|
|
3772
|
+
name: `${method.toUpperCase()} ${pathStr}`,
|
|
3773
|
+
kind: "route",
|
|
3774
|
+
method: method.toUpperCase(),
|
|
3775
|
+
path: pathStr,
|
|
3776
|
+
...requestSchema ? { requestSchema } : {},
|
|
3777
|
+
...responseSchema ? { responseSchema } : {},
|
|
3778
|
+
filePath
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3590
3782
|
}
|
|
3591
|
-
return
|
|
3783
|
+
return contracts;
|
|
3784
|
+
}
|
|
3785
|
+
var OPENAPI_FILENAMES, HTTP_METHODS;
|
|
3786
|
+
var init_openapi_parser = __esm({
|
|
3787
|
+
"src/multi-repo/schema-parsers/openapi-parser.ts"() {
|
|
3788
|
+
init_file_scanner();
|
|
3789
|
+
OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
3790
|
+
"openapi.yaml",
|
|
3791
|
+
"openapi.json",
|
|
3792
|
+
"openapi.yml",
|
|
3793
|
+
"swagger.yaml",
|
|
3794
|
+
"swagger.json",
|
|
3795
|
+
"swagger.yml"
|
|
3796
|
+
]);
|
|
3797
|
+
HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
function extractFieldNames(block) {
|
|
3801
|
+
return block.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
|
|
3802
|
+
const m = line.match(/^(\w+)\s*[(:]/);
|
|
3803
|
+
return m ? m[1] : null;
|
|
3804
|
+
}).filter((f) => f !== null);
|
|
3805
|
+
}
|
|
3806
|
+
async function parseGraphQLContracts(repoRoot) {
|
|
3807
|
+
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
3808
|
+
const contracts = [];
|
|
3809
|
+
for (const filePath of files) {
|
|
3810
|
+
const content = fs33.readFileSync(filePath, "utf-8");
|
|
3811
|
+
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
3812
|
+
let match;
|
|
3813
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
3814
|
+
const typeName = match[1];
|
|
3815
|
+
const body = match[2];
|
|
3816
|
+
const fields = extractFieldNames(body);
|
|
3817
|
+
const lcName = typeName.toLowerCase();
|
|
3818
|
+
if (lcName === "query") {
|
|
3819
|
+
for (const field of fields) {
|
|
3820
|
+
contracts.push({ name: `query.${field}`, kind: "graphql", operation: "query", fields, filePath });
|
|
3821
|
+
}
|
|
3822
|
+
} else if (lcName === "mutation") {
|
|
3823
|
+
for (const field of fields) {
|
|
3824
|
+
contracts.push({ name: `mutation.${field}`, kind: "graphql", operation: "mutation", fields, filePath });
|
|
3825
|
+
}
|
|
3826
|
+
} else if (lcName === "subscription") {
|
|
3827
|
+
for (const field of fields) {
|
|
3828
|
+
contracts.push({ name: `subscription.${field}`, kind: "graphql", operation: "subscription", fields, filePath });
|
|
3829
|
+
}
|
|
3830
|
+
} else {
|
|
3831
|
+
contracts.push({ name: `type.${typeName}`, kind: "graphql", operation: "type", fields, filePath });
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
return contracts;
|
|
3836
|
+
}
|
|
3837
|
+
var init_graphql_parser = __esm({
|
|
3838
|
+
"src/multi-repo/schema-parsers/graphql-parser.ts"() {
|
|
3839
|
+
init_file_scanner();
|
|
3840
|
+
}
|
|
3841
|
+
});
|
|
3842
|
+
async function parseProtoContracts(repoRoot) {
|
|
3843
|
+
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
3844
|
+
const contracts = [];
|
|
3845
|
+
for (const filePath of files) {
|
|
3846
|
+
const content = fs33.readFileSync(filePath, "utf-8");
|
|
3847
|
+
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
3848
|
+
let serviceMatch;
|
|
3849
|
+
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
3850
|
+
const serviceName = serviceMatch[1];
|
|
3851
|
+
const body = serviceMatch[2];
|
|
3852
|
+
const rpcRegex = /rpc\s+(\w+)\s*\((\w+)\)\s*returns\s*\((\w+)\)/g;
|
|
3853
|
+
let rpcMatch;
|
|
3854
|
+
while ((rpcMatch = rpcRegex.exec(body)) !== null) {
|
|
3855
|
+
const rpcName = rpcMatch[1];
|
|
3856
|
+
const inputType = rpcMatch[2];
|
|
3857
|
+
const outputType = rpcMatch[3];
|
|
3858
|
+
contracts.push({
|
|
3859
|
+
name: `${serviceName}.${rpcName}`,
|
|
3860
|
+
kind: "grpc",
|
|
3861
|
+
serviceName,
|
|
3862
|
+
rpcName,
|
|
3863
|
+
inputType,
|
|
3864
|
+
outputType,
|
|
3865
|
+
filePath
|
|
3866
|
+
});
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
return contracts;
|
|
3871
|
+
}
|
|
3872
|
+
var init_proto_parser = __esm({
|
|
3873
|
+
"src/multi-repo/schema-parsers/proto-parser.ts"() {
|
|
3874
|
+
init_file_scanner();
|
|
3875
|
+
}
|
|
3876
|
+
});
|
|
3877
|
+
|
|
3878
|
+
// src/multi-repo/type-similarity.ts
|
|
3879
|
+
function normalizeType(t) {
|
|
3880
|
+
return t.toLowerCase().replace(/[\[\]?<>\s]/g, "").trim();
|
|
3881
|
+
}
|
|
3882
|
+
function paramTypeSimilarity(paramsA, paramsB) {
|
|
3883
|
+
if (paramsA.length === 0 && paramsB.length === 0) return 1;
|
|
3884
|
+
if (paramsA.length === 0 || paramsB.length === 0) return 0;
|
|
3885
|
+
const setA = new Set(paramsA.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
|
|
3886
|
+
const setB = new Set(paramsB.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
|
|
3887
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
3888
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
3889
|
+
const intersection = [...setA].filter((x) => setB.has(x)).length;
|
|
3890
|
+
const union = (/* @__PURE__ */ new Set([...setA, ...setB])).size;
|
|
3891
|
+
return intersection / union;
|
|
3892
|
+
}
|
|
3893
|
+
function returnTypeSimilarity(typeA, typeB) {
|
|
3894
|
+
if (!typeA || !typeB) return 0.5;
|
|
3895
|
+
const a = normalizeType(typeA);
|
|
3896
|
+
const b = normalizeType(typeB);
|
|
3897
|
+
if (a === b) return 1;
|
|
3898
|
+
const compatible = [
|
|
3899
|
+
["string", "str"],
|
|
3900
|
+
["number", "int"],
|
|
3901
|
+
["number", "float"],
|
|
3902
|
+
["number", "double"],
|
|
3903
|
+
["boolean", "bool"],
|
|
3904
|
+
["void", "unit"],
|
|
3905
|
+
["void", "none"]
|
|
3906
|
+
];
|
|
3907
|
+
for (const [x, y] of compatible) {
|
|
3908
|
+
if (a === x && b === y || a === y && b === x) return 0.8;
|
|
3909
|
+
}
|
|
3910
|
+
return 0;
|
|
3911
|
+
}
|
|
3912
|
+
function paramCountSimilarity(countA, countB) {
|
|
3913
|
+
const maxCount = Math.max(countA, countB, 1);
|
|
3914
|
+
return 1 - Math.abs(countA - countB) / maxCount;
|
|
3915
|
+
}
|
|
3916
|
+
function computeContractSimilarity(a, b, nameSim) {
|
|
3917
|
+
const paramsA = a.parameters ?? [];
|
|
3918
|
+
const paramsB = b.parameters ?? [];
|
|
3919
|
+
const ptSim = paramTypeSimilarity(paramsA, paramsB);
|
|
3920
|
+
const rtSim = returnTypeSimilarity(a.returnType, b.returnType);
|
|
3921
|
+
const pcSim = paramCountSimilarity(paramsA.length, paramsB.length);
|
|
3922
|
+
let score = 0.4 * nameSim + 0.3 * ptSim + 0.2 * rtSim + 0.1 * pcSim;
|
|
3923
|
+
if (ptSim > 0.8) {
|
|
3924
|
+
score = Math.min(1, score * 1.2);
|
|
3925
|
+
}
|
|
3926
|
+
return score;
|
|
3927
|
+
}
|
|
3928
|
+
var init_type_similarity = __esm({
|
|
3929
|
+
"src/multi-repo/type-similarity.ts"() {
|
|
3930
|
+
}
|
|
3931
|
+
});
|
|
3932
|
+
|
|
3933
|
+
// src/multi-repo/group-sync.ts
|
|
3934
|
+
var group_sync_exports = {};
|
|
3935
|
+
__export(group_sync_exports, {
|
|
3936
|
+
syncGroup: () => syncGroup
|
|
3937
|
+
});
|
|
3938
|
+
function extractContracts(graph, repoName, repoPath) {
|
|
3939
|
+
const contracts = [];
|
|
3940
|
+
for (const node of graph.allNodes()) {
|
|
3941
|
+
if (node.exported === true && ["function", "class", "interface", "method", "type_alias", "constant", "enum", "struct", "trait"].includes(node.kind)) {
|
|
3942
|
+
contracts.push({
|
|
3943
|
+
repoName,
|
|
3944
|
+
repoPath,
|
|
3945
|
+
kind: "export",
|
|
3946
|
+
name: node.name,
|
|
3947
|
+
nodeId: node.id,
|
|
3948
|
+
nodeKind: node.kind,
|
|
3949
|
+
filePath: node.filePath,
|
|
3950
|
+
signature: node.content?.split("\n")[0]?.trim(),
|
|
3951
|
+
parameters: node.metadata?.parameters ?? node.metadata?.params,
|
|
3952
|
+
returnType: node.metadata?.returnType,
|
|
3953
|
+
exported: node.exported
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
if (node.kind === "route") {
|
|
3957
|
+
contracts.push({
|
|
3958
|
+
repoName,
|
|
3959
|
+
repoPath,
|
|
3960
|
+
kind: "route",
|
|
3961
|
+
name: node.name,
|
|
3962
|
+
nodeId: node.id,
|
|
3963
|
+
nodeKind: node.kind,
|
|
3964
|
+
filePath: node.filePath,
|
|
3965
|
+
signature: node.content?.split("\n")[0]?.trim()
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3968
|
+
if (["interface", "type_alias"].includes(node.kind)) {
|
|
3969
|
+
const nameLower = node.name.toLowerCase();
|
|
3970
|
+
if (nameLower.includes("event") || nameLower.includes("message")) {
|
|
3971
|
+
contracts.push({
|
|
3972
|
+
repoName,
|
|
3973
|
+
repoPath,
|
|
3974
|
+
kind: "event",
|
|
3975
|
+
name: node.name,
|
|
3976
|
+
nodeId: node.id,
|
|
3977
|
+
nodeKind: node.kind,
|
|
3978
|
+
filePath: node.filePath
|
|
3979
|
+
});
|
|
3980
|
+
} else if (nameLower.includes("schema") || nameLower.includes("dto") || nameLower.includes("request") || nameLower.includes("response")) {
|
|
3981
|
+
contracts.push({
|
|
3982
|
+
repoName,
|
|
3983
|
+
repoPath,
|
|
3984
|
+
kind: "schema",
|
|
3985
|
+
name: node.name,
|
|
3986
|
+
nodeId: node.id,
|
|
3987
|
+
nodeKind: node.kind,
|
|
3988
|
+
filePath: node.filePath
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
return contracts;
|
|
3994
|
+
}
|
|
3995
|
+
function matchContracts(allContracts) {
|
|
3996
|
+
const links = [];
|
|
3997
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
3998
|
+
for (const c of allContracts) {
|
|
3999
|
+
const arr = byRepo.get(c.repoName) ?? [];
|
|
4000
|
+
arr.push(c);
|
|
4001
|
+
byRepo.set(c.repoName, arr);
|
|
4002
|
+
}
|
|
4003
|
+
const repoNames = [...byRepo.keys()];
|
|
4004
|
+
for (let i = 0; i < repoNames.length; i++) {
|
|
4005
|
+
for (let j = 0; j < repoNames.length; j++) {
|
|
4006
|
+
if (i === j) continue;
|
|
4007
|
+
const providerContracts = byRepo.get(repoNames[i]);
|
|
4008
|
+
const consumerContracts = byRepo.get(repoNames[j]);
|
|
4009
|
+
const consumerByName = /* @__PURE__ */ new Map();
|
|
4010
|
+
for (const c of consumerContracts) consumerByName.set(c.name, c);
|
|
4011
|
+
for (const provider of providerContracts) {
|
|
4012
|
+
const consumer = consumerByName.get(provider.name);
|
|
4013
|
+
if (consumer) {
|
|
4014
|
+
const sameKind = provider.kind === consumer.kind;
|
|
4015
|
+
const typedScore = computeContractSimilarity(provider, consumer, 1);
|
|
4016
|
+
const confidence = sameKind ? Math.max(typedScore, 0.9) : Math.max(typedScore, 0.6);
|
|
4017
|
+
links.push({
|
|
4018
|
+
providerRepo: provider.repoName,
|
|
4019
|
+
providerContract: provider.name,
|
|
4020
|
+
consumerRepo: consumer.repoName,
|
|
4021
|
+
consumerContract: consumer.name,
|
|
4022
|
+
matchKind: provider.kind === "route" ? "route-match" : "name-match",
|
|
4023
|
+
confidence: Math.min(1, confidence)
|
|
4024
|
+
});
|
|
4025
|
+
} else {
|
|
4026
|
+
const providerLC = provider.name.toLowerCase();
|
|
4027
|
+
for (const c of consumerContracts) {
|
|
4028
|
+
if (c.name.toLowerCase().includes(providerLC) || providerLC.includes(c.name.toLowerCase())) {
|
|
4029
|
+
if (c.name.length >= 4 && provider.name.length >= 4) {
|
|
4030
|
+
links.push({
|
|
4031
|
+
providerRepo: provider.repoName,
|
|
4032
|
+
providerContract: provider.name,
|
|
4033
|
+
consumerRepo: c.repoName,
|
|
4034
|
+
consumerContract: c.name,
|
|
4035
|
+
matchKind: "name-match",
|
|
4036
|
+
confidence: 0.4
|
|
4037
|
+
});
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4046
|
+
for (const link of links) {
|
|
4047
|
+
const key = `${link.providerRepo}:${link.providerContract}:${link.consumerRepo}:${link.consumerContract}`;
|
|
4048
|
+
const existing = seen.get(key);
|
|
4049
|
+
if (!existing || link.confidence > existing.confidence) {
|
|
4050
|
+
seen.set(key, link);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
return [...seen.values()].sort((a, b) => b.confidence - a.confidence);
|
|
4054
|
+
}
|
|
4055
|
+
async function syncGroup(group) {
|
|
4056
|
+
const registry = loadRegistry();
|
|
4057
|
+
const allContracts = [];
|
|
4058
|
+
for (const member of group.members) {
|
|
4059
|
+
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
4060
|
+
if (!regEntry) {
|
|
4061
|
+
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
4062
|
+
continue;
|
|
4063
|
+
}
|
|
4064
|
+
const dbPath = path33.join(regEntry.path, ".code-intel", "graph.db");
|
|
4065
|
+
if (!fs33.existsSync(dbPath)) {
|
|
4066
|
+
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
4067
|
+
continue;
|
|
4068
|
+
}
|
|
4069
|
+
const graph = createKnowledgeGraph();
|
|
4070
|
+
const db = new DbManager(dbPath);
|
|
4071
|
+
try {
|
|
4072
|
+
await db.init();
|
|
4073
|
+
await loadGraphFromDB(graph, db);
|
|
4074
|
+
db.close();
|
|
4075
|
+
} catch (err) {
|
|
4076
|
+
db.close();
|
|
4077
|
+
logger_default.warn(` \u26A0 Could not load graph for "${member.registryName}": ${err instanceof Error ? err.message : err}`);
|
|
4078
|
+
continue;
|
|
4079
|
+
}
|
|
4080
|
+
const contracts = extractContracts(graph, member.registryName, regEntry.path);
|
|
4081
|
+
const [openapiContracts, graphqlContracts, protoContracts] = await Promise.all([
|
|
4082
|
+
parseOpenAPIContracts(regEntry.path).catch(() => []),
|
|
4083
|
+
parseGraphQLContracts(regEntry.path).catch(() => []),
|
|
4084
|
+
parseProtoContracts(regEntry.path).catch(() => [])
|
|
4085
|
+
]);
|
|
4086
|
+
for (const c of openapiContracts) {
|
|
4087
|
+
contracts.push({
|
|
4088
|
+
repoName: member.registryName,
|
|
4089
|
+
repoPath: regEntry.path,
|
|
4090
|
+
kind: "route",
|
|
4091
|
+
name: c.name,
|
|
4092
|
+
nodeId: `openapi:${c.method}:${c.path}`,
|
|
4093
|
+
nodeKind: "route",
|
|
4094
|
+
filePath: c.filePath
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
for (const c of graphqlContracts) {
|
|
4098
|
+
contracts.push({
|
|
4099
|
+
repoName: member.registryName,
|
|
4100
|
+
repoPath: regEntry.path,
|
|
4101
|
+
kind: "graphql",
|
|
4102
|
+
name: c.name,
|
|
4103
|
+
nodeId: `graphql:${c.name}`,
|
|
4104
|
+
nodeKind: "graphql",
|
|
4105
|
+
filePath: c.filePath
|
|
4106
|
+
});
|
|
4107
|
+
}
|
|
4108
|
+
for (const c of protoContracts) {
|
|
4109
|
+
contracts.push({
|
|
4110
|
+
repoName: member.registryName,
|
|
4111
|
+
repoPath: regEntry.path,
|
|
4112
|
+
kind: "grpc",
|
|
4113
|
+
name: c.name,
|
|
4114
|
+
nodeId: `grpc:${c.serviceName}:${c.rpcName}`,
|
|
4115
|
+
nodeKind: "grpc",
|
|
4116
|
+
filePath: c.filePath
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
logger_default.info(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
|
|
4120
|
+
allContracts.push(...contracts);
|
|
4121
|
+
}
|
|
4122
|
+
const links = matchContracts(allContracts);
|
|
4123
|
+
return {
|
|
4124
|
+
groupName: group.name,
|
|
4125
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4126
|
+
memberCount: group.members.length,
|
|
4127
|
+
contracts: allContracts,
|
|
4128
|
+
links
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
var init_group_sync = __esm({
|
|
4132
|
+
"src/multi-repo/group-sync.ts"() {
|
|
4133
|
+
init_repo_registry();
|
|
4134
|
+
init_db_manager();
|
|
4135
|
+
init_knowledge_graph();
|
|
4136
|
+
init_graph_from_db();
|
|
4137
|
+
init_logger();
|
|
4138
|
+
init_openapi_parser();
|
|
4139
|
+
init_graphql_parser();
|
|
4140
|
+
init_proto_parser();
|
|
4141
|
+
init_type_similarity();
|
|
4142
|
+
}
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
// src/errors/codes.ts
|
|
4146
|
+
var ErrorCodes, AppError;
|
|
4147
|
+
var init_codes = __esm({
|
|
4148
|
+
"src/errors/codes.ts"() {
|
|
4149
|
+
ErrorCodes = {
|
|
4150
|
+
UNAUTHORIZED: "CI-1000",
|
|
4151
|
+
FORBIDDEN: "CI-1001",
|
|
4152
|
+
NOT_FOUND: "CI-1002",
|
|
4153
|
+
ANALYSIS_IN_PROGRESS: "CI-1003",
|
|
4154
|
+
INDEX_NOT_FOUND: "CI-1004",
|
|
4155
|
+
DB_CORRUPTED: "CI-1042",
|
|
4156
|
+
RATE_LIMIT_EXCEEDED: "CI-1100",
|
|
4157
|
+
PAYLOAD_TOO_LARGE: "CI-1101",
|
|
4158
|
+
INVALID_REQUEST: "CI-1200",
|
|
4159
|
+
INTERNAL_ERROR: "CI-5000"
|
|
4160
|
+
};
|
|
4161
|
+
AppError = class extends Error {
|
|
4162
|
+
constructor(code, message, hint, statusCode = 500, docs) {
|
|
4163
|
+
super(message);
|
|
4164
|
+
this.code = code;
|
|
4165
|
+
this.hint = hint;
|
|
4166
|
+
this.statusCode = statusCode;
|
|
4167
|
+
this.docs = docs;
|
|
4168
|
+
this.name = "AppError";
|
|
4169
|
+
}
|
|
4170
|
+
code;
|
|
4171
|
+
hint;
|
|
4172
|
+
statusCode;
|
|
4173
|
+
docs;
|
|
4174
|
+
};
|
|
4175
|
+
}
|
|
4176
|
+
});
|
|
4177
|
+
function secureMkdir(dir) {
|
|
4178
|
+
fs33.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
4179
|
+
if (process.platform !== "win32") {
|
|
4180
|
+
try {
|
|
4181
|
+
fs33.chmodSync(dir, SECURE_DIR_MODE);
|
|
4182
|
+
} catch {
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
function secureChmodFile(file) {
|
|
4187
|
+
if (process.platform === "win32") return;
|
|
4188
|
+
try {
|
|
4189
|
+
fs33.chmodSync(file, SECURE_FILE_MODE);
|
|
4190
|
+
} catch {
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
function secureWriteFile(file, data) {
|
|
4194
|
+
secureMkdir(path33.dirname(file));
|
|
4195
|
+
fs33.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
|
|
4196
|
+
secureChmodFile(file);
|
|
4197
|
+
}
|
|
4198
|
+
function tightenDbFiles(dir) {
|
|
4199
|
+
if (process.platform === "win32") return;
|
|
4200
|
+
if (!fs33.existsSync(dir)) return;
|
|
4201
|
+
for (const name of fs33.readdirSync(dir)) {
|
|
4202
|
+
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
4203
|
+
try {
|
|
4204
|
+
fs33.chmodSync(path33.join(dir, name), SECURE_FILE_MODE);
|
|
4205
|
+
} catch {
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
var SECURE_DIR_MODE, SECURE_FILE_MODE;
|
|
4211
|
+
var init_fs_secure = __esm({
|
|
4212
|
+
"src/shared/fs-secure.ts"() {
|
|
4213
|
+
SECURE_DIR_MODE = 448;
|
|
4214
|
+
SECURE_FILE_MODE = 384;
|
|
4215
|
+
}
|
|
4216
|
+
});
|
|
4217
|
+
function getUsersDBPath() {
|
|
4218
|
+
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path33.join(os12.homedir(), ".code-intel", "users.db");
|
|
4219
|
+
}
|
|
4220
|
+
function getOrCreateUsersDB() {
|
|
4221
|
+
if (!_usersDB) {
|
|
4222
|
+
_usersDB = new UsersDB(getUsersDBPath());
|
|
4223
|
+
}
|
|
4224
|
+
return _usersDB;
|
|
3592
4225
|
}
|
|
3593
4226
|
var BCRYPT_ROUNDS, UsersDB, _usersDB;
|
|
3594
4227
|
var init_users_db = __esm({
|
|
@@ -3598,7 +4231,7 @@ var init_users_db = __esm({
|
|
|
3598
4231
|
UsersDB = class {
|
|
3599
4232
|
db;
|
|
3600
4233
|
constructor(dbPath) {
|
|
3601
|
-
const dir =
|
|
4234
|
+
const dir = path33.dirname(dbPath);
|
|
3602
4235
|
secureMkdir(dir);
|
|
3603
4236
|
this.db = new Database(dbPath);
|
|
3604
4237
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3875,7 +4508,7 @@ function getScryptN() {
|
|
|
3875
4508
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
3876
4509
|
}
|
|
3877
4510
|
function getSecretsPath() {
|
|
3878
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ??
|
|
4511
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path33.join(os12.homedir(), ".code-intel", ".secrets");
|
|
3879
4512
|
}
|
|
3880
4513
|
function getMasterPassword() {
|
|
3881
4514
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
@@ -3917,12 +4550,12 @@ function decryptSecrets(encrypted) {
|
|
|
3917
4550
|
return JSON.parse(plaintext.toString("utf8"));
|
|
3918
4551
|
}
|
|
3919
4552
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
3920
|
-
if (!
|
|
3921
|
-
const blob =
|
|
4553
|
+
if (!fs33.existsSync(secretsPath)) return {};
|
|
4554
|
+
const blob = fs33.readFileSync(secretsPath);
|
|
3922
4555
|
return decryptSecrets(blob);
|
|
3923
4556
|
}
|
|
3924
4557
|
function saveSecrets(blob, secretsPath = getSecretsPath()) {
|
|
3925
|
-
secureMkdir(
|
|
4558
|
+
secureMkdir(path33.dirname(secretsPath));
|
|
3926
4559
|
const encrypted = encryptSecrets(blob);
|
|
3927
4560
|
secureWriteFile(secretsPath, encrypted);
|
|
3928
4561
|
secureChmodFile(secretsPath);
|
|
@@ -4056,6 +4689,9 @@ function requireAuth(req, res, next) {
|
|
|
4056
4689
|
}
|
|
4057
4690
|
next();
|
|
4058
4691
|
}
|
|
4692
|
+
function meetsRole(userRole, required) {
|
|
4693
|
+
return (ROLE_RANK[userRole] ?? 0) >= (ROLE_RANK[required] ?? 0);
|
|
4694
|
+
}
|
|
4059
4695
|
function requireRole(...roles) {
|
|
4060
4696
|
return (req, res, next) => {
|
|
4061
4697
|
if (!req.user) {
|
|
@@ -4070,7 +4706,8 @@ function requireRole(...roles) {
|
|
|
4070
4706
|
});
|
|
4071
4707
|
return;
|
|
4072
4708
|
}
|
|
4073
|
-
|
|
4709
|
+
const allowed = roles.some((r) => meetsRole(req.user.role, r));
|
|
4710
|
+
if (!allowed) {
|
|
4074
4711
|
res.status(403).json({
|
|
4075
4712
|
error: {
|
|
4076
4713
|
code: ErrorCodes.FORBIDDEN,
|
|
@@ -4179,7 +4816,7 @@ function clearSessionCookie() {
|
|
|
4179
4816
|
async function verifyPassword(plain, hash) {
|
|
4180
4817
|
return bcrypt.compare(plain, hash);
|
|
4181
4818
|
}
|
|
4182
|
-
var sessionStore, SESSION_COOKIE_NAME;
|
|
4819
|
+
var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
|
|
4183
4820
|
var init_middleware = __esm({
|
|
4184
4821
|
"src/auth/middleware.ts"() {
|
|
4185
4822
|
init_users_db();
|
|
@@ -4187,6 +4824,12 @@ var init_middleware = __esm({
|
|
|
4187
4824
|
init_secret_store();
|
|
4188
4825
|
sessionStore = /* @__PURE__ */ new Map();
|
|
4189
4826
|
SESSION_COOKIE_NAME = "code_intel_session";
|
|
4827
|
+
ROLE_RANK = {
|
|
4828
|
+
viewer: 1,
|
|
4829
|
+
"repo-owner": 2,
|
|
4830
|
+
analyst: 3,
|
|
4831
|
+
admin: 4
|
|
4832
|
+
};
|
|
4190
4833
|
}
|
|
4191
4834
|
});
|
|
4192
4835
|
|
|
@@ -5267,6 +5910,117 @@ var init_websocket_server = __esm({
|
|
|
5267
5910
|
}
|
|
5268
5911
|
});
|
|
5269
5912
|
|
|
5913
|
+
// src/query/pr-impact.ts
|
|
5914
|
+
var pr_impact_exports = {};
|
|
5915
|
+
__export(pr_impact_exports, {
|
|
5916
|
+
computePRImpact: () => computePRImpact,
|
|
5917
|
+
parseDiffFiles: () => parseDiffFiles
|
|
5918
|
+
});
|
|
5919
|
+
function parseDiffFiles(diff) {
|
|
5920
|
+
const files = [];
|
|
5921
|
+
for (const line of diff.split("\n")) {
|
|
5922
|
+
const match = line.match(/^\+\+\+ b\/(.+)/);
|
|
5923
|
+
if (match) {
|
|
5924
|
+
files.push(match[1]);
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
return files;
|
|
5928
|
+
}
|
|
5929
|
+
function computePRImpact(graph, changedFiles, maxHops) {
|
|
5930
|
+
const changedSymbolIds = /* @__PURE__ */ new Set();
|
|
5931
|
+
for (const node of graph.allNodes()) {
|
|
5932
|
+
if (!node.filePath) continue;
|
|
5933
|
+
for (const changedFile of changedFiles) {
|
|
5934
|
+
if (node.filePath === changedFile || node.filePath.endsWith(changedFile) || changedFile.endsWith(node.filePath)) {
|
|
5935
|
+
changedSymbolIds.add(node.id);
|
|
5936
|
+
break;
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
const allBlastRadiusNodes = /* @__PURE__ */ new Set();
|
|
5941
|
+
const changedSymbols = [];
|
|
5942
|
+
for (const symbolId of changedSymbolIds) {
|
|
5943
|
+
const symbolNode = graph.getNode(symbolId);
|
|
5944
|
+
if (!symbolNode) continue;
|
|
5945
|
+
const blastRadius = /* @__PURE__ */ new Set();
|
|
5946
|
+
const queue = [{ id: symbolId, depth: 0 }];
|
|
5947
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5948
|
+
while (queue.length > 0) {
|
|
5949
|
+
const { id, depth } = queue.shift();
|
|
5950
|
+
if (visited.has(id) || depth > maxHops) continue;
|
|
5951
|
+
visited.add(id);
|
|
5952
|
+
if (id !== symbolId) blastRadius.add(id);
|
|
5953
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
5954
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
5955
|
+
queue.push({ id: edge.source, depth: depth + 1 });
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
for (const id of blastRadius) allBlastRadiusNodes.add(id);
|
|
5960
|
+
const blastCount = blastRadius.size;
|
|
5961
|
+
let risk;
|
|
5962
|
+
if (blastCount > 50) {
|
|
5963
|
+
risk = "HIGH";
|
|
5964
|
+
} else if (blastCount >= 10) {
|
|
5965
|
+
risk = "MEDIUM";
|
|
5966
|
+
} else {
|
|
5967
|
+
risk = "LOW";
|
|
5968
|
+
}
|
|
5969
|
+
let callerCount = 0;
|
|
5970
|
+
for (const edge of graph.findEdgesTo(symbolId)) {
|
|
5971
|
+
if (edge.kind === "calls") callerCount++;
|
|
5972
|
+
}
|
|
5973
|
+
let testCoverage = false;
|
|
5974
|
+
for (const edge of graph.findEdgesTo(symbolId)) {
|
|
5975
|
+
if (edge.kind === "imports") {
|
|
5976
|
+
const callerNode = graph.getNode(edge.source);
|
|
5977
|
+
if (callerNode?.filePath && (callerNode.filePath.includes(".test.") || callerNode.filePath.includes(".spec."))) {
|
|
5978
|
+
testCoverage = true;
|
|
5979
|
+
break;
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
changedSymbols.push({ name: symbolNode.name, risk, callerCount, testCoverage });
|
|
5984
|
+
}
|
|
5985
|
+
const impactedSymbols = [];
|
|
5986
|
+
for (const id of allBlastRadiusNodes) {
|
|
5987
|
+
if (changedSymbolIds.has(id)) continue;
|
|
5988
|
+
const node = graph.getNode(id);
|
|
5989
|
+
if (node) {
|
|
5990
|
+
impactedSymbols.push({ name: node.name, filePath: node.filePath });
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
const riskSummary = { HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
5994
|
+
for (const s of changedSymbols) {
|
|
5995
|
+
riskSummary[s.risk]++;
|
|
5996
|
+
}
|
|
5997
|
+
const coverageGaps = [];
|
|
5998
|
+
for (const s of changedSymbols) {
|
|
5999
|
+
if ((s.risk === "HIGH" || s.risk === "MEDIUM") && !s.testCoverage) {
|
|
6000
|
+
coverageGaps.push(`${s.name} has no test coverage`);
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
const fileImpactCount = /* @__PURE__ */ new Map();
|
|
6004
|
+
for (const sym of impactedSymbols) {
|
|
6005
|
+
if (sym.filePath) {
|
|
6006
|
+
fileImpactCount.set(sym.filePath, (fileImpactCount.get(sym.filePath) ?? 0) + 1);
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
const filesToReview = [...fileImpactCount.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([fp]) => fp);
|
|
6010
|
+
return {
|
|
6011
|
+
changedSymbols,
|
|
6012
|
+
impactedSymbols,
|
|
6013
|
+
riskSummary,
|
|
6014
|
+
coverageGaps,
|
|
6015
|
+
filesToReview,
|
|
6016
|
+
crossRepoImpact: null
|
|
6017
|
+
};
|
|
6018
|
+
}
|
|
6019
|
+
var init_pr_impact = __esm({
|
|
6020
|
+
"src/query/pr-impact.ts"() {
|
|
6021
|
+
}
|
|
6022
|
+
});
|
|
6023
|
+
|
|
5270
6024
|
// src/health/dead-code.ts
|
|
5271
6025
|
function detectDeadCode(graph) {
|
|
5272
6026
|
const results = [];
|
|
@@ -5516,9 +6270,9 @@ var init_orphan_files = __esm({
|
|
|
5516
6270
|
// src/health/health-score.ts
|
|
5517
6271
|
var health_score_exports = {};
|
|
5518
6272
|
__export(health_score_exports, {
|
|
5519
|
-
computeHealthReport: () =>
|
|
6273
|
+
computeHealthReport: () => computeHealthReport2
|
|
5520
6274
|
});
|
|
5521
|
-
function
|
|
6275
|
+
function computeHealthReport2(graph, godNodeConfig) {
|
|
5522
6276
|
const deadCode = detectDeadCode(graph);
|
|
5523
6277
|
const cycles = detectCircularDeps(graph);
|
|
5524
6278
|
const godNodes = detectGodNodes(graph, godNodeConfig);
|
|
@@ -5622,10 +6376,10 @@ var init_file_watcher = __esm({
|
|
|
5622
6376
|
}
|
|
5623
6377
|
// ── private ─────────────────────────────────────────────────────────────────
|
|
5624
6378
|
readCodeIntelIgnore() {
|
|
5625
|
-
const ignoreFile =
|
|
6379
|
+
const ignoreFile = path33.join(this.workspaceRoot, ".codeintelignore");
|
|
5626
6380
|
try {
|
|
5627
|
-
if (!
|
|
5628
|
-
return
|
|
6381
|
+
if (!fs33.existsSync(ignoreFile)) return [];
|
|
6382
|
+
return fs33.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
5629
6383
|
} catch {
|
|
5630
6384
|
return [];
|
|
5631
6385
|
}
|
|
@@ -5677,7 +6431,7 @@ var init_incremental_indexer = __esm({
|
|
|
5677
6431
|
}
|
|
5678
6432
|
const nodeIdsToRemove = /* @__PURE__ */ new Set();
|
|
5679
6433
|
for (const absPath of changedFiles) {
|
|
5680
|
-
const relPath2 =
|
|
6434
|
+
const relPath2 = path33.relative(workspaceRoot, absPath);
|
|
5681
6435
|
for (const id of nodesByFilePath.get(relPath2) ?? []) nodeIdsToRemove.add(id);
|
|
5682
6436
|
for (const id of nodesByFilePath.get(absPath) ?? []) nodeIdsToRemove.add(id);
|
|
5683
6437
|
}
|
|
@@ -5685,12 +6439,12 @@ var init_incremental_indexer = __esm({
|
|
|
5685
6439
|
graph.removeNodeCascade(id);
|
|
5686
6440
|
nodesRemoved++;
|
|
5687
6441
|
}
|
|
5688
|
-
if (
|
|
6442
|
+
if (fs33.existsSync(dbPath)) {
|
|
5689
6443
|
try {
|
|
5690
6444
|
const db = new DbManager(dbPath);
|
|
5691
6445
|
await db.init();
|
|
5692
6446
|
for (const absPath of changedFiles) {
|
|
5693
|
-
const relPath2 =
|
|
6447
|
+
const relPath2 = path33.relative(workspaceRoot, absPath);
|
|
5694
6448
|
await removeNodesForFile(relPath2, db);
|
|
5695
6449
|
}
|
|
5696
6450
|
db.close();
|
|
@@ -5700,7 +6454,7 @@ var init_incremental_indexer = __esm({
|
|
|
5700
6454
|
}
|
|
5701
6455
|
const existingFiles = changedFiles.filter((f) => {
|
|
5702
6456
|
try {
|
|
5703
|
-
return
|
|
6457
|
+
return fs33.statSync(f).isFile();
|
|
5704
6458
|
} catch {
|
|
5705
6459
|
return false;
|
|
5706
6460
|
}
|
|
@@ -5722,13 +6476,13 @@ var init_incremental_indexer = __esm({
|
|
|
5722
6476
|
await runPipeline([noopScan, parsePhase, resolvePhase], context2);
|
|
5723
6477
|
}
|
|
5724
6478
|
const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
|
|
5725
|
-
if (
|
|
6479
|
+
if (fs33.existsSync(dbPath) && existingFiles.length > 0) {
|
|
5726
6480
|
try {
|
|
5727
6481
|
const db = new DbManager(dbPath);
|
|
5728
6482
|
await db.init();
|
|
5729
|
-
const changedRelPaths = new Set(changedFiles.map((f) =>
|
|
6483
|
+
const changedRelPaths = new Set(changedFiles.map((f) => path33.relative(workspaceRoot, f)));
|
|
5730
6484
|
const nodesToUpsert = [...graph.allNodes()].filter(
|
|
5731
|
-
(n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(
|
|
6485
|
+
(n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path33.relative(workspaceRoot, n.filePath))
|
|
5732
6486
|
);
|
|
5733
6487
|
await upsertNodes(nodesToUpsert, db);
|
|
5734
6488
|
db.close();
|
|
@@ -5759,35 +6513,35 @@ __export(saved_queries_exports, {
|
|
|
5759
6513
|
saveQuery: () => saveQuery
|
|
5760
6514
|
});
|
|
5761
6515
|
function getQueriesDir(workspaceRoot) {
|
|
5762
|
-
return
|
|
6516
|
+
return path33.join(workspaceRoot, ".code-intel", "queries");
|
|
5763
6517
|
}
|
|
5764
6518
|
function ensureQueriesDir(workspaceRoot) {
|
|
5765
6519
|
const dir = getQueriesDir(workspaceRoot);
|
|
5766
|
-
if (!
|
|
5767
|
-
|
|
6520
|
+
if (!fs33.existsSync(dir)) {
|
|
6521
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
5768
6522
|
}
|
|
5769
6523
|
return dir;
|
|
5770
6524
|
}
|
|
5771
6525
|
function saveQuery(workspaceRoot, name, gql) {
|
|
5772
6526
|
const dir = ensureQueriesDir(workspaceRoot);
|
|
5773
|
-
const filePath =
|
|
5774
|
-
|
|
6527
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6528
|
+
fs33.writeFileSync(filePath, gql, "utf-8");
|
|
5775
6529
|
}
|
|
5776
6530
|
function loadQuery(workspaceRoot, name) {
|
|
5777
6531
|
const dir = getQueriesDir(workspaceRoot);
|
|
5778
|
-
const filePath =
|
|
5779
|
-
if (!
|
|
5780
|
-
return
|
|
6532
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6533
|
+
if (!fs33.existsSync(filePath)) return null;
|
|
6534
|
+
return fs33.readFileSync(filePath, "utf-8");
|
|
5781
6535
|
}
|
|
5782
6536
|
function listQueries(workspaceRoot) {
|
|
5783
6537
|
const dir = getQueriesDir(workspaceRoot);
|
|
5784
|
-
if (!
|
|
5785
|
-
const files =
|
|
6538
|
+
if (!fs33.existsSync(dir)) return [];
|
|
6539
|
+
const files = fs33.readdirSync(dir).filter((f) => f.endsWith(".gql"));
|
|
5786
6540
|
return files.map((f) => {
|
|
5787
|
-
const filePath =
|
|
6541
|
+
const filePath = path33.join(dir, f);
|
|
5788
6542
|
const name = f.replace(/\.gql$/, "");
|
|
5789
|
-
const content =
|
|
5790
|
-
const stat =
|
|
6543
|
+
const content = fs33.readFileSync(filePath, "utf-8");
|
|
6544
|
+
const stat = fs33.statSync(filePath);
|
|
5791
6545
|
return {
|
|
5792
6546
|
name,
|
|
5793
6547
|
content,
|
|
@@ -5798,146 +6552,85 @@ function listQueries(workspaceRoot) {
|
|
|
5798
6552
|
}
|
|
5799
6553
|
function deleteQuery(workspaceRoot, name) {
|
|
5800
6554
|
const dir = getQueriesDir(workspaceRoot);
|
|
5801
|
-
const filePath =
|
|
5802
|
-
if (!
|
|
5803
|
-
|
|
6555
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6556
|
+
if (!fs33.existsSync(filePath)) return false;
|
|
6557
|
+
fs33.unlinkSync(filePath);
|
|
5804
6558
|
return true;
|
|
5805
6559
|
}
|
|
5806
6560
|
function queryExists(workspaceRoot, name) {
|
|
5807
6561
|
const dir = getQueriesDir(workspaceRoot);
|
|
5808
|
-
const filePath =
|
|
5809
|
-
return
|
|
6562
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6563
|
+
return fs33.existsSync(filePath);
|
|
5810
6564
|
}
|
|
5811
6565
|
var init_saved_queries = __esm({
|
|
5812
6566
|
"src/query/saved-queries.ts"() {
|
|
5813
6567
|
}
|
|
5814
6568
|
});
|
|
5815
6569
|
|
|
5816
|
-
// src/cli/
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
const
|
|
5823
|
-
const
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
let toSet = edgesToNode.get(edge.target);
|
|
5840
|
-
if (!toSet) {
|
|
5841
|
-
toSet = /* @__PURE__ */ new Set();
|
|
5842
|
-
edgesToNode.set(edge.target, toSet);
|
|
5843
|
-
}
|
|
5844
|
-
toSet.add(edge.id);
|
|
5845
|
-
}
|
|
5846
|
-
function unindexEdge(edge) {
|
|
5847
|
-
edgesByKind.get(edge.kind)?.delete(edge.id);
|
|
5848
|
-
edgesFromNode.get(edge.source)?.delete(edge.id);
|
|
5849
|
-
edgesToNode.get(edge.target)?.delete(edge.id);
|
|
5850
|
-
}
|
|
5851
|
-
return {
|
|
5852
|
-
addNode(node) {
|
|
5853
|
-
nodes.set(node.id, node);
|
|
5854
|
-
},
|
|
5855
|
-
addEdge(edge) {
|
|
5856
|
-
edges.set(edge.id, edge);
|
|
5857
|
-
indexEdge(edge);
|
|
5858
|
-
},
|
|
5859
|
-
getNode(id) {
|
|
5860
|
-
return nodes.get(id);
|
|
5861
|
-
},
|
|
5862
|
-
getEdge(id) {
|
|
5863
|
-
return edges.get(id);
|
|
5864
|
-
},
|
|
5865
|
-
*findEdgesByKind(kind) {
|
|
5866
|
-
const ids = edgesByKind.get(kind);
|
|
5867
|
-
if (!ids) return;
|
|
5868
|
-
for (const id of ids) {
|
|
5869
|
-
const edge = edges.get(id);
|
|
5870
|
-
if (edge) yield edge;
|
|
5871
|
-
}
|
|
5872
|
-
},
|
|
5873
|
-
*findEdgesFrom(sourceId) {
|
|
5874
|
-
const ids = edgesFromNode.get(sourceId);
|
|
5875
|
-
if (!ids) return;
|
|
5876
|
-
for (const id of ids) {
|
|
5877
|
-
const edge = edges.get(id);
|
|
5878
|
-
if (edge) yield edge;
|
|
5879
|
-
}
|
|
5880
|
-
},
|
|
5881
|
-
*findEdgesTo(targetId) {
|
|
5882
|
-
const ids = edgesToNode.get(targetId);
|
|
5883
|
-
if (!ids) return;
|
|
5884
|
-
for (const id of ids) {
|
|
5885
|
-
const edge = edges.get(id);
|
|
5886
|
-
if (edge) yield edge;
|
|
5887
|
-
}
|
|
5888
|
-
},
|
|
5889
|
-
removeNodeCascade(id) {
|
|
5890
|
-
const fromEdges = edgesFromNode.get(id);
|
|
5891
|
-
if (fromEdges) {
|
|
5892
|
-
for (const edgeId of [...fromEdges]) {
|
|
5893
|
-
const edge = edges.get(edgeId);
|
|
5894
|
-
if (edge) {
|
|
5895
|
-
unindexEdge(edge);
|
|
5896
|
-
edges.delete(edgeId);
|
|
6570
|
+
// src/cli/sarif-builder.ts
|
|
6571
|
+
var sarif_builder_exports = {};
|
|
6572
|
+
__export(sarif_builder_exports, {
|
|
6573
|
+
buildSARIF: () => buildSARIF
|
|
6574
|
+
});
|
|
6575
|
+
function buildSARIF(result, version) {
|
|
6576
|
+
const results = [];
|
|
6577
|
+
for (const sym of result.changedSymbols) {
|
|
6578
|
+
if (sym.risk !== "HIGH" && sym.risk !== "MEDIUM") continue;
|
|
6579
|
+
const ruleId = sym.risk === "HIGH" ? "HIGH-RISK-SYMBOL" : "MEDIUM-RISK-SYMBOL";
|
|
6580
|
+
const level = sym.risk === "HIGH" ? "error" : "warning";
|
|
6581
|
+
const uri = result.filesToReview[0] ?? "unknown";
|
|
6582
|
+
results.push({
|
|
6583
|
+
ruleId,
|
|
6584
|
+
level,
|
|
6585
|
+
message: {
|
|
6586
|
+
text: `${sym.name} has blast radius of ${sym.callerCount} callers (${sym.risk} risk)`
|
|
6587
|
+
},
|
|
6588
|
+
locations: [
|
|
6589
|
+
{
|
|
6590
|
+
physicalLocation: {
|
|
6591
|
+
artifactLocation: { uri },
|
|
6592
|
+
region: { startLine: 1 }
|
|
5897
6593
|
}
|
|
5898
6594
|
}
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
6595
|
+
]
|
|
6596
|
+
});
|
|
6597
|
+
}
|
|
6598
|
+
return {
|
|
6599
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
6600
|
+
version: "2.1.0",
|
|
6601
|
+
runs: [
|
|
6602
|
+
{
|
|
6603
|
+
tool: {
|
|
6604
|
+
driver: {
|
|
6605
|
+
name: "code-intel",
|
|
6606
|
+
version,
|
|
6607
|
+
rules: [
|
|
6608
|
+
{
|
|
6609
|
+
id: "HIGH-RISK-SYMBOL",
|
|
6610
|
+
name: "HighRiskSymbol",
|
|
6611
|
+
shortDescription: { text: "Symbol has high blast radius" }
|
|
6612
|
+
},
|
|
6613
|
+
{
|
|
6614
|
+
id: "MEDIUM-RISK-SYMBOL",
|
|
6615
|
+
name: "MediumRiskSymbol",
|
|
6616
|
+
shortDescription: { text: "Symbol has medium blast radius" }
|
|
6617
|
+
}
|
|
6618
|
+
]
|
|
5907
6619
|
}
|
|
5908
|
-
}
|
|
5909
|
-
|
|
5910
|
-
edgesFromNode.delete(id);
|
|
5911
|
-
edgesToNode.delete(id);
|
|
5912
|
-
nodes.delete(id);
|
|
5913
|
-
},
|
|
5914
|
-
removeEdge(id) {
|
|
5915
|
-
const edge = edges.get(id);
|
|
5916
|
-
if (edge) {
|
|
5917
|
-
unindexEdge(edge);
|
|
5918
|
-
edges.delete(id);
|
|
6620
|
+
},
|
|
6621
|
+
results
|
|
5919
6622
|
}
|
|
5920
|
-
|
|
5921
|
-
*allNodes() {
|
|
5922
|
-
yield* nodes.values();
|
|
5923
|
-
},
|
|
5924
|
-
*allEdges() {
|
|
5925
|
-
yield* edges.values();
|
|
5926
|
-
},
|
|
5927
|
-
get size() {
|
|
5928
|
-
return { nodes: nodes.size, edges: edges.size };
|
|
5929
|
-
},
|
|
5930
|
-
clear() {
|
|
5931
|
-
nodes.clear();
|
|
5932
|
-
edges.clear();
|
|
5933
|
-
edgesByKind.clear();
|
|
5934
|
-
edgesFromNode.clear();
|
|
5935
|
-
edgesToNode.clear();
|
|
5936
|
-
}
|
|
6623
|
+
]
|
|
5937
6624
|
};
|
|
5938
6625
|
}
|
|
6626
|
+
var init_sarif_builder = __esm({
|
|
6627
|
+
"src/cli/sarif-builder.ts"() {
|
|
6628
|
+
}
|
|
6629
|
+
});
|
|
5939
6630
|
|
|
5940
6631
|
// src/cli/main.ts
|
|
6632
|
+
init_logger();
|
|
6633
|
+
init_knowledge_graph();
|
|
5941
6634
|
init_orchestrator();
|
|
5942
6635
|
|
|
5943
6636
|
// src/pipeline/phases/scan-phase.ts
|
|
@@ -5976,7 +6669,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
5976
6669
|
]);
|
|
5977
6670
|
function loadIgnorePatterns(workspaceRoot) {
|
|
5978
6671
|
try {
|
|
5979
|
-
const raw =
|
|
6672
|
+
const raw = fs33.readFileSync(path33.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
5980
6673
|
const extras = /* @__PURE__ */ new Set();
|
|
5981
6674
|
for (const line of raw.split("\n")) {
|
|
5982
6675
|
const trimmed = line.trim();
|
|
@@ -6000,7 +6693,7 @@ var scanPhase = {
|
|
|
6000
6693
|
function walk2(dir) {
|
|
6001
6694
|
let entries;
|
|
6002
6695
|
try {
|
|
6003
|
-
entries =
|
|
6696
|
+
entries = fs33.readdirSync(dir, { withFileTypes: true });
|
|
6004
6697
|
} catch {
|
|
6005
6698
|
return;
|
|
6006
6699
|
}
|
|
@@ -6009,15 +6702,15 @@ var scanPhase = {
|
|
|
6009
6702
|
if (entry.name.startsWith(".")) continue;
|
|
6010
6703
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6011
6704
|
if (extraIgnore.has(entry.name)) continue;
|
|
6012
|
-
walk2(
|
|
6705
|
+
walk2(path33.join(dir, entry.name));
|
|
6013
6706
|
} else if (entry.isFile()) {
|
|
6014
6707
|
const name = entry.name;
|
|
6015
6708
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
6016
|
-
const ext =
|
|
6709
|
+
const ext = path33.extname(name);
|
|
6017
6710
|
if (!extensions.has(ext)) continue;
|
|
6018
|
-
const fullPath =
|
|
6711
|
+
const fullPath = path33.join(dir, name);
|
|
6019
6712
|
try {
|
|
6020
|
-
const stat =
|
|
6713
|
+
const stat = fs33.statSync(fullPath);
|
|
6021
6714
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
6022
6715
|
} catch {
|
|
6023
6716
|
continue;
|
|
@@ -6044,20 +6737,20 @@ var structurePhase = {
|
|
|
6044
6737
|
const dirs = /* @__PURE__ */ new Set();
|
|
6045
6738
|
let structDone = 0;
|
|
6046
6739
|
for (const filePath of context2.filePaths) {
|
|
6047
|
-
const relativePath =
|
|
6740
|
+
const relativePath = path33.relative(context2.workspaceRoot, filePath);
|
|
6048
6741
|
const lang = detectLanguage(filePath);
|
|
6049
6742
|
context2.graph.addNode({
|
|
6050
6743
|
id: generateNodeId("file", relativePath, relativePath),
|
|
6051
6744
|
kind: "file",
|
|
6052
|
-
name:
|
|
6745
|
+
name: path33.basename(filePath),
|
|
6053
6746
|
filePath: relativePath,
|
|
6054
6747
|
metadata: lang ? { language: lang } : void 0
|
|
6055
6748
|
});
|
|
6056
|
-
let dir =
|
|
6749
|
+
let dir = path33.dirname(relativePath);
|
|
6057
6750
|
while (dir && dir !== "." && dir !== "") {
|
|
6058
6751
|
if (dirs.has(dir)) break;
|
|
6059
6752
|
dirs.add(dir);
|
|
6060
|
-
dir =
|
|
6753
|
+
dir = path33.dirname(dir);
|
|
6061
6754
|
}
|
|
6062
6755
|
structDone++;
|
|
6063
6756
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -6066,7 +6759,7 @@ var structurePhase = {
|
|
|
6066
6759
|
context2.graph.addNode({
|
|
6067
6760
|
id: generateNodeId("directory", dir, dir),
|
|
6068
6761
|
kind: "directory",
|
|
6069
|
-
name:
|
|
6762
|
+
name: path33.basename(dir),
|
|
6070
6763
|
filePath: dir
|
|
6071
6764
|
});
|
|
6072
6765
|
}
|
|
@@ -6177,22 +6870,22 @@ var flowPhase = {
|
|
|
6177
6870
|
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
6178
6871
|
const visited = /* @__PURE__ */ new Set();
|
|
6179
6872
|
while (queue.length > 0 && flowCount < maxFlows) {
|
|
6180
|
-
const { nodeId, path:
|
|
6181
|
-
if (
|
|
6873
|
+
const { nodeId, path: path34 } = queue.shift();
|
|
6874
|
+
if (path34.length > maxDepth) continue;
|
|
6182
6875
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
6183
|
-
if (callEdges.length === 0 &&
|
|
6876
|
+
if (callEdges.length === 0 && path34.length >= 3) {
|
|
6184
6877
|
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
6185
6878
|
graph.addNode({
|
|
6186
6879
|
id: flowId,
|
|
6187
6880
|
kind: "flow",
|
|
6188
6881
|
name: `${ep.name} flow ${flowCount}`,
|
|
6189
6882
|
filePath: ep.filePath,
|
|
6190
|
-
metadata: { steps:
|
|
6883
|
+
metadata: { steps: path34, entryPoint: ep.name }
|
|
6191
6884
|
});
|
|
6192
|
-
for (let i = 0; i <
|
|
6885
|
+
for (let i = 0; i < path34.length; i++) {
|
|
6193
6886
|
graph.addEdge({
|
|
6194
|
-
id: generateEdgeId(
|
|
6195
|
-
source:
|
|
6887
|
+
id: generateEdgeId(path34[i], flowId, `step_of_${i}`),
|
|
6888
|
+
source: path34[i],
|
|
6196
6889
|
target: flowId,
|
|
6197
6890
|
kind: "step_of",
|
|
6198
6891
|
weight: 1,
|
|
@@ -6205,7 +6898,7 @@ var flowPhase = {
|
|
|
6205
6898
|
for (const edge of callEdges) {
|
|
6206
6899
|
if (visited.has(edge.target)) continue;
|
|
6207
6900
|
visited.add(edge.target);
|
|
6208
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6901
|
+
queue.push({ nodeId: edge.target, path: [...path34, edge.target] });
|
|
6209
6902
|
}
|
|
6210
6903
|
}
|
|
6211
6904
|
}
|
|
@@ -6223,7 +6916,7 @@ var LLMGovernanceLogger = class {
|
|
|
6223
6916
|
}
|
|
6224
6917
|
/** Path to the JSONL log file. */
|
|
6225
6918
|
getLogPath() {
|
|
6226
|
-
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ??
|
|
6919
|
+
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path33.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
|
|
6227
6920
|
}
|
|
6228
6921
|
/**
|
|
6229
6922
|
* Append an entry to the governance log.
|
|
@@ -6239,8 +6932,8 @@ var LLMGovernanceLogger = class {
|
|
|
6239
6932
|
...entry
|
|
6240
6933
|
};
|
|
6241
6934
|
const logPath = this.getLogPath();
|
|
6242
|
-
|
|
6243
|
-
|
|
6935
|
+
fs33.mkdirSync(path33.dirname(logPath), { recursive: true });
|
|
6936
|
+
fs33.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
6244
6937
|
} catch {
|
|
6245
6938
|
}
|
|
6246
6939
|
}
|
|
@@ -6250,7 +6943,7 @@ var LLMGovernanceLogger = class {
|
|
|
6250
6943
|
*/
|
|
6251
6944
|
readLog(limit = 100) {
|
|
6252
6945
|
try {
|
|
6253
|
-
const raw =
|
|
6946
|
+
const raw = fs33.readFileSync(this.getLogPath(), "utf-8");
|
|
6254
6947
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
6255
6948
|
return lines.map((l) => JSON.parse(l));
|
|
6256
6949
|
} catch {
|
|
@@ -6564,7 +7257,7 @@ var LANG_QUERIES2 = {
|
|
|
6564
7257
|
};
|
|
6565
7258
|
function workerScriptPath() {
|
|
6566
7259
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6567
|
-
return
|
|
7260
|
+
return path33.join(path33.dirname(thisFile), "parse-worker.js");
|
|
6568
7261
|
}
|
|
6569
7262
|
var parsePhaseParallel = {
|
|
6570
7263
|
name: "parse",
|
|
@@ -6580,14 +7273,14 @@ var parsePhaseParallel = {
|
|
|
6580
7273
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
6581
7274
|
await Promise.all(batch.map(async (filePath) => {
|
|
6582
7275
|
try {
|
|
6583
|
-
const source = await
|
|
7276
|
+
const source = await fs33.promises.readFile(filePath, "utf-8");
|
|
6584
7277
|
context2.fileCache.set(filePath, source);
|
|
6585
7278
|
} catch {
|
|
6586
7279
|
}
|
|
6587
7280
|
}));
|
|
6588
7281
|
}
|
|
6589
7282
|
const workerScript = workerScriptPath();
|
|
6590
|
-
const workerScriptExists =
|
|
7283
|
+
const workerScriptExists = fs33.existsSync(workerScript);
|
|
6591
7284
|
if (!workerScriptExists || workerCount === 1) {
|
|
6592
7285
|
logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
|
|
6593
7286
|
const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
|
|
@@ -6599,7 +7292,7 @@ var parsePhaseParallel = {
|
|
|
6599
7292
|
if (!lang) continue;
|
|
6600
7293
|
const source = context2.fileCache.get(filePath);
|
|
6601
7294
|
if (!source) continue;
|
|
6602
|
-
const relativePath =
|
|
7295
|
+
const relativePath = path33.relative(context2.workspaceRoot, filePath);
|
|
6603
7296
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
6604
7297
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
6605
7298
|
if (fileNode) fileNode.content = source.slice(0, 2e3);
|
|
@@ -6642,7 +7335,7 @@ var parsePhaseParallel = {
|
|
|
6642
7335
|
symbolCount += res.nodes.length;
|
|
6643
7336
|
if (res.usedTreeSitter) treeSitterCount++;
|
|
6644
7337
|
else regexCount++;
|
|
6645
|
-
const relativePath =
|
|
7338
|
+
const relativePath = path33.relative(context2.workspaceRoot, res.taskId);
|
|
6646
7339
|
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);
|
|
6647
7340
|
if (funcs.length > 0) context2.fileFunctionIndex.set(relativePath, funcs);
|
|
6648
7341
|
parseDone++;
|
|
@@ -6669,7 +7362,7 @@ init_id_generator();
|
|
|
6669
7362
|
init_logger();
|
|
6670
7363
|
function workerScriptPath2() {
|
|
6671
7364
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6672
|
-
return
|
|
7365
|
+
return path33.join(path33.dirname(thisFile), "resolve-worker.js");
|
|
6673
7366
|
}
|
|
6674
7367
|
var resolvePhaseParallel = {
|
|
6675
7368
|
name: "resolve",
|
|
@@ -6681,11 +7374,11 @@ var resolvePhaseParallel = {
|
|
|
6681
7374
|
const fileFunctionIndex = context2.fileFunctionIndex ?? /* @__PURE__ */ new Map();
|
|
6682
7375
|
const fileIndex = {};
|
|
6683
7376
|
for (const fp of filePaths) {
|
|
6684
|
-
const rel =
|
|
7377
|
+
const rel = path33.relative(workspaceRoot, fp);
|
|
6685
7378
|
fileIndex[rel] = fp;
|
|
6686
7379
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
6687
7380
|
if (!fileIndex[noExt]) fileIndex[noExt] = fp;
|
|
6688
|
-
const base =
|
|
7381
|
+
const base = path33.basename(rel, path33.extname(rel));
|
|
6689
7382
|
if (!fileIndex[base]) fileIndex[base] = fp;
|
|
6690
7383
|
}
|
|
6691
7384
|
const symbolIndex = {};
|
|
@@ -6699,427 +7392,202 @@ var resolvePhaseParallel = {
|
|
|
6699
7392
|
}
|
|
6700
7393
|
const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
|
|
6701
7394
|
const workerScript = workerScriptPath2();
|
|
6702
|
-
const workerScriptExists =
|
|
6703
|
-
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os12.cpus().length - 1);
|
|
6704
|
-
if (!workerScriptExists || workerCount === 1) {
|
|
6705
|
-
logger_default.info(`[resolve-parallel] falling back to sequential`);
|
|
6706
|
-
const { resolvePhase: resolvePhase2 } = await Promise.resolve().then(() => (init_resolve_phase(), resolve_phase_exports));
|
|
6707
|
-
return resolvePhase2.execute(context2, /* @__PURE__ */ new Map());
|
|
6708
|
-
}
|
|
6709
|
-
const tasks = [];
|
|
6710
|
-
for (const filePath of filePaths) {
|
|
6711
|
-
const lang = detectLanguage(filePath);
|
|
6712
|
-
if (!lang) continue;
|
|
6713
|
-
const source = fileCache.get(filePath);
|
|
6714
|
-
if (!source) continue;
|
|
6715
|
-
const relativePath = path30.relative(workspaceRoot, filePath);
|
|
6716
|
-
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
6717
|
-
const funcList = fileFunctionIndex.get(relativePath) ?? [];
|
|
6718
|
-
tasks.push({ taskId: filePath, filePath, relativePath, fileNodeId, source, funcList });
|
|
6719
|
-
}
|
|
6720
|
-
let importEdges = 0;
|
|
6721
|
-
let callEdges = 0;
|
|
6722
|
-
let heritageEdges = 0;
|
|
6723
|
-
let fileDone = 0;
|
|
6724
|
-
const BATCH_SIZE = 100;
|
|
6725
|
-
const workers = [];
|
|
6726
|
-
for (let i = 0; i < workerCount; i++) {
|
|
6727
|
-
workers.push({ w: new Worker(workerScript, { workerData: snapshot }), busy: false });
|
|
6728
|
-
}
|
|
6729
|
-
const pendingResolvers = /* @__PURE__ */ new Map();
|
|
6730
|
-
for (const { w } of workers) {
|
|
6731
|
-
w.on("message", (result) => {
|
|
6732
|
-
const resolve = pendingResolvers.get(result.taskId);
|
|
6733
|
-
if (resolve) {
|
|
6734
|
-
pendingResolvers.delete(result.taskId);
|
|
6735
|
-
resolve(result);
|
|
6736
|
-
}
|
|
6737
|
-
});
|
|
6738
|
-
w.on("error", (err) => logger_default.warn(`[resolve-worker] error: ${err.message}`));
|
|
6739
|
-
}
|
|
6740
|
-
let workerIdx = 0;
|
|
6741
|
-
function runTask(task) {
|
|
6742
|
-
return new Promise((resolve) => {
|
|
6743
|
-
pendingResolvers.set(task.taskId, resolve);
|
|
6744
|
-
const { w } = workers[workerIdx % workers.length];
|
|
6745
|
-
workerIdx++;
|
|
6746
|
-
w.postMessage(task);
|
|
6747
|
-
});
|
|
6748
|
-
}
|
|
6749
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6750
|
-
for (let i = 0; i < tasks.length; i += BATCH_SIZE) {
|
|
6751
|
-
const batch = tasks.slice(i, i + BATCH_SIZE);
|
|
6752
|
-
const results = await Promise.all(batch.map((t) => runTask(t)));
|
|
6753
|
-
for (const res of results) {
|
|
6754
|
-
if (res.error) logger_default.warn(`[resolve-parallel] task error: ${res.error}`);
|
|
6755
|
-
for (const edge of res.edges) {
|
|
6756
|
-
if (seen.has(edge.id)) continue;
|
|
6757
|
-
seen.add(edge.id);
|
|
6758
|
-
graph.addEdge(edge);
|
|
6759
|
-
if (edge.kind === "imports") importEdges++;
|
|
6760
|
-
else if (edge.kind === "calls") callEdges++;
|
|
6761
|
-
else heritageEdges++;
|
|
6762
|
-
}
|
|
6763
|
-
fileDone++;
|
|
6764
|
-
context2.onPhaseProgress?.("resolve", fileDone, tasks.length);
|
|
6765
|
-
}
|
|
6766
|
-
}
|
|
6767
|
-
await Promise.all(workers.map(({ w }) => w.terminate()));
|
|
6768
|
-
return {
|
|
6769
|
-
status: "completed",
|
|
6770
|
-
duration: Date.now() - start,
|
|
6771
|
-
message: `Resolved ${importEdges} imports, ${callEdges} calls, ${heritageEdges} heritage edges. Graph: ${graph.size.nodes} nodes, ${graph.size.edges} edges`
|
|
6772
|
-
};
|
|
6773
|
-
}
|
|
6774
|
-
};
|
|
6775
|
-
|
|
6776
|
-
// src/search/text-search.ts
|
|
6777
|
-
function textSearch(graph, query, limit = 20) {
|
|
6778
|
-
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
6779
|
-
const results = [];
|
|
6780
|
-
const isTestPath = (fp) => fp.includes("test") || fp.includes("spec") || fp.includes("__test");
|
|
6781
|
-
const isDistPath = (fp) => fp.includes("/dist") || fp.includes("\\dist") || fp.includes(".d.ts");
|
|
6782
|
-
for (const node of graph.allNodes()) {
|
|
6783
|
-
if (["directory", "cluster", "flow"].includes(node.kind)) continue;
|
|
6784
|
-
let score = 0;
|
|
6785
|
-
const nameLC = node.name.toLowerCase();
|
|
6786
|
-
const pathLC = node.filePath.toLowerCase();
|
|
6787
|
-
for (const term of terms) {
|
|
6788
|
-
if (nameLC === term) score += 10;
|
|
6789
|
-
else if (nameLC.startsWith(term)) score += 7;
|
|
6790
|
-
else if (nameLC.includes(term)) score += 5;
|
|
6791
|
-
if (pathLC.includes(term)) score += 2;
|
|
6792
|
-
if (node.content?.toLowerCase().includes(term)) score += 3;
|
|
6793
|
-
}
|
|
6794
|
-
if (score > 0) {
|
|
6795
|
-
if (isDistPath(node.filePath)) score -= 8;
|
|
6796
|
-
if (isTestPath(node.filePath)) score -= 4;
|
|
6797
|
-
if (["function", "class", "interface", "method"].includes(node.kind)) score += 1;
|
|
6798
|
-
}
|
|
6799
|
-
if (score > 0) {
|
|
6800
|
-
results.push({
|
|
6801
|
-
nodeId: node.id,
|
|
6802
|
-
name: node.name,
|
|
6803
|
-
kind: node.kind,
|
|
6804
|
-
filePath: node.filePath,
|
|
6805
|
-
score,
|
|
6806
|
-
snippet: node.content?.slice(0, 200)
|
|
6807
|
-
});
|
|
6808
|
-
}
|
|
6809
|
-
}
|
|
6810
|
-
results.sort((a, b) => b.score - a.score);
|
|
6811
|
-
return results.slice(0, limit);
|
|
6812
|
-
}
|
|
6813
|
-
function reciprocalRankFusion(...rankings) {
|
|
6814
|
-
const K = 60;
|
|
6815
|
-
const scoreMap = /* @__PURE__ */ new Map();
|
|
6816
|
-
for (const ranking of rankings) {
|
|
6817
|
-
for (let rank = 0; rank < ranking.length; rank++) {
|
|
6818
|
-
const result = ranking[rank];
|
|
6819
|
-
const existing = scoreMap.get(result.nodeId);
|
|
6820
|
-
const rrfContribution = 1 / (K + rank + 1);
|
|
6821
|
-
if (existing) {
|
|
6822
|
-
existing.rrfScore += rrfContribution;
|
|
6823
|
-
} else {
|
|
6824
|
-
scoreMap.set(result.nodeId, {
|
|
6825
|
-
result,
|
|
6826
|
-
rrfScore: rrfContribution
|
|
6827
|
-
});
|
|
6828
|
-
}
|
|
6829
|
-
}
|
|
6830
|
-
}
|
|
6831
|
-
return [...scoreMap.values()].sort((a, b) => b.rrfScore - a.rrfScore).map((entry) => ({ ...entry.result, score: entry.rrfScore }));
|
|
6832
|
-
}
|
|
6833
|
-
init_vector_index();
|
|
6834
|
-
init_embedder();
|
|
6835
|
-
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6836
|
-
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
6837
|
-
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6838
|
-
const hasVectorDb = Boolean(vectorDbPath && fs28.existsSync(vectorDbPath));
|
|
6839
|
-
if (!hasVectorDb) {
|
|
6840
|
-
const bm25Results2 = await bm25Promise;
|
|
6841
|
-
return {
|
|
6842
|
-
results: bm25Results2.slice(0, limit).map((r) => ({ ...r, searchMode: "bm25" })),
|
|
6843
|
-
searchMode: "bm25"
|
|
6844
|
-
};
|
|
6845
|
-
}
|
|
6846
|
-
const vectorPromise = runVectorSearch(vectorDbPath, query, vectorLimit);
|
|
6847
|
-
const [bm25Results, vectorResults] = await Promise.all([bm25Promise, vectorPromise]);
|
|
6848
|
-
if (vectorResults === null || vectorResults.length === 0) {
|
|
6849
|
-
return {
|
|
6850
|
-
results: bm25Results.slice(0, limit).map((r) => ({ ...r, searchMode: "bm25" })),
|
|
6851
|
-
searchMode: "bm25"
|
|
6852
|
-
};
|
|
6853
|
-
}
|
|
6854
|
-
const vectorAsSearchResults = vectorResults.map((h) => ({
|
|
6855
|
-
nodeId: h.nodeId,
|
|
6856
|
-
name: h.name,
|
|
6857
|
-
kind: h.kind,
|
|
6858
|
-
filePath: h.filePath,
|
|
6859
|
-
score: h.score,
|
|
6860
|
-
snippet: graph.getNode(h.nodeId)?.content?.slice(0, 200)
|
|
6861
|
-
}));
|
|
6862
|
-
const merged = reciprocalRankFusion(bm25Results, vectorAsSearchResults);
|
|
6863
|
-
return {
|
|
6864
|
-
results: merged.slice(0, limit).map((r) => ({ ...r, searchMode: "hybrid" })),
|
|
6865
|
-
searchMode: "hybrid"
|
|
6866
|
-
};
|
|
6867
|
-
}
|
|
6868
|
-
async function runVectorSearch(vectorDbPath, query, topK) {
|
|
6869
|
-
try {
|
|
6870
|
-
const idx = new VectorIndex(vectorDbPath);
|
|
6871
|
-
await idx.init();
|
|
6872
|
-
const built = await idx.isBuilt();
|
|
6873
|
-
if (!built) {
|
|
6874
|
-
idx.close();
|
|
6875
|
-
return null;
|
|
6876
|
-
}
|
|
6877
|
-
const embedder = await getEmbedder();
|
|
6878
|
-
const out = await embedder(query, { pooling: "mean", normalize: true });
|
|
6879
|
-
const queryEmbedding = Array.from(out.data);
|
|
6880
|
-
const hits = await idx.search(queryEmbedding, topK);
|
|
6881
|
-
idx.close();
|
|
6882
|
-
return hits;
|
|
6883
|
-
} catch {
|
|
6884
|
-
return null;
|
|
6885
|
-
}
|
|
6886
|
-
}
|
|
6887
|
-
|
|
6888
|
-
// src/http/app.ts
|
|
6889
|
-
init_storage();
|
|
6890
|
-
init_metadata();
|
|
6891
|
-
init_vector_index();
|
|
6892
|
-
init_group_registry();
|
|
6893
|
-
|
|
6894
|
-
// src/multi-repo/group-sync.ts
|
|
6895
|
-
init_repo_registry();
|
|
6896
|
-
init_db_manager();
|
|
6897
|
-
|
|
6898
|
-
// src/multi-repo/graph-from-db.ts
|
|
6899
|
-
init_schema();
|
|
6900
|
-
var TABLE_TO_KIND = Object.fromEntries(
|
|
6901
|
-
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
6902
|
-
);
|
|
6903
|
-
function parseRow(row, kind) {
|
|
6904
|
-
return {
|
|
6905
|
-
id: String(row["id"] ?? ""),
|
|
6906
|
-
kind,
|
|
6907
|
-
name: String(row["name"] ?? ""),
|
|
6908
|
-
filePath: String(row["file_path"] ?? ""),
|
|
6909
|
-
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
6910
|
-
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
6911
|
-
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
6912
|
-
content: row["content"] ? String(row["content"]) : void 0,
|
|
6913
|
-
metadata: row["metadata"] ? (() => {
|
|
6914
|
-
try {
|
|
6915
|
-
return JSON.parse(String(row["metadata"]));
|
|
6916
|
-
} catch {
|
|
6917
|
-
return void 0;
|
|
6918
|
-
}
|
|
6919
|
-
})() : void 0
|
|
6920
|
-
};
|
|
6921
|
-
}
|
|
6922
|
-
async function loadGraphFromDB(graph, db) {
|
|
6923
|
-
for (const table of ALL_NODE_TABLES) {
|
|
6924
|
-
const kind = TABLE_TO_KIND[table];
|
|
6925
|
-
if (!kind) continue;
|
|
6926
|
-
let rows = [];
|
|
6927
|
-
try {
|
|
6928
|
-
rows = await db.query(`MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`);
|
|
6929
|
-
} catch {
|
|
6930
|
-
continue;
|
|
6931
|
-
}
|
|
6932
|
-
for (const row of rows) {
|
|
6933
|
-
const node = parseRow({
|
|
6934
|
-
id: row["n.id"],
|
|
6935
|
-
name: row["n.name"],
|
|
6936
|
-
file_path: row["n.file_path"],
|
|
6937
|
-
start_line: row["n.start_line"],
|
|
6938
|
-
end_line: row["n.end_line"],
|
|
6939
|
-
exported: row["n.exported"],
|
|
6940
|
-
content: row["n.content"],
|
|
6941
|
-
metadata: row["n.metadata"]
|
|
6942
|
-
}, kind);
|
|
6943
|
-
if (node.id && node.name) graph.addNode(node);
|
|
7395
|
+
const workerScriptExists = fs33.existsSync(workerScript);
|
|
7396
|
+
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os12.cpus().length - 1);
|
|
7397
|
+
if (!workerScriptExists || workerCount === 1) {
|
|
7398
|
+
logger_default.info(`[resolve-parallel] falling back to sequential`);
|
|
7399
|
+
const { resolvePhase: resolvePhase2 } = await Promise.resolve().then(() => (init_resolve_phase(), resolve_phase_exports));
|
|
7400
|
+
return resolvePhase2.execute(context2, /* @__PURE__ */ new Map());
|
|
6944
7401
|
}
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
const
|
|
6952
|
-
const
|
|
6953
|
-
const
|
|
6954
|
-
|
|
6955
|
-
const edge = {
|
|
6956
|
-
id: `${sourceId}::${kind}::${targetId}`,
|
|
6957
|
-
source: sourceId,
|
|
6958
|
-
target: targetId,
|
|
6959
|
-
kind,
|
|
6960
|
-
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
6961
|
-
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
6962
|
-
};
|
|
6963
|
-
graph.addEdge(edge);
|
|
7402
|
+
const tasks = [];
|
|
7403
|
+
for (const filePath of filePaths) {
|
|
7404
|
+
const lang = detectLanguage(filePath);
|
|
7405
|
+
if (!lang) continue;
|
|
7406
|
+
const source = fileCache.get(filePath);
|
|
7407
|
+
if (!source) continue;
|
|
7408
|
+
const relativePath = path33.relative(workspaceRoot, filePath);
|
|
7409
|
+
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
7410
|
+
const funcList = fileFunctionIndex.get(relativePath) ?? [];
|
|
7411
|
+
tasks.push({ taskId: filePath, filePath, relativePath, fileNodeId, source, funcList });
|
|
6964
7412
|
}
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
for (const node of graph.allNodes()) {
|
|
6974
|
-
if (node.exported === true && ["function", "class", "interface", "method", "type_alias", "constant", "enum", "struct", "trait"].includes(node.kind)) {
|
|
6975
|
-
contracts.push({
|
|
6976
|
-
repoName,
|
|
6977
|
-
repoPath,
|
|
6978
|
-
kind: "export",
|
|
6979
|
-
name: node.name,
|
|
6980
|
-
nodeId: node.id,
|
|
6981
|
-
nodeKind: node.kind,
|
|
6982
|
-
filePath: node.filePath,
|
|
6983
|
-
signature: node.content?.split("\n")[0]?.trim()
|
|
6984
|
-
});
|
|
7413
|
+
let importEdges = 0;
|
|
7414
|
+
let callEdges = 0;
|
|
7415
|
+
let heritageEdges = 0;
|
|
7416
|
+
let fileDone = 0;
|
|
7417
|
+
const BATCH_SIZE = 100;
|
|
7418
|
+
const workers = [];
|
|
7419
|
+
for (let i = 0; i < workerCount; i++) {
|
|
7420
|
+
workers.push({ w: new Worker(workerScript, { workerData: snapshot }), busy: false });
|
|
6985
7421
|
}
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
filePath: node.filePath,
|
|
6995
|
-
signature: node.content?.split("\n")[0]?.trim()
|
|
7422
|
+
const pendingResolvers = /* @__PURE__ */ new Map();
|
|
7423
|
+
for (const { w } of workers) {
|
|
7424
|
+
w.on("message", (result) => {
|
|
7425
|
+
const resolve = pendingResolvers.get(result.taskId);
|
|
7426
|
+
if (resolve) {
|
|
7427
|
+
pendingResolvers.delete(result.taskId);
|
|
7428
|
+
resolve(result);
|
|
7429
|
+
}
|
|
6996
7430
|
});
|
|
7431
|
+
w.on("error", (err) => logger_default.warn(`[resolve-worker] error: ${err.message}`));
|
|
6997
7432
|
}
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
nodeId: node.id,
|
|
7007
|
-
nodeKind: node.kind,
|
|
7008
|
-
filePath: node.filePath
|
|
7009
|
-
});
|
|
7010
|
-
} else if (nameLower.includes("schema") || nameLower.includes("dto") || nameLower.includes("request") || nameLower.includes("response")) {
|
|
7011
|
-
contracts.push({
|
|
7012
|
-
repoName,
|
|
7013
|
-
repoPath,
|
|
7014
|
-
kind: "schema",
|
|
7015
|
-
name: node.name,
|
|
7016
|
-
nodeId: node.id,
|
|
7017
|
-
nodeKind: node.kind,
|
|
7018
|
-
filePath: node.filePath
|
|
7019
|
-
});
|
|
7020
|
-
}
|
|
7433
|
+
let workerIdx = 0;
|
|
7434
|
+
function runTask(task) {
|
|
7435
|
+
return new Promise((resolve) => {
|
|
7436
|
+
pendingResolvers.set(task.taskId, resolve);
|
|
7437
|
+
const { w } = workers[workerIdx % workers.length];
|
|
7438
|
+
workerIdx++;
|
|
7439
|
+
w.postMessage(task);
|
|
7440
|
+
});
|
|
7021
7441
|
}
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
for (let j = 0; j < repoNames.length; j++) {
|
|
7036
|
-
if (i === j) continue;
|
|
7037
|
-
const providerContracts = byRepo.get(repoNames[i]);
|
|
7038
|
-
const consumerContracts = byRepo.get(repoNames[j]);
|
|
7039
|
-
const consumerByName = /* @__PURE__ */ new Map();
|
|
7040
|
-
for (const c of consumerContracts) consumerByName.set(c.name, c);
|
|
7041
|
-
for (const provider of providerContracts) {
|
|
7042
|
-
const consumer = consumerByName.get(provider.name);
|
|
7043
|
-
if (consumer) {
|
|
7044
|
-
const sameKind = provider.kind === consumer.kind;
|
|
7045
|
-
links.push({
|
|
7046
|
-
providerRepo: provider.repoName,
|
|
7047
|
-
providerContract: provider.name,
|
|
7048
|
-
consumerRepo: consumer.repoName,
|
|
7049
|
-
consumerContract: consumer.name,
|
|
7050
|
-
matchKind: provider.kind === "route" ? "route-match" : "name-match",
|
|
7051
|
-
confidence: sameKind ? 0.9 : 0.6
|
|
7052
|
-
});
|
|
7053
|
-
} else {
|
|
7054
|
-
const providerLC = provider.name.toLowerCase();
|
|
7055
|
-
for (const c of consumerContracts) {
|
|
7056
|
-
if (c.name.toLowerCase().includes(providerLC) || providerLC.includes(c.name.toLowerCase())) {
|
|
7057
|
-
if (c.name.length >= 4 && provider.name.length >= 4) {
|
|
7058
|
-
links.push({
|
|
7059
|
-
providerRepo: provider.repoName,
|
|
7060
|
-
providerContract: provider.name,
|
|
7061
|
-
consumerRepo: c.repoName,
|
|
7062
|
-
consumerContract: c.name,
|
|
7063
|
-
matchKind: "name-match",
|
|
7064
|
-
confidence: 0.4
|
|
7065
|
-
});
|
|
7066
|
-
}
|
|
7067
|
-
}
|
|
7068
|
-
}
|
|
7442
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7443
|
+
for (let i = 0; i < tasks.length; i += BATCH_SIZE) {
|
|
7444
|
+
const batch = tasks.slice(i, i + BATCH_SIZE);
|
|
7445
|
+
const results = await Promise.all(batch.map((t) => runTask(t)));
|
|
7446
|
+
for (const res of results) {
|
|
7447
|
+
if (res.error) logger_default.warn(`[resolve-parallel] task error: ${res.error}`);
|
|
7448
|
+
for (const edge of res.edges) {
|
|
7449
|
+
if (seen.has(edge.id)) continue;
|
|
7450
|
+
seen.add(edge.id);
|
|
7451
|
+
graph.addEdge(edge);
|
|
7452
|
+
if (edge.kind === "imports") importEdges++;
|
|
7453
|
+
else if (edge.kind === "calls") callEdges++;
|
|
7454
|
+
else heritageEdges++;
|
|
7069
7455
|
}
|
|
7456
|
+
fileDone++;
|
|
7457
|
+
context2.onPhaseProgress?.("resolve", fileDone, tasks.length);
|
|
7070
7458
|
}
|
|
7071
7459
|
}
|
|
7460
|
+
await Promise.all(workers.map(({ w }) => w.terminate()));
|
|
7461
|
+
return {
|
|
7462
|
+
status: "completed",
|
|
7463
|
+
duration: Date.now() - start,
|
|
7464
|
+
message: `Resolved ${importEdges} imports, ${callEdges} calls, ${heritageEdges} heritage edges. Graph: ${graph.size.nodes} nodes, ${graph.size.edges} edges`
|
|
7465
|
+
};
|
|
7072
7466
|
}
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7467
|
+
};
|
|
7468
|
+
|
|
7469
|
+
// src/search/text-search.ts
|
|
7470
|
+
function textSearch(graph, query, limit = 20) {
|
|
7471
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
7472
|
+
const results = [];
|
|
7473
|
+
const isTestPath = (fp) => fp.includes("test") || fp.includes("spec") || fp.includes("__test");
|
|
7474
|
+
const isDistPath = (fp) => fp.includes("/dist") || fp.includes("\\dist") || fp.includes(".d.ts");
|
|
7475
|
+
for (const node of graph.allNodes()) {
|
|
7476
|
+
if (["directory", "cluster", "flow"].includes(node.kind)) continue;
|
|
7477
|
+
let score = 0;
|
|
7478
|
+
const nameLC = node.name.toLowerCase();
|
|
7479
|
+
const pathLC = node.filePath.toLowerCase();
|
|
7480
|
+
for (const term of terms) {
|
|
7481
|
+
if (nameLC === term) score += 10;
|
|
7482
|
+
else if (nameLC.startsWith(term)) score += 7;
|
|
7483
|
+
else if (nameLC.includes(term)) score += 5;
|
|
7484
|
+
if (pathLC.includes(term)) score += 2;
|
|
7485
|
+
if (node.content?.toLowerCase().includes(term)) score += 3;
|
|
7079
7486
|
}
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
const registry = loadRegistry();
|
|
7085
|
-
const allContracts = [];
|
|
7086
|
-
for (const member of group.members) {
|
|
7087
|
-
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7088
|
-
if (!regEntry) {
|
|
7089
|
-
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
7090
|
-
continue;
|
|
7487
|
+
if (score > 0) {
|
|
7488
|
+
if (isDistPath(node.filePath)) score -= 8;
|
|
7489
|
+
if (isTestPath(node.filePath)) score -= 4;
|
|
7490
|
+
if (["function", "class", "interface", "method"].includes(node.kind)) score += 1;
|
|
7091
7491
|
}
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7492
|
+
if (score > 0) {
|
|
7493
|
+
results.push({
|
|
7494
|
+
nodeId: node.id,
|
|
7495
|
+
name: node.name,
|
|
7496
|
+
kind: node.kind,
|
|
7497
|
+
filePath: node.filePath,
|
|
7498
|
+
score,
|
|
7499
|
+
snippet: node.content?.slice(0, 200)
|
|
7500
|
+
});
|
|
7096
7501
|
}
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7502
|
+
}
|
|
7503
|
+
results.sort((a, b) => b.score - a.score);
|
|
7504
|
+
return results.slice(0, limit);
|
|
7505
|
+
}
|
|
7506
|
+
function reciprocalRankFusion(...rankings) {
|
|
7507
|
+
const K = 60;
|
|
7508
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
7509
|
+
for (const ranking of rankings) {
|
|
7510
|
+
for (let rank = 0; rank < ranking.length; rank++) {
|
|
7511
|
+
const result = ranking[rank];
|
|
7512
|
+
const existing = scoreMap.get(result.nodeId);
|
|
7513
|
+
const rrfContribution = 1 / (K + rank + 1);
|
|
7514
|
+
if (existing) {
|
|
7515
|
+
existing.rrfScore += rrfContribution;
|
|
7516
|
+
} else {
|
|
7517
|
+
scoreMap.set(result.nodeId, {
|
|
7518
|
+
result,
|
|
7519
|
+
rrfScore: rrfContribution
|
|
7520
|
+
});
|
|
7521
|
+
}
|
|
7107
7522
|
}
|
|
7108
|
-
const contracts = extractContracts(graph, member.registryName, regEntry.path);
|
|
7109
|
-
logger_default.info(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
|
|
7110
|
-
allContracts.push(...contracts);
|
|
7111
7523
|
}
|
|
7112
|
-
|
|
7524
|
+
return [...scoreMap.values()].sort((a, b) => b.rrfScore - a.rrfScore).map((entry) => ({ ...entry.result, score: entry.rrfScore }));
|
|
7525
|
+
}
|
|
7526
|
+
init_vector_index();
|
|
7527
|
+
init_embedder();
|
|
7528
|
+
async function hybridSearch(graph, query, limit, options = {}) {
|
|
7529
|
+
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
7530
|
+
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
7531
|
+
const hasVectorDb = Boolean(vectorDbPath && fs33.existsSync(vectorDbPath));
|
|
7532
|
+
if (!hasVectorDb) {
|
|
7533
|
+
const bm25Results2 = await bm25Promise;
|
|
7534
|
+
return {
|
|
7535
|
+
results: bm25Results2.slice(0, limit).map((r) => ({ ...r, searchMode: "bm25" })),
|
|
7536
|
+
searchMode: "bm25"
|
|
7537
|
+
};
|
|
7538
|
+
}
|
|
7539
|
+
const vectorPromise = runVectorSearch(vectorDbPath, query, vectorLimit);
|
|
7540
|
+
const [bm25Results, vectorResults] = await Promise.all([bm25Promise, vectorPromise]);
|
|
7541
|
+
if (vectorResults === null || vectorResults.length === 0) {
|
|
7542
|
+
return {
|
|
7543
|
+
results: bm25Results.slice(0, limit).map((r) => ({ ...r, searchMode: "bm25" })),
|
|
7544
|
+
searchMode: "bm25"
|
|
7545
|
+
};
|
|
7546
|
+
}
|
|
7547
|
+
const vectorAsSearchResults = vectorResults.map((h) => ({
|
|
7548
|
+
nodeId: h.nodeId,
|
|
7549
|
+
name: h.name,
|
|
7550
|
+
kind: h.kind,
|
|
7551
|
+
filePath: h.filePath,
|
|
7552
|
+
score: h.score,
|
|
7553
|
+
snippet: graph.getNode(h.nodeId)?.content?.slice(0, 200)
|
|
7554
|
+
}));
|
|
7555
|
+
const merged = reciprocalRankFusion(bm25Results, vectorAsSearchResults);
|
|
7113
7556
|
return {
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
memberCount: group.members.length,
|
|
7117
|
-
contracts: allContracts,
|
|
7118
|
-
links
|
|
7557
|
+
results: merged.slice(0, limit).map((r) => ({ ...r, searchMode: "hybrid" })),
|
|
7558
|
+
searchMode: "hybrid"
|
|
7119
7559
|
};
|
|
7120
7560
|
}
|
|
7561
|
+
async function runVectorSearch(vectorDbPath, query, topK) {
|
|
7562
|
+
try {
|
|
7563
|
+
const idx = new VectorIndex(vectorDbPath);
|
|
7564
|
+
await idx.init();
|
|
7565
|
+
const built = await idx.isBuilt();
|
|
7566
|
+
if (!built) {
|
|
7567
|
+
idx.close();
|
|
7568
|
+
return null;
|
|
7569
|
+
}
|
|
7570
|
+
const embedder = await getEmbedder();
|
|
7571
|
+
const out = await embedder(query, { pooling: "mean", normalize: true });
|
|
7572
|
+
const queryEmbedding = Array.from(out.data);
|
|
7573
|
+
const hits = await idx.search(queryEmbedding, topK);
|
|
7574
|
+
idx.close();
|
|
7575
|
+
return hits;
|
|
7576
|
+
} catch {
|
|
7577
|
+
return null;
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
|
|
7581
|
+
// src/http/app.ts
|
|
7582
|
+
init_storage();
|
|
7583
|
+
init_metadata();
|
|
7584
|
+
init_vector_index();
|
|
7585
|
+
init_group_registry();
|
|
7586
|
+
init_group_sync();
|
|
7121
7587
|
init_repo_registry();
|
|
7122
7588
|
init_db_manager();
|
|
7589
|
+
init_knowledge_graph();
|
|
7590
|
+
init_graph_from_db();
|
|
7123
7591
|
async function queryGroup(group, query, limit = 20) {
|
|
7124
7592
|
const registry = loadRegistry();
|
|
7125
7593
|
const perRepo = [];
|
|
@@ -7127,8 +7595,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7127
7595
|
for (const member of group.members) {
|
|
7128
7596
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7129
7597
|
if (!regEntry) continue;
|
|
7130
|
-
const dbPath =
|
|
7131
|
-
if (!
|
|
7598
|
+
const dbPath = path33.join(regEntry.path, ".code-intel", "graph.db");
|
|
7599
|
+
if (!fs33.existsSync(dbPath)) continue;
|
|
7132
7600
|
const graph = createKnowledgeGraph();
|
|
7133
7601
|
const db = new DbManager(dbPath);
|
|
7134
7602
|
try {
|
|
@@ -7157,6 +7625,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7157
7625
|
}
|
|
7158
7626
|
|
|
7159
7627
|
// src/http/app.ts
|
|
7628
|
+
init_knowledge_graph();
|
|
7629
|
+
init_graph_from_db();
|
|
7160
7630
|
init_repo_registry();
|
|
7161
7631
|
init_logger();
|
|
7162
7632
|
init_codes();
|
|
@@ -7168,7 +7638,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
7168
7638
|
var JobsDB = class {
|
|
7169
7639
|
db;
|
|
7170
7640
|
constructor(dbPath) {
|
|
7171
|
-
|
|
7641
|
+
fs33.mkdirSync(path33.dirname(dbPath), { recursive: true });
|
|
7172
7642
|
this.db = new Database(dbPath);
|
|
7173
7643
|
this.db.pragma("journal_mode = WAL");
|
|
7174
7644
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -7310,7 +7780,7 @@ var JobsDB = class {
|
|
|
7310
7780
|
}
|
|
7311
7781
|
};
|
|
7312
7782
|
function getJobsDBPath() {
|
|
7313
|
-
return
|
|
7783
|
+
return path33.join(os12.homedir(), ".code-intel", "jobs.db");
|
|
7314
7784
|
}
|
|
7315
7785
|
var _jobsDB = null;
|
|
7316
7786
|
function getOrCreateJobsDB() {
|
|
@@ -7402,7 +7872,7 @@ var BACKUP_VERSION = "1.0";
|
|
|
7402
7872
|
var ALGORITHM = "aes-256-gcm";
|
|
7403
7873
|
var IV_LENGTH = 16;
|
|
7404
7874
|
function getBackupDir() {
|
|
7405
|
-
return
|
|
7875
|
+
return path33.join(os12.homedir(), ".code-intel", "backups");
|
|
7406
7876
|
}
|
|
7407
7877
|
function getBackupKey() {
|
|
7408
7878
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
@@ -7433,30 +7903,30 @@ var BackupService = class {
|
|
|
7433
7903
|
constructor(backupDir) {
|
|
7434
7904
|
this.backupDir = backupDir ?? getBackupDir();
|
|
7435
7905
|
this.key = getBackupKey();
|
|
7436
|
-
|
|
7906
|
+
fs33.mkdirSync(this.backupDir, { recursive: true });
|
|
7437
7907
|
}
|
|
7438
7908
|
/**
|
|
7439
7909
|
* Create a backup for a repository.
|
|
7440
7910
|
* Returns the backup entry.
|
|
7441
7911
|
*/
|
|
7442
7912
|
createBackup(repoPath) {
|
|
7443
|
-
const codeIntelDir =
|
|
7913
|
+
const codeIntelDir = path33.join(repoPath, ".code-intel");
|
|
7444
7914
|
const id = v4();
|
|
7445
7915
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7446
7916
|
const filesToBackup = [];
|
|
7447
7917
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
7448
7918
|
for (const f of candidates) {
|
|
7449
|
-
const fp =
|
|
7450
|
-
if (
|
|
7919
|
+
const fp = path33.join(codeIntelDir, f);
|
|
7920
|
+
if (fs33.existsSync(fp)) {
|
|
7451
7921
|
filesToBackup.push({ name: f, localPath: fp });
|
|
7452
7922
|
}
|
|
7453
7923
|
}
|
|
7454
|
-
const registryPath =
|
|
7455
|
-
if (
|
|
7924
|
+
const registryPath = path33.join(os12.homedir(), ".code-intel", "registry.json");
|
|
7925
|
+
if (fs33.existsSync(registryPath)) {
|
|
7456
7926
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
7457
7927
|
}
|
|
7458
|
-
const usersDbPath =
|
|
7459
|
-
if (
|
|
7928
|
+
const usersDbPath = path33.join(os12.homedir(), ".code-intel", "users.db");
|
|
7929
|
+
if (fs33.existsSync(usersDbPath)) {
|
|
7460
7930
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
7461
7931
|
}
|
|
7462
7932
|
if (filesToBackup.length === 0) {
|
|
@@ -7467,7 +7937,7 @@ var BackupService = class {
|
|
|
7467
7937
|
createdAt,
|
|
7468
7938
|
version: BACKUP_VERSION,
|
|
7469
7939
|
files: filesToBackup.map((f) => {
|
|
7470
|
-
const data =
|
|
7940
|
+
const data = fs33.readFileSync(f.localPath);
|
|
7471
7941
|
return {
|
|
7472
7942
|
name: f.name,
|
|
7473
7943
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -7481,7 +7951,7 @@ var BackupService = class {
|
|
|
7481
7951
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
7482
7952
|
parts.push(manifestLenBuf, manifestBuf);
|
|
7483
7953
|
for (const f of filesToBackup) {
|
|
7484
|
-
const data =
|
|
7954
|
+
const data = fs33.readFileSync(f.localPath);
|
|
7485
7955
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
7486
7956
|
const nameLenBuf = Buffer.alloc(2);
|
|
7487
7957
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -7492,8 +7962,8 @@ var BackupService = class {
|
|
|
7492
7962
|
const plaintext = Buffer.concat(parts);
|
|
7493
7963
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
7494
7964
|
const backupFileName = `backup-${id}.cib`;
|
|
7495
|
-
const backupPath =
|
|
7496
|
-
|
|
7965
|
+
const backupPath = path33.join(this.backupDir, backupFileName);
|
|
7966
|
+
fs33.writeFileSync(backupPath, encrypted);
|
|
7497
7967
|
const entry = {
|
|
7498
7968
|
id,
|
|
7499
7969
|
createdAt,
|
|
@@ -7520,9 +7990,9 @@ var BackupService = class {
|
|
|
7520
7990
|
async uploadToS3(entry) {
|
|
7521
7991
|
const cfg = getS3Config();
|
|
7522
7992
|
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.");
|
|
7523
|
-
const fileName =
|
|
7993
|
+
const fileName = path33.basename(entry.path);
|
|
7524
7994
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
7525
|
-
const body =
|
|
7995
|
+
const body = fs33.readFileSync(entry.path);
|
|
7526
7996
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
7527
7997
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
7528
7998
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -7539,8 +8009,8 @@ var BackupService = class {
|
|
|
7539
8009
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
7540
8010
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
7541
8011
|
}
|
|
7542
|
-
|
|
7543
|
-
|
|
8012
|
+
fs33.mkdirSync(path33.dirname(destPath), { recursive: true });
|
|
8013
|
+
fs33.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
7544
8014
|
}
|
|
7545
8015
|
/**
|
|
7546
8016
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -7586,10 +8056,10 @@ var BackupService = class {
|
|
|
7586
8056
|
if (!entry) {
|
|
7587
8057
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
7588
8058
|
}
|
|
7589
|
-
if (!
|
|
8059
|
+
if (!fs33.existsSync(entry.path)) {
|
|
7590
8060
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
7591
8061
|
}
|
|
7592
|
-
const encrypted =
|
|
8062
|
+
const encrypted = fs33.readFileSync(entry.path);
|
|
7593
8063
|
let plaintext;
|
|
7594
8064
|
try {
|
|
7595
8065
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -7603,8 +8073,8 @@ var BackupService = class {
|
|
|
7603
8073
|
offset += manifestLen;
|
|
7604
8074
|
const manifest = JSON.parse(manifestStr);
|
|
7605
8075
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
7606
|
-
const codeIntelDir =
|
|
7607
|
-
|
|
8076
|
+
const codeIntelDir = path33.join(restoreBase, ".code-intel");
|
|
8077
|
+
fs33.mkdirSync(codeIntelDir, { recursive: true });
|
|
7608
8078
|
for (const fileEntry of manifest.files) {
|
|
7609
8079
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
7610
8080
|
offset += 2;
|
|
@@ -7621,18 +8091,18 @@ var BackupService = class {
|
|
|
7621
8091
|
}
|
|
7622
8092
|
let destPath;
|
|
7623
8093
|
if (name === "registry.json" || name === "users.db") {
|
|
7624
|
-
destPath =
|
|
8094
|
+
destPath = path33.join(os12.homedir(), ".code-intel", name);
|
|
7625
8095
|
} else {
|
|
7626
|
-
destPath =
|
|
8096
|
+
destPath = path33.join(codeIntelDir, name);
|
|
7627
8097
|
}
|
|
7628
|
-
|
|
8098
|
+
fs33.writeFileSync(destPath, data);
|
|
7629
8099
|
}
|
|
7630
8100
|
}
|
|
7631
8101
|
/**
|
|
7632
8102
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
7633
8103
|
*/
|
|
7634
8104
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
7635
|
-
const entries = this._loadIndex().filter((e) =>
|
|
8105
|
+
const entries = this._loadIndex().filter((e) => fs33.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
7636
8106
|
const keep = /* @__PURE__ */ new Set();
|
|
7637
8107
|
const now = /* @__PURE__ */ new Date();
|
|
7638
8108
|
const dailyCutoff = new Date(now);
|
|
@@ -7662,7 +8132,7 @@ var BackupService = class {
|
|
|
7662
8132
|
for (const e of entries) {
|
|
7663
8133
|
if (!keep.has(e.id)) {
|
|
7664
8134
|
try {
|
|
7665
|
-
|
|
8135
|
+
fs33.unlinkSync(e.path);
|
|
7666
8136
|
deleted++;
|
|
7667
8137
|
} catch {
|
|
7668
8138
|
}
|
|
@@ -7674,17 +8144,17 @@ var BackupService = class {
|
|
|
7674
8144
|
}
|
|
7675
8145
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
7676
8146
|
_indexPath() {
|
|
7677
|
-
return
|
|
8147
|
+
return path33.join(this.backupDir, "index.json");
|
|
7678
8148
|
}
|
|
7679
8149
|
_loadIndex() {
|
|
7680
8150
|
try {
|
|
7681
|
-
return JSON.parse(
|
|
8151
|
+
return JSON.parse(fs33.readFileSync(this._indexPath(), "utf-8"));
|
|
7682
8152
|
} catch {
|
|
7683
8153
|
return [];
|
|
7684
8154
|
}
|
|
7685
8155
|
}
|
|
7686
8156
|
_saveIndex(entries) {
|
|
7687
|
-
|
|
8157
|
+
fs33.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
7688
8158
|
}
|
|
7689
8159
|
_appendIndex(entry) {
|
|
7690
8160
|
const entries = this._loadIndex();
|
|
@@ -8191,6 +8661,30 @@ var openApiSpec = {
|
|
|
8191
8661
|
}
|
|
8192
8662
|
}
|
|
8193
8663
|
},
|
|
8664
|
+
"/groups/{name}/topology": {
|
|
8665
|
+
get: {
|
|
8666
|
+
tags: ["Groups"],
|
|
8667
|
+
summary: "Get the topology of repos and cross-repo contract edges for a group",
|
|
8668
|
+
parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" } }],
|
|
8669
|
+
responses: {
|
|
8670
|
+
"200": {
|
|
8671
|
+
description: "Repos and cross-repo edges",
|
|
8672
|
+
content: {
|
|
8673
|
+
"application/json": {
|
|
8674
|
+
schema: {
|
|
8675
|
+
type: "object",
|
|
8676
|
+
properties: {
|
|
8677
|
+
repos: { type: "array", items: { type: "object", properties: { name: { type: "string" }, groupPath: { type: "string" }, nodeCount: { type: "integer" }, edgeCount: { type: "integer" } } } },
|
|
8678
|
+
edges: { type: "array", items: { type: "object", properties: { source: { type: "string" }, target: { type: "string" }, contractName: { type: "string" }, confidence: { type: "number" }, kind: { type: "string" } } } }
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
},
|
|
8684
|
+
"404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
8685
|
+
}
|
|
8686
|
+
}
|
|
8687
|
+
},
|
|
8194
8688
|
"/query": {
|
|
8195
8689
|
post: {
|
|
8196
8690
|
tags: ["GQL"],
|
|
@@ -8301,11 +8795,11 @@ var openApiSpec = {
|
|
|
8301
8795
|
};
|
|
8302
8796
|
|
|
8303
8797
|
// src/http/app.ts
|
|
8304
|
-
var __dirname$1 =
|
|
8798
|
+
var __dirname$1 = path33.dirname(fileURLToPath(import.meta.url));
|
|
8305
8799
|
var WEB_DIST = (() => {
|
|
8306
|
-
const bundled =
|
|
8307
|
-
if (
|
|
8308
|
-
return
|
|
8800
|
+
const bundled = path33.resolve(__dirname$1, "..", "web");
|
|
8801
|
+
if (fs33.existsSync(bundled)) return bundled;
|
|
8802
|
+
return path33.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
8309
8803
|
})();
|
|
8310
8804
|
function getAllowedOrigins() {
|
|
8311
8805
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -8836,8 +9330,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8836
9330
|
const registry = loadRegistry();
|
|
8837
9331
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
8838
9332
|
if (!entry) return null;
|
|
8839
|
-
const dbPath =
|
|
8840
|
-
if (!
|
|
9333
|
+
const dbPath = path33.join(entry.path, ".code-intel", "graph.db");
|
|
9334
|
+
if (!fs33.existsSync(dbPath)) return null;
|
|
8841
9335
|
const repoGraph = createKnowledgeGraph();
|
|
8842
9336
|
const db = new DbManager(dbPath);
|
|
8843
9337
|
try {
|
|
@@ -8924,7 +9418,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8924
9418
|
return;
|
|
8925
9419
|
}
|
|
8926
9420
|
try {
|
|
8927
|
-
const content =
|
|
9421
|
+
const content = fs33.readFileSync(file_path, "utf-8");
|
|
8928
9422
|
res.json({ content });
|
|
8929
9423
|
} catch {
|
|
8930
9424
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -9182,8 +9676,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9182
9676
|
for (const member of group.members) {
|
|
9183
9677
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
9184
9678
|
if (!regEntry) continue;
|
|
9185
|
-
const dbPath =
|
|
9186
|
-
if (!
|
|
9679
|
+
const dbPath = path33.join(regEntry.path, ".code-intel", "graph.db");
|
|
9680
|
+
if (!fs33.existsSync(dbPath)) continue;
|
|
9187
9681
|
const db = new DbManager(dbPath);
|
|
9188
9682
|
try {
|
|
9189
9683
|
await db.init();
|
|
@@ -9195,6 +9689,45 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9195
9689
|
}
|
|
9196
9690
|
res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
|
|
9197
9691
|
});
|
|
9692
|
+
app.get("/api/v1/groups/:name/topology", requireAuth, requireRole("viewer"), async (req, res) => {
|
|
9693
|
+
const groupName = req.params["name"];
|
|
9694
|
+
const group = loadGroup(groupName);
|
|
9695
|
+
if (!group) {
|
|
9696
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
9697
|
+
return;
|
|
9698
|
+
}
|
|
9699
|
+
const syncResult = loadSyncResult(groupName);
|
|
9700
|
+
const registry = loadRegistry();
|
|
9701
|
+
const repos = await Promise.all(group.members.map(async (member) => {
|
|
9702
|
+
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
9703
|
+
let nodeCount = 0;
|
|
9704
|
+
let edgeCount = 0;
|
|
9705
|
+
if (regEntry) {
|
|
9706
|
+
const dbPath = path33.join(regEntry.path, ".code-intel", "graph.db");
|
|
9707
|
+
if (fs33.existsSync(dbPath)) {
|
|
9708
|
+
try {
|
|
9709
|
+
const db = new DbManager(dbPath);
|
|
9710
|
+
await db.init();
|
|
9711
|
+
const g = createKnowledgeGraph();
|
|
9712
|
+
await loadGraphFromDB(g, db);
|
|
9713
|
+
db.close();
|
|
9714
|
+
nodeCount = g.size.nodes;
|
|
9715
|
+
edgeCount = g.size.edges;
|
|
9716
|
+
} catch {
|
|
9717
|
+
}
|
|
9718
|
+
}
|
|
9719
|
+
}
|
|
9720
|
+
return { name: member.registryName, groupPath: member.groupPath, nodeCount, edgeCount };
|
|
9721
|
+
}));
|
|
9722
|
+
const edges = syncResult ? syncResult.links.map((link) => ({
|
|
9723
|
+
source: link.providerRepo,
|
|
9724
|
+
target: link.consumerRepo,
|
|
9725
|
+
contractName: link.providerContract,
|
|
9726
|
+
confidence: link.confidence,
|
|
9727
|
+
kind: "contract"
|
|
9728
|
+
})) : [];
|
|
9729
|
+
res.json({ repos, edges });
|
|
9730
|
+
});
|
|
9198
9731
|
app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
|
|
9199
9732
|
const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
|
|
9200
9733
|
if (!file) {
|
|
@@ -9220,14 +9753,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9220
9753
|
});
|
|
9221
9754
|
return;
|
|
9222
9755
|
}
|
|
9223
|
-
let rawResolved =
|
|
9224
|
-
if (!
|
|
9225
|
-
rawResolved =
|
|
9756
|
+
let rawResolved = path33.normalize(file);
|
|
9757
|
+
if (!path33.isAbsolute(rawResolved) && workspaceRoot) {
|
|
9758
|
+
rawResolved = path33.join(workspaceRoot, rawResolved);
|
|
9226
9759
|
}
|
|
9227
|
-
const resolvedFile =
|
|
9760
|
+
const resolvedFile = path33.resolve(rawResolved);
|
|
9228
9761
|
function isInsideDir(fileAbs, dir) {
|
|
9229
|
-
const rel =
|
|
9230
|
-
return !rel.startsWith("..") && !
|
|
9762
|
+
const rel = path33.relative(path33.resolve(dir), fileAbs);
|
|
9763
|
+
return !rel.startsWith("..") && !path33.isAbsolute(rel);
|
|
9231
9764
|
}
|
|
9232
9765
|
if (workspaceRoot) {
|
|
9233
9766
|
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
@@ -9264,7 +9797,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9264
9797
|
}
|
|
9265
9798
|
let fileContent;
|
|
9266
9799
|
try {
|
|
9267
|
-
fileContent =
|
|
9800
|
+
fileContent = fs33.readFileSync(resolvedFile, "utf-8");
|
|
9268
9801
|
} catch {
|
|
9269
9802
|
res.status(404).json({
|
|
9270
9803
|
error: {
|
|
@@ -9295,7 +9828,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9295
9828
|
const contextStart = Math.max(1, startLine - 20);
|
|
9296
9829
|
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
9297
9830
|
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
9298
|
-
const ext =
|
|
9831
|
+
const ext = path33.extname(resolvedFile).toLowerCase();
|
|
9299
9832
|
const languageMap = {
|
|
9300
9833
|
".ts": "typescript",
|
|
9301
9834
|
".tsx": "typescript",
|
|
@@ -9430,10 +9963,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9430
9963
|
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() } });
|
|
9431
9964
|
}
|
|
9432
9965
|
});
|
|
9433
|
-
if (
|
|
9966
|
+
if (fs33.existsSync(WEB_DIST)) {
|
|
9434
9967
|
app.use(express.static(WEB_DIST));
|
|
9435
9968
|
app.get("/{*path}", (_req, res) => {
|
|
9436
|
-
res.sendFile(
|
|
9969
|
+
res.sendFile(path33.join(WEB_DIST, "index.html"));
|
|
9437
9970
|
});
|
|
9438
9971
|
}
|
|
9439
9972
|
app.use("/admin", requireRole("admin"));
|
|
@@ -9580,16 +10113,536 @@ async function startHttpServer(graph, repoName, port = 4747, workspaceRoot, watc
|
|
|
9580
10113
|
wsServer = new WsServer2(httpServer);
|
|
9581
10114
|
} catch {
|
|
9582
10115
|
}
|
|
9583
|
-
resolve({ wsServer });
|
|
9584
|
-
});
|
|
9585
|
-
});
|
|
10116
|
+
resolve({ wsServer });
|
|
10117
|
+
});
|
|
10118
|
+
});
|
|
10119
|
+
}
|
|
10120
|
+
init_storage();
|
|
10121
|
+
init_repo_registry();
|
|
10122
|
+
init_metadata();
|
|
10123
|
+
init_group_registry();
|
|
10124
|
+
init_group_sync();
|
|
10125
|
+
init_tracing();
|
|
10126
|
+
init_metrics();
|
|
10127
|
+
|
|
10128
|
+
// src/query/explain-relationship.ts
|
|
10129
|
+
function explainRelationship(graph, from, to) {
|
|
10130
|
+
const allNodes = [...graph.allNodes()];
|
|
10131
|
+
const fromNode = allNodes.find((n) => n.name === from);
|
|
10132
|
+
if (!fromNode) {
|
|
10133
|
+
const firstChar = from[0]?.toLowerCase() ?? "";
|
|
10134
|
+
const fromLower = from.toLowerCase();
|
|
10135
|
+
const suggestions = allNodes.filter((n) => n.name.toLowerCase().startsWith(firstChar) || n.name.toLowerCase().includes(fromLower)).slice(0, 5).map((n) => n.name);
|
|
10136
|
+
return { error: `Symbol not found: ${from}`, suggestions };
|
|
10137
|
+
}
|
|
10138
|
+
const toNode = allNodes.find((n) => n.name === to);
|
|
10139
|
+
if (!toNode) {
|
|
10140
|
+
const firstChar = to[0]?.toLowerCase() ?? "";
|
|
10141
|
+
const toLower = to.toLowerCase();
|
|
10142
|
+
const suggestions = allNodes.filter((n) => n.name.toLowerCase().startsWith(firstChar) || n.name.toLowerCase().includes(toLower)).slice(0, 5).map((n) => n.name);
|
|
10143
|
+
return { error: `Symbol not found: ${to}`, suggestions };
|
|
10144
|
+
}
|
|
10145
|
+
const paths = [];
|
|
10146
|
+
const queue = [{
|
|
10147
|
+
id: fromNode.id,
|
|
10148
|
+
nodeNames: [fromNode.name],
|
|
10149
|
+
lastEdgeKind: "",
|
|
10150
|
+
visited: /* @__PURE__ */ new Set([fromNode.id])
|
|
10151
|
+
}];
|
|
10152
|
+
while (queue.length > 0 && paths.length < 10) {
|
|
10153
|
+
const entry = queue.shift();
|
|
10154
|
+
const { id, nodeNames, visited } = entry;
|
|
10155
|
+
if (nodeNames.length > 6) continue;
|
|
10156
|
+
for (const edge of graph.findEdgesFrom(id)) {
|
|
10157
|
+
const targetNode = graph.getNode(edge.target);
|
|
10158
|
+
if (!targetNode) continue;
|
|
10159
|
+
if (visited.has(edge.target)) continue;
|
|
10160
|
+
const newNames = [...nodeNames, targetNode.name];
|
|
10161
|
+
if (edge.target === toNode.id) {
|
|
10162
|
+
paths.push({ hops: newNames.length - 1, nodes: newNames, edgeKind: edge.kind });
|
|
10163
|
+
if (paths.length >= 10) break;
|
|
10164
|
+
continue;
|
|
10165
|
+
}
|
|
10166
|
+
if (newNames.length < 6) {
|
|
10167
|
+
const newVisited = new Set(visited);
|
|
10168
|
+
newVisited.add(edge.target);
|
|
10169
|
+
queue.push({ id: edge.target, nodeNames: newNames, lastEdgeKind: edge.kind, visited: newVisited });
|
|
10170
|
+
}
|
|
10171
|
+
}
|
|
10172
|
+
}
|
|
10173
|
+
const fromImports = /* @__PURE__ */ new Set();
|
|
10174
|
+
for (const edge of graph.findEdgesFrom(fromNode.id)) {
|
|
10175
|
+
if (edge.kind === "imports") fromImports.add(edge.target);
|
|
10176
|
+
}
|
|
10177
|
+
const sharedImportIds = [];
|
|
10178
|
+
for (const edge of graph.findEdgesFrom(toNode.id)) {
|
|
10179
|
+
if (edge.kind === "imports" && fromImports.has(edge.target)) {
|
|
10180
|
+
sharedImportIds.push(edge.target);
|
|
10181
|
+
}
|
|
10182
|
+
}
|
|
10183
|
+
const sharedImports = sharedImportIds.map((id) => graph.getNode(id)?.name ?? id);
|
|
10184
|
+
let heritage = null;
|
|
10185
|
+
for (const edge of graph.findEdgesFrom(fromNode.id)) {
|
|
10186
|
+
if ((edge.kind === "extends" || edge.kind === "implements") && edge.target === toNode.id) {
|
|
10187
|
+
heritage = `${from} ${edge.kind} ${to}`;
|
|
10188
|
+
break;
|
|
10189
|
+
}
|
|
10190
|
+
}
|
|
10191
|
+
if (!heritage) {
|
|
10192
|
+
for (const edge of graph.findEdgesFrom(toNode.id)) {
|
|
10193
|
+
if ((edge.kind === "extends" || edge.kind === "implements") && edge.target === fromNode.id) {
|
|
10194
|
+
heritage = `${to} ${edge.kind} ${from}`;
|
|
10195
|
+
break;
|
|
10196
|
+
}
|
|
10197
|
+
}
|
|
10198
|
+
}
|
|
10199
|
+
const sharedStr = sharedImports.length > 0 ? sharedImports.join(", ") : "none";
|
|
10200
|
+
const heritageStr = heritage ?? "none";
|
|
10201
|
+
const connectionStr = paths.length === 0 ? "No connection found." : `${from} \u2192 ${to} via ${paths.length} path(s).`;
|
|
10202
|
+
const summary = `${connectionStr} Shared imports: [${sharedStr}]. Heritage: ${heritageStr}.`;
|
|
10203
|
+
return { paths, sharedImports, heritage, summary };
|
|
10204
|
+
}
|
|
10205
|
+
|
|
10206
|
+
// src/mcp-server/server.ts
|
|
10207
|
+
init_pr_impact();
|
|
10208
|
+
|
|
10209
|
+
// src/query/similar-symbols.ts
|
|
10210
|
+
function levenshtein(a, b) {
|
|
10211
|
+
const m = a.length;
|
|
10212
|
+
const n = b.length;
|
|
10213
|
+
const dp = Array.from(
|
|
10214
|
+
{ length: m + 1 },
|
|
10215
|
+
(_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
|
|
10216
|
+
);
|
|
10217
|
+
for (let i = 1; i <= m; i++) {
|
|
10218
|
+
for (let j = 1; j <= n; j++) {
|
|
10219
|
+
if (a[i - 1] === b[j - 1]) {
|
|
10220
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
10221
|
+
} else {
|
|
10222
|
+
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10225
|
+
}
|
|
10226
|
+
return dp[m][n];
|
|
10227
|
+
}
|
|
10228
|
+
function findSimilarSymbols(graph, symbolName, limit) {
|
|
10229
|
+
const clampedLimit = Math.min(Math.max(1, limit), 50);
|
|
10230
|
+
const allNodes = [...graph.allNodes()];
|
|
10231
|
+
const targetNode = allNodes.find((n) => n.name === symbolName);
|
|
10232
|
+
if (!targetNode) {
|
|
10233
|
+
return { similar: [] };
|
|
10234
|
+
}
|
|
10235
|
+
let targetCluster = null;
|
|
10236
|
+
for (const edge of graph.findEdgesFrom(targetNode.id)) {
|
|
10237
|
+
if (edge.kind === "belongs_to") {
|
|
10238
|
+
const clusterNode = graph.getNode(edge.target);
|
|
10239
|
+
if (clusterNode) {
|
|
10240
|
+
targetCluster = clusterNode.name;
|
|
10241
|
+
break;
|
|
10242
|
+
}
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
if (!targetCluster) {
|
|
10246
|
+
for (const edge of graph.findEdgesTo(targetNode.id)) {
|
|
10247
|
+
if (edge.kind === "belongs_to") {
|
|
10248
|
+
const clusterNode = graph.getNode(edge.source);
|
|
10249
|
+
if (clusterNode) {
|
|
10250
|
+
targetCluster = clusterNode.name;
|
|
10251
|
+
break;
|
|
10252
|
+
}
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
10255
|
+
}
|
|
10256
|
+
const results = [];
|
|
10257
|
+
for (const node of allNodes) {
|
|
10258
|
+
if (node.id === targetNode.id) continue;
|
|
10259
|
+
const maxLen = Math.max(symbolName.length, node.name.length);
|
|
10260
|
+
const nameSim = maxLen === 0 ? 1 : 1 - levenshtein(symbolName, node.name) / maxLen;
|
|
10261
|
+
const structuralSim = node.kind === targetNode.kind ? 0.5 : 0;
|
|
10262
|
+
const combined = 0.5 * nameSim + 0.5 * structuralSim;
|
|
10263
|
+
const reasons = [];
|
|
10264
|
+
if (nameSim >= 0.6) reasons.push("similar name");
|
|
10265
|
+
if (node.kind === targetNode.kind) reasons.push("same kind");
|
|
10266
|
+
if (targetCluster !== null) {
|
|
10267
|
+
let nodeCluster = null;
|
|
10268
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
10269
|
+
if (edge.kind === "belongs_to") {
|
|
10270
|
+
const clusterNode = graph.getNode(edge.target);
|
|
10271
|
+
if (clusterNode) {
|
|
10272
|
+
nodeCluster = clusterNode.name;
|
|
10273
|
+
break;
|
|
10274
|
+
}
|
|
10275
|
+
}
|
|
10276
|
+
}
|
|
10277
|
+
if (!nodeCluster) {
|
|
10278
|
+
for (const edge of graph.findEdgesTo(node.id)) {
|
|
10279
|
+
if (edge.kind === "belongs_to") {
|
|
10280
|
+
const clusterNode = graph.getNode(edge.source);
|
|
10281
|
+
if (clusterNode) {
|
|
10282
|
+
nodeCluster = clusterNode.name;
|
|
10283
|
+
break;
|
|
10284
|
+
}
|
|
10285
|
+
}
|
|
10286
|
+
}
|
|
10287
|
+
}
|
|
10288
|
+
if (nodeCluster !== null && nodeCluster === targetCluster) {
|
|
10289
|
+
reasons.push("same module");
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
10292
|
+
if (targetNode.metadata?.["cluster"] !== void 0 && node.metadata?.["cluster"] !== void 0 && node.metadata["cluster"] === targetNode.metadata["cluster"]) {
|
|
10293
|
+
if (!reasons.includes("same module")) reasons.push("same module");
|
|
10294
|
+
}
|
|
10295
|
+
results.push({ name: node.name, similarity: combined, reasons });
|
|
10296
|
+
}
|
|
10297
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
10298
|
+
return { similar: results.slice(0, clampedLimit) };
|
|
10299
|
+
}
|
|
10300
|
+
|
|
10301
|
+
// src/query/health-report.ts
|
|
10302
|
+
function computeHealthReport(graph, scope) {
|
|
10303
|
+
const wholeRepo = scope === ".";
|
|
10304
|
+
function inScope(filePath) {
|
|
10305
|
+
if (wholeRepo) return true;
|
|
10306
|
+
return filePath.startsWith(scope) || filePath.includes(scope);
|
|
10307
|
+
}
|
|
10308
|
+
const scopedNodes = [...graph.allNodes()].filter((n) => inScope(n.filePath));
|
|
10309
|
+
const deadCodeKinds = /* @__PURE__ */ new Set(["function", "method", "class"]);
|
|
10310
|
+
const deadCode = [];
|
|
10311
|
+
for (const node of scopedNodes) {
|
|
10312
|
+
if (!deadCodeKinds.has(node.kind)) continue;
|
|
10313
|
+
if (node.exported === true) continue;
|
|
10314
|
+
let hasIncoming = false;
|
|
10315
|
+
for (const _edge of graph.findEdgesTo(node.id)) {
|
|
10316
|
+
hasIncoming = true;
|
|
10317
|
+
break;
|
|
10318
|
+
}
|
|
10319
|
+
if (!hasIncoming) {
|
|
10320
|
+
deadCode.push({ name: node.name, filePath: node.filePath, kind: node.kind });
|
|
10321
|
+
if (deadCode.length >= 20) break;
|
|
10322
|
+
}
|
|
10323
|
+
}
|
|
10324
|
+
const cycles = [];
|
|
10325
|
+
const scopedNodeIds = new Set(scopedNodes.map((n) => n.id));
|
|
10326
|
+
const importAdj = /* @__PURE__ */ new Map();
|
|
10327
|
+
for (const node of scopedNodes) {
|
|
10328
|
+
importAdj.set(node.id, []);
|
|
10329
|
+
}
|
|
10330
|
+
for (const edge of graph.findEdgesByKind("imports")) {
|
|
10331
|
+
if (scopedNodeIds.has(edge.source) && scopedNodeIds.has(edge.target)) {
|
|
10332
|
+
importAdj.get(edge.source).push(edge.target);
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10336
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
10337
|
+
const stackPath = [];
|
|
10338
|
+
function dfs(nodeId) {
|
|
10339
|
+
if (cycles.length >= 5) return;
|
|
10340
|
+
visited.add(nodeId);
|
|
10341
|
+
inStack.add(nodeId);
|
|
10342
|
+
stackPath.push(nodeId);
|
|
10343
|
+
for (const neighborId of importAdj.get(nodeId) ?? []) {
|
|
10344
|
+
if (cycles.length >= 5) break;
|
|
10345
|
+
if (inStack.has(neighborId)) {
|
|
10346
|
+
const cycleStart = stackPath.indexOf(neighborId);
|
|
10347
|
+
const cyclePath = stackPath.slice(cycleStart).map((id) => {
|
|
10348
|
+
const node = graph.getNode(id);
|
|
10349
|
+
return node ? node.name : id;
|
|
10350
|
+
});
|
|
10351
|
+
cycles.push(cyclePath);
|
|
10352
|
+
} else if (!visited.has(neighborId)) {
|
|
10353
|
+
dfs(neighborId);
|
|
10354
|
+
}
|
|
10355
|
+
}
|
|
10356
|
+
stackPath.pop();
|
|
10357
|
+
inStack.delete(nodeId);
|
|
10358
|
+
}
|
|
10359
|
+
for (const node of scopedNodes) {
|
|
10360
|
+
if (cycles.length >= 5) break;
|
|
10361
|
+
if (!visited.has(node.id)) {
|
|
10362
|
+
dfs(node.id);
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
10365
|
+
const godNodes = [];
|
|
10366
|
+
for (const node of scopedNodes) {
|
|
10367
|
+
let edgeCount = 0;
|
|
10368
|
+
for (const _edge of graph.findEdgesFrom(node.id)) {
|
|
10369
|
+
edgeCount++;
|
|
10370
|
+
}
|
|
10371
|
+
if (edgeCount > 10) {
|
|
10372
|
+
godNodes.push({ name: node.name, edgeCount, filePath: node.filePath });
|
|
10373
|
+
}
|
|
10374
|
+
}
|
|
10375
|
+
godNodes.sort((a, b) => b.edgeCount - a.edgeCount);
|
|
10376
|
+
godNodes.splice(10);
|
|
10377
|
+
const filePathToNodes = /* @__PURE__ */ new Map();
|
|
10378
|
+
for (const node of scopedNodes) {
|
|
10379
|
+
if (!node.filePath) continue;
|
|
10380
|
+
let arr = filePathToNodes.get(node.filePath);
|
|
10381
|
+
if (!arr) {
|
|
10382
|
+
arr = [];
|
|
10383
|
+
filePathToNodes.set(node.filePath, arr);
|
|
10384
|
+
}
|
|
10385
|
+
arr.push(node.id);
|
|
10386
|
+
}
|
|
10387
|
+
const orphanFiles = [];
|
|
10388
|
+
for (const [filePath, nodeIds] of filePathToNodes) {
|
|
10389
|
+
if (orphanFiles.length >= 10) break;
|
|
10390
|
+
let hasAnyEdge = false;
|
|
10391
|
+
for (const nodeId of nodeIds) {
|
|
10392
|
+
let hasOut = false;
|
|
10393
|
+
for (const _edge of graph.findEdgesFrom(nodeId)) {
|
|
10394
|
+
hasOut = true;
|
|
10395
|
+
break;
|
|
10396
|
+
}
|
|
10397
|
+
let hasIn = false;
|
|
10398
|
+
for (const _edge of graph.findEdgesTo(nodeId)) {
|
|
10399
|
+
hasIn = true;
|
|
10400
|
+
break;
|
|
10401
|
+
}
|
|
10402
|
+
if (hasOut || hasIn) {
|
|
10403
|
+
hasAnyEdge = true;
|
|
10404
|
+
break;
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
if (!hasAnyEdge) {
|
|
10408
|
+
orphanFiles.push(filePath);
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
const hotspotCandidates = [];
|
|
10412
|
+
for (const node of scopedNodes) {
|
|
10413
|
+
const visitedBfs = /* @__PURE__ */ new Set();
|
|
10414
|
+
const queue = [{ id: node.id, depth: 0 }];
|
|
10415
|
+
while (queue.length > 0) {
|
|
10416
|
+
const item = queue.shift();
|
|
10417
|
+
if (item.depth > 5 || visitedBfs.has(item.id)) continue;
|
|
10418
|
+
visitedBfs.add(item.id);
|
|
10419
|
+
for (const edge of graph.findEdgesTo(item.id)) {
|
|
10420
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
10421
|
+
if (!visitedBfs.has(edge.source)) {
|
|
10422
|
+
queue.push({ id: edge.source, depth: item.depth + 1 });
|
|
10423
|
+
}
|
|
10424
|
+
}
|
|
10425
|
+
}
|
|
10426
|
+
}
|
|
10427
|
+
const blastRadius = visitedBfs.size - 1;
|
|
10428
|
+
hotspotCandidates.push({ name: node.name, blastRadius, filePath: node.filePath });
|
|
10429
|
+
}
|
|
10430
|
+
hotspotCandidates.sort((a, b) => b.blastRadius - a.blastRadius);
|
|
10431
|
+
const complexityHotspots = hotspotCandidates.slice(0, 5);
|
|
10432
|
+
const healthScore = Math.max(
|
|
10433
|
+
0,
|
|
10434
|
+
Math.min(100, 100 - deadCode.length * 2 - cycles.length * 5 - godNodes.length * 3)
|
|
10435
|
+
);
|
|
10436
|
+
return {
|
|
10437
|
+
healthScore,
|
|
10438
|
+
deadCode,
|
|
10439
|
+
cycles,
|
|
10440
|
+
godNodes,
|
|
10441
|
+
orphanFiles,
|
|
10442
|
+
complexityHotspots
|
|
10443
|
+
};
|
|
10444
|
+
}
|
|
10445
|
+
|
|
10446
|
+
// src/query/suggest-tests.ts
|
|
10447
|
+
function getSuggestedCases(symbolName) {
|
|
10448
|
+
const lower = symbolName.toLowerCase();
|
|
10449
|
+
if (/parse|validate|check|verify/.test(lower)) {
|
|
10450
|
+
return [
|
|
10451
|
+
"Valid input \u2192 success",
|
|
10452
|
+
"Invalid input \u2192 throws error",
|
|
10453
|
+
"Edge case: empty/null input \u2192 handled gracefully"
|
|
10454
|
+
];
|
|
10455
|
+
}
|
|
10456
|
+
if (/create|add|insert|save/.test(lower)) {
|
|
10457
|
+
return [
|
|
10458
|
+
"Success: valid data \u2192 created",
|
|
10459
|
+
"Duplicate: existing item \u2192 error or no-op",
|
|
10460
|
+
"Missing required fields \u2192 validation error"
|
|
10461
|
+
];
|
|
10462
|
+
}
|
|
10463
|
+
if (/delete|remove|destroy/.test(lower)) {
|
|
10464
|
+
return [
|
|
10465
|
+
"Existing item \u2192 deleted successfully",
|
|
10466
|
+
"Non-existent item \u2192 no error or 404",
|
|
10467
|
+
"Unauthorized access \u2192 rejected"
|
|
10468
|
+
];
|
|
10469
|
+
}
|
|
10470
|
+
if (/get|find|fetch|load/.test(lower)) {
|
|
10471
|
+
return [
|
|
10472
|
+
"Found: returns correct data",
|
|
10473
|
+
"Not found: returns null or throws",
|
|
10474
|
+
"Empty collection: returns []"
|
|
10475
|
+
];
|
|
10476
|
+
}
|
|
10477
|
+
return [
|
|
10478
|
+
"Happy path: valid input \u2192 expected output",
|
|
10479
|
+
"Error case: invalid input \u2192 error handled",
|
|
10480
|
+
"Edge case: boundary values \u2192 correct behavior"
|
|
10481
|
+
];
|
|
10482
|
+
}
|
|
10483
|
+
function suggestTests(graph, symbolName) {
|
|
10484
|
+
let targetNode = void 0;
|
|
10485
|
+
for (const node of graph.allNodes()) {
|
|
10486
|
+
if (node.name === symbolName) {
|
|
10487
|
+
targetNode = node;
|
|
10488
|
+
break;
|
|
10489
|
+
}
|
|
10490
|
+
}
|
|
10491
|
+
if (!targetNode) {
|
|
10492
|
+
return { error: `Symbol not found: ${symbolName}` };
|
|
10493
|
+
}
|
|
10494
|
+
const targetId = targetNode.id;
|
|
10495
|
+
const callPaths = [];
|
|
10496
|
+
const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
|
|
10497
|
+
while (pathQueue.length > 0 && callPaths.length < 5) {
|
|
10498
|
+
const { id, path: path34, depth } = pathQueue.shift();
|
|
10499
|
+
let hasCallers = false;
|
|
10500
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
10501
|
+
if (edge.kind !== "calls") continue;
|
|
10502
|
+
const callerNode = graph.getNode(edge.source);
|
|
10503
|
+
if (!callerNode) continue;
|
|
10504
|
+
hasCallers = true;
|
|
10505
|
+
const newPath = [callerNode.name, ...path34];
|
|
10506
|
+
if (depth + 1 >= 3 || callPaths.length >= 5) {
|
|
10507
|
+
if (callPaths.length < 5) callPaths.push(newPath);
|
|
10508
|
+
continue;
|
|
10509
|
+
}
|
|
10510
|
+
pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
|
|
10511
|
+
}
|
|
10512
|
+
if (!hasCallers && path34.length > 1) {
|
|
10513
|
+
callPaths.push(path34);
|
|
10514
|
+
}
|
|
10515
|
+
}
|
|
10516
|
+
if (callPaths.length === 0) {
|
|
10517
|
+
for (const edge of graph.findEdgesTo(targetId)) {
|
|
10518
|
+
if (edge.kind !== "calls") continue;
|
|
10519
|
+
const callerNode = graph.getNode(edge.source);
|
|
10520
|
+
if (!callerNode) continue;
|
|
10521
|
+
callPaths.push([callerNode.name, symbolName]);
|
|
10522
|
+
if (callPaths.length >= 5) break;
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10525
|
+
const existingTestFiles = /* @__PURE__ */ new Set();
|
|
10526
|
+
for (const edge of graph.findEdgesTo(targetId)) {
|
|
10527
|
+
if (edge.kind !== "imports") continue;
|
|
10528
|
+
const importerNode = graph.getNode(edge.source);
|
|
10529
|
+
if (!importerNode) continue;
|
|
10530
|
+
if (importerNode.filePath.includes(".test.") || importerNode.filePath.includes(".spec.")) {
|
|
10531
|
+
existingTestFiles.add(importerNode.filePath);
|
|
10532
|
+
}
|
|
10533
|
+
}
|
|
10534
|
+
const existingTests = [...existingTestFiles];
|
|
10535
|
+
const untestedCallers = [];
|
|
10536
|
+
for (const edge of graph.findEdgesTo(targetId)) {
|
|
10537
|
+
if (edge.kind !== "calls") continue;
|
|
10538
|
+
const callerNode = graph.getNode(edge.source);
|
|
10539
|
+
if (!callerNode) continue;
|
|
10540
|
+
if (callerNode.filePath.includes(".test.") || callerNode.filePath.includes(".spec.")) {
|
|
10541
|
+
continue;
|
|
10542
|
+
}
|
|
10543
|
+
let callerHasTest = false;
|
|
10544
|
+
for (const callerImportEdge of graph.findEdgesTo(callerNode.id)) {
|
|
10545
|
+
if (callerImportEdge.kind !== "imports") continue;
|
|
10546
|
+
const importerOfCaller = graph.getNode(callerImportEdge.source);
|
|
10547
|
+
if (!importerOfCaller) continue;
|
|
10548
|
+
if (importerOfCaller.filePath.includes(".test.") || importerOfCaller.filePath.includes(".spec.")) {
|
|
10549
|
+
callerHasTest = true;
|
|
10550
|
+
break;
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
if (!callerHasTest) {
|
|
10554
|
+
untestedCallers.push(callerNode.name);
|
|
10555
|
+
}
|
|
10556
|
+
}
|
|
10557
|
+
const suggestedCases = getSuggestedCases(symbolName);
|
|
10558
|
+
return {
|
|
10559
|
+
callPaths,
|
|
10560
|
+
suggestedCases,
|
|
10561
|
+
existingTests,
|
|
10562
|
+
untestedCallers
|
|
10563
|
+
};
|
|
10564
|
+
}
|
|
10565
|
+
|
|
10566
|
+
// src/query/cluster-summary.ts
|
|
10567
|
+
function getPathPrefix(filePath) {
|
|
10568
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
10569
|
+
return parts.slice(0, 2).join("/");
|
|
10570
|
+
}
|
|
10571
|
+
function summarizeCluster(graph, cluster) {
|
|
10572
|
+
const clusterNodes = [...graph.allNodes()].filter(
|
|
10573
|
+
(n) => n.filePath.startsWith(cluster) || n.metadata?.["cluster"] === cluster
|
|
10574
|
+
);
|
|
10575
|
+
if (clusterNodes.length === 0) {
|
|
10576
|
+
return { error: `Cluster not found: ${cluster}` };
|
|
10577
|
+
}
|
|
10578
|
+
const clusterNodeIds = new Set(clusterNodes.map((n) => n.id));
|
|
10579
|
+
const callerCountMap = /* @__PURE__ */ new Map();
|
|
10580
|
+
for (const node of clusterNodes) {
|
|
10581
|
+
let count = 0;
|
|
10582
|
+
for (const _edge of graph.findEdgesTo(node.id)) {
|
|
10583
|
+
count++;
|
|
10584
|
+
}
|
|
10585
|
+
callerCountMap.set(node.id, count);
|
|
10586
|
+
}
|
|
10587
|
+
const sortedByCallers = [...clusterNodes].sort(
|
|
10588
|
+
(a, b) => (callerCountMap.get(b.id) ?? 0) - (callerCountMap.get(a.id) ?? 0)
|
|
10589
|
+
);
|
|
10590
|
+
const keySymbols = sortedByCallers.slice(0, 5).map((n) => ({
|
|
10591
|
+
name: n.name,
|
|
10592
|
+
callerCount: callerCountMap.get(n.id) ?? 0
|
|
10593
|
+
}));
|
|
10594
|
+
const depsSet = /* @__PURE__ */ new Set();
|
|
10595
|
+
for (const node of clusterNodes) {
|
|
10596
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
10597
|
+
if (edge.kind !== "imports") continue;
|
|
10598
|
+
const targetNode = graph.getNode(edge.target);
|
|
10599
|
+
if (!targetNode) continue;
|
|
10600
|
+
if (!clusterNodeIds.has(targetNode.id)) {
|
|
10601
|
+
const prefix = getPathPrefix(targetNode.filePath);
|
|
10602
|
+
depsSet.add(prefix);
|
|
10603
|
+
}
|
|
10604
|
+
}
|
|
10605
|
+
}
|
|
10606
|
+
const dependencies = [...depsSet];
|
|
10607
|
+
const dependentsSet = /* @__PURE__ */ new Set();
|
|
10608
|
+
for (const node of clusterNodes) {
|
|
10609
|
+
for (const edge of graph.findEdgesTo(node.id)) {
|
|
10610
|
+
if (edge.kind !== "imports") continue;
|
|
10611
|
+
const sourceNode = graph.getNode(edge.source);
|
|
10612
|
+
if (!sourceNode) continue;
|
|
10613
|
+
if (!clusterNodeIds.has(sourceNode.id)) {
|
|
10614
|
+
const prefix = getPathPrefix(sourceNode.filePath);
|
|
10615
|
+
dependentsSet.add(prefix);
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
}
|
|
10619
|
+
const dependents = [...dependentsSet];
|
|
10620
|
+
const healthResult = computeHealthReport(graph, cluster);
|
|
10621
|
+
const health = { score: healthResult.healthScore };
|
|
10622
|
+
const symbolCount = {};
|
|
10623
|
+
for (const node of clusterNodes) {
|
|
10624
|
+
symbolCount[node.kind] = (symbolCount[node.kind] ?? 0) + 1;
|
|
10625
|
+
}
|
|
10626
|
+
let purpose;
|
|
10627
|
+
const topNode = sortedByCallers[0];
|
|
10628
|
+
if (topNode?.metadata?.["summary"] && typeof topNode.metadata["summary"] === "string") {
|
|
10629
|
+
purpose = topNode.metadata["summary"];
|
|
10630
|
+
} else {
|
|
10631
|
+
const clusterName = cluster.split("/").pop() ?? cluster;
|
|
10632
|
+
purpose = `Handles ${clusterName.replace(/[-_/]/g, " ")} functionality`;
|
|
10633
|
+
}
|
|
10634
|
+
return {
|
|
10635
|
+
cluster,
|
|
10636
|
+
purpose,
|
|
10637
|
+
keySymbols,
|
|
10638
|
+
dependencies,
|
|
10639
|
+
dependents,
|
|
10640
|
+
health,
|
|
10641
|
+
symbolCount
|
|
10642
|
+
};
|
|
9586
10643
|
}
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
init_metadata();
|
|
9590
|
-
init_group_registry();
|
|
9591
|
-
init_tracing();
|
|
9592
|
-
init_metrics();
|
|
10644
|
+
|
|
10645
|
+
// src/mcp-server/server.ts
|
|
9593
10646
|
function createMcpServer(graph, repoName, workspaceRoot) {
|
|
9594
10647
|
const server = new Server(
|
|
9595
10648
|
{ name: "code-intel", version: "0.1.0" },
|
|
@@ -9619,7 +10672,8 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9619
10672
|
type: "object",
|
|
9620
10673
|
properties: {
|
|
9621
10674
|
query: { type: "string", description: "Search query (symbol name, keyword, or partial match)" },
|
|
9622
|
-
|
|
10675
|
+
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
10676
|
+
limit: { type: "number", description: "Max results per page (default: 50, max: 500)" },
|
|
9623
10677
|
..._tokenProp
|
|
9624
10678
|
},
|
|
9625
10679
|
required: ["query"]
|
|
@@ -9662,6 +10716,8 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9662
10716
|
type: "object",
|
|
9663
10717
|
properties: {
|
|
9664
10718
|
file_path: { type: "string", description: 'File path (partial match is supported, e.g. "auth/login.ts")' },
|
|
10719
|
+
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
10720
|
+
limit: { type: "number", description: "Max results per page (default: 50, max: 500)" },
|
|
9665
10721
|
..._tokenProp
|
|
9666
10722
|
},
|
|
9667
10723
|
required: ["file_path"]
|
|
@@ -9691,7 +10747,8 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9691
10747
|
type: "string",
|
|
9692
10748
|
description: "Filter by node kind: function | class | interface | method | type_alias | constant | enum (optional)"
|
|
9693
10749
|
},
|
|
9694
|
-
|
|
10750
|
+
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
10751
|
+
limit: { type: "number", description: "Max results per page (default: 50, max: 500)" },
|
|
9695
10752
|
..._tokenProp
|
|
9696
10753
|
}
|
|
9697
10754
|
}
|
|
@@ -9708,7 +10765,8 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9708
10765
|
inputSchema: {
|
|
9709
10766
|
type: "object",
|
|
9710
10767
|
properties: {
|
|
9711
|
-
|
|
10768
|
+
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
10769
|
+
limit: { type: "number", description: "Max clusters per page (default: 50, max: 500)" },
|
|
9712
10770
|
..._tokenProp
|
|
9713
10771
|
}
|
|
9714
10772
|
}
|
|
@@ -9719,7 +10777,8 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9719
10777
|
inputSchema: {
|
|
9720
10778
|
type: "object",
|
|
9721
10779
|
properties: {
|
|
9722
|
-
|
|
10780
|
+
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
10781
|
+
limit: { type: "number", description: "Max flows per page (default: 50, max: 500)" },
|
|
9723
10782
|
..._tokenProp
|
|
9724
10783
|
}
|
|
9725
10784
|
}
|
|
@@ -9841,6 +10900,91 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9841
10900
|
},
|
|
9842
10901
|
required: ["name"]
|
|
9843
10902
|
}
|
|
10903
|
+
},
|
|
10904
|
+
// ── Reasoning / analysis tools ────────────────────────────────────────
|
|
10905
|
+
{
|
|
10906
|
+
name: "explain_relationship",
|
|
10907
|
+
description: "Explain how two symbols are connected: directed paths, shared imports, and heritage (extends/implements). Returns up to 10 paths with at most 5 hops each.",
|
|
10908
|
+
inputSchema: {
|
|
10909
|
+
type: "object",
|
|
10910
|
+
properties: {
|
|
10911
|
+
from: { type: "string", description: "Source symbol name" },
|
|
10912
|
+
to: { type: "string", description: "Target symbol name" },
|
|
10913
|
+
..._tokenProp
|
|
10914
|
+
},
|
|
10915
|
+
required: ["from", "to"]
|
|
10916
|
+
}
|
|
10917
|
+
},
|
|
10918
|
+
{
|
|
10919
|
+
name: "pr_impact",
|
|
10920
|
+
description: "Given changed files or a unified diff, compute full blast radius with risk scores (HIGH/MEDIUM/LOW), test coverage gaps, and top files to review.",
|
|
10921
|
+
inputSchema: {
|
|
10922
|
+
type: "object",
|
|
10923
|
+
properties: {
|
|
10924
|
+
changedFiles: {
|
|
10925
|
+
type: "array",
|
|
10926
|
+
items: { type: "string" },
|
|
10927
|
+
description: "List of changed file paths (relative or absolute)"
|
|
10928
|
+
},
|
|
10929
|
+
diff: {
|
|
10930
|
+
type: "string",
|
|
10931
|
+
description: "Raw unified diff text. Changed files are extracted automatically."
|
|
10932
|
+
},
|
|
10933
|
+
maxHops: {
|
|
10934
|
+
type: "number",
|
|
10935
|
+
description: "Maximum BFS depth for blast radius (default: 5)"
|
|
10936
|
+
},
|
|
10937
|
+
..._tokenProp
|
|
10938
|
+
}
|
|
10939
|
+
}
|
|
10940
|
+
},
|
|
10941
|
+
{
|
|
10942
|
+
name: "similar_symbols",
|
|
10943
|
+
description: "Find symbols with similar names or structure using Levenshtein distance and kind matching. Useful for finding related functions, classes, or interfaces.",
|
|
10944
|
+
inputSchema: {
|
|
10945
|
+
type: "object",
|
|
10946
|
+
properties: {
|
|
10947
|
+
symbol: { type: "string", description: "Symbol name to find similar symbols for" },
|
|
10948
|
+
limit: { type: "number", description: "Maximum number of results (default: 10, max: 50)" },
|
|
10949
|
+
..._tokenProp
|
|
10950
|
+
},
|
|
10951
|
+
required: ["symbol"]
|
|
10952
|
+
}
|
|
10953
|
+
},
|
|
10954
|
+
{
|
|
10955
|
+
name: "health_report",
|
|
10956
|
+
description: "Code health signals for a scope: dead code, cycles, god nodes, orphan files, complexity hotspots",
|
|
10957
|
+
inputSchema: {
|
|
10958
|
+
type: "object",
|
|
10959
|
+
properties: {
|
|
10960
|
+
scope: { type: "string", description: "Directory scope, e.g. 'src/api/' or '.' for whole repo" },
|
|
10961
|
+
..._tokenProp
|
|
10962
|
+
}
|
|
10963
|
+
}
|
|
10964
|
+
},
|
|
10965
|
+
{
|
|
10966
|
+
name: "suggest_tests",
|
|
10967
|
+
description: "Suggest test cases for a symbol: call paths, suggested cases, existing tests, untested callers",
|
|
10968
|
+
inputSchema: {
|
|
10969
|
+
type: "object",
|
|
10970
|
+
properties: {
|
|
10971
|
+
symbol: { type: "string", description: "Symbol name to generate test suggestions for" },
|
|
10972
|
+
..._tokenProp
|
|
10973
|
+
},
|
|
10974
|
+
required: ["symbol"]
|
|
10975
|
+
}
|
|
10976
|
+
},
|
|
10977
|
+
{
|
|
10978
|
+
name: "cluster_summary",
|
|
10979
|
+
description: "Rich summary of a module/cluster: purpose, key symbols, dependencies, health",
|
|
10980
|
+
inputSchema: {
|
|
10981
|
+
type: "object",
|
|
10982
|
+
properties: {
|
|
10983
|
+
cluster: { type: "string", description: "Cluster path e.g. 'src/auth'" },
|
|
10984
|
+
..._tokenProp
|
|
10985
|
+
},
|
|
10986
|
+
required: ["cluster"]
|
|
10987
|
+
}
|
|
9844
10988
|
}
|
|
9845
10989
|
]
|
|
9846
10990
|
}));
|
|
@@ -9911,8 +11055,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9911
11055
|
for (const edge of graph.allEdges()) {
|
|
9912
11056
|
edgeCounts[edge.kind] = (edgeCounts[edge.kind] ?? 0) + 1;
|
|
9913
11057
|
}
|
|
9914
|
-
const { computeHealthReport:
|
|
9915
|
-
const healthReport =
|
|
11058
|
+
const { computeHealthReport: computeHealthReport3 } = await Promise.resolve().then(() => (init_health_score(), health_score_exports));
|
|
11059
|
+
const healthReport = computeHealthReport3(graph);
|
|
9916
11060
|
const health = {
|
|
9917
11061
|
score: Math.round(healthReport.score),
|
|
9918
11062
|
grade: healthReport.grade,
|
|
@@ -9937,10 +11081,37 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9937
11081
|
// ── search ─────────────────────────────────────────────────────────────
|
|
9938
11082
|
case "search": {
|
|
9939
11083
|
const query = a.query;
|
|
9940
|
-
const
|
|
11084
|
+
const offset = a.offset ?? 0;
|
|
11085
|
+
const effectiveLimit = Math.min(a.limit ?? 50, 500);
|
|
9941
11086
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
9942
|
-
const
|
|
9943
|
-
|
|
11087
|
+
const fetchLimit = Math.min(offset + effectiveLimit, 500);
|
|
11088
|
+
const { results: allResults, searchMode } = await hybridSearch(graph, query, fetchLimit, { vectorDbPath: vdbPath });
|
|
11089
|
+
const total = allResults.length;
|
|
11090
|
+
const results = allResults.slice(offset, offset + effectiveLimit);
|
|
11091
|
+
const hasMore = offset + effectiveLimit < total;
|
|
11092
|
+
const suggestNextTools = [];
|
|
11093
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] !== "false";
|
|
11094
|
+
if (suggestEnabled && results.length > 0) {
|
|
11095
|
+
const topName = results[0].name;
|
|
11096
|
+
suggestNextTools.push(
|
|
11097
|
+
{ tool: "inspect", reason: "Inspect the top result in detail", input: { symbol: topName } },
|
|
11098
|
+
{ tool: "similar_symbols", reason: "Find symbols similar to the top result", input: { symbol: topName } }
|
|
11099
|
+
);
|
|
11100
|
+
}
|
|
11101
|
+
return {
|
|
11102
|
+
content: [{
|
|
11103
|
+
type: "text",
|
|
11104
|
+
text: JSON.stringify({
|
|
11105
|
+
results,
|
|
11106
|
+
searchMode,
|
|
11107
|
+
total,
|
|
11108
|
+
offset,
|
|
11109
|
+
limit: effectiveLimit,
|
|
11110
|
+
hasMore,
|
|
11111
|
+
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
11112
|
+
}, null, 2)
|
|
11113
|
+
}]
|
|
11114
|
+
};
|
|
9944
11115
|
}
|
|
9945
11116
|
// ── inspect ────────────────────────────────────────────────────────────
|
|
9946
11117
|
case "inspect": {
|
|
@@ -9949,6 +11120,26 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9949
11120
|
if (!node) return { content: [{ type: "text", text: `Symbol "${symbolName}" not found. Try search first.` }] };
|
|
9950
11121
|
const incoming = [...graph.findEdgesTo(node.id)];
|
|
9951
11122
|
const outgoing = [...graph.findEdgesFrom(node.id)];
|
|
11123
|
+
const callers = incoming.filter((e) => e.kind === "calls").map((e) => ({
|
|
11124
|
+
id: e.source,
|
|
11125
|
+
name: graph.getNode(e.source)?.name,
|
|
11126
|
+
file: graph.getNode(e.source)?.filePath
|
|
11127
|
+
}));
|
|
11128
|
+
const callees = outgoing.filter((e) => e.kind === "calls").map((e) => ({
|
|
11129
|
+
id: e.target,
|
|
11130
|
+
name: graph.getNode(e.target)?.name,
|
|
11131
|
+
file: graph.getNode(e.target)?.filePath
|
|
11132
|
+
}));
|
|
11133
|
+
const cluster = incoming.filter((e) => e.kind === "belongs_to").map((e) => graph.getNode(e.target)?.name)[0];
|
|
11134
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] !== "false";
|
|
11135
|
+
const suggestNextTools = [];
|
|
11136
|
+
if (suggestEnabled) {
|
|
11137
|
+
const topCallerName = callers[0]?.name;
|
|
11138
|
+
suggestNextTools.push(
|
|
11139
|
+
...topCallerName ? [{ tool: "explain_relationship", reason: "Explain connection to a related symbol", input: { from: node.name, to: topCallerName } }] : [],
|
|
11140
|
+
...cluster ? [{ tool: "cluster_summary", reason: "Summarize the module this symbol belongs to", input: { cluster } }] : [{ tool: "cluster_summary", reason: "Summarize the module this symbol belongs to", input: { cluster: node.filePath } }]
|
|
11141
|
+
);
|
|
11142
|
+
}
|
|
9952
11143
|
return {
|
|
9953
11144
|
content: [{
|
|
9954
11145
|
type: "text",
|
|
@@ -9962,16 +11153,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9962
11153
|
endLine: node.endLine,
|
|
9963
11154
|
exported: node.exported
|
|
9964
11155
|
},
|
|
9965
|
-
callers
|
|
9966
|
-
|
|
9967
|
-
name: graph.getNode(e.source)?.name,
|
|
9968
|
-
file: graph.getNode(e.source)?.filePath
|
|
9969
|
-
})),
|
|
9970
|
-
callees: outgoing.filter((e) => e.kind === "calls").map((e) => ({
|
|
9971
|
-
id: e.target,
|
|
9972
|
-
name: graph.getNode(e.target)?.name,
|
|
9973
|
-
file: graph.getNode(e.target)?.filePath
|
|
9974
|
-
})),
|
|
11156
|
+
callers,
|
|
11157
|
+
callees,
|
|
9975
11158
|
imports: incoming.filter((e) => e.kind === "imports").map((e) => graph.getNode(e.source)?.name),
|
|
9976
11159
|
importedBy: outgoing.filter((e) => e.kind === "imports").map((e) => graph.getNode(e.target)?.name),
|
|
9977
11160
|
extends: outgoing.filter((e) => e.kind === "extends").map((e) => graph.getNode(e.target)?.name),
|
|
@@ -9980,8 +11163,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9980
11163
|
name: graph.getNode(e.target)?.name,
|
|
9981
11164
|
kind: graph.getNode(e.target)?.kind
|
|
9982
11165
|
})),
|
|
9983
|
-
cluster
|
|
9984
|
-
content: node.content?.slice(0, 500)
|
|
11166
|
+
cluster,
|
|
11167
|
+
content: node.content?.slice(0, 500),
|
|
11168
|
+
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9985
11169
|
}, null, 2)
|
|
9986
11170
|
}]
|
|
9987
11171
|
};
|
|
@@ -10017,6 +11201,16 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10017
11201
|
return n ? { id, name: n.name, kind: n.kind, filePath: n.filePath } : { id };
|
|
10018
11202
|
});
|
|
10019
11203
|
const risk = affected.size > 10 ? "HIGH" : affected.size > 5 ? "MEDIUM" : "LOW";
|
|
11204
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] !== "false";
|
|
11205
|
+
const suggestNextTools = [];
|
|
11206
|
+
if (suggestEnabled) {
|
|
11207
|
+
const highestRiskSymbol = node.name;
|
|
11208
|
+
const firstFilePath = affectedDetails[0]?.filePath ?? "";
|
|
11209
|
+
suggestNextTools.push(
|
|
11210
|
+
{ tool: "suggest_tests", reason: "Generate tests for the highest-risk symbol", input: { symbol: highestRiskSymbol } },
|
|
11211
|
+
{ tool: "pr_impact", reason: "Compute full PR impact for changed files", input: { changedFiles: [firstFilePath] } }
|
|
11212
|
+
);
|
|
11213
|
+
}
|
|
10020
11214
|
return {
|
|
10021
11215
|
content: [{
|
|
10022
11216
|
type: "text",
|
|
@@ -10024,7 +11218,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10024
11218
|
target: node.name,
|
|
10025
11219
|
affectedCount: affected.size,
|
|
10026
11220
|
riskLevel: risk,
|
|
10027
|
-
affected: affectedDetails
|
|
11221
|
+
affected: affectedDetails,
|
|
11222
|
+
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
10028
11223
|
}, null, 2)
|
|
10029
11224
|
}]
|
|
10030
11225
|
};
|
|
@@ -10032,17 +11227,27 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10032
11227
|
// ── file_symbols ───────────────────────────────────────────────────────
|
|
10033
11228
|
case "file_symbols": {
|
|
10034
11229
|
const filePath = a.file_path;
|
|
10035
|
-
const
|
|
11230
|
+
const offset = a.offset ?? 0;
|
|
11231
|
+
const effectiveLimit = Math.min(a.limit ?? 50, 500);
|
|
11232
|
+
const allMatches = [];
|
|
10036
11233
|
for (const node of graph.allNodes()) {
|
|
10037
11234
|
if (node.filePath && node.filePath.includes(filePath)) {
|
|
10038
|
-
|
|
11235
|
+
allMatches.push({ kind: node.kind, name: node.name, startLine: node.startLine, exported: node.exported });
|
|
10039
11236
|
}
|
|
10040
11237
|
}
|
|
10041
|
-
if (
|
|
11238
|
+
if (allMatches.length === 0) {
|
|
10042
11239
|
return { content: [{ type: "text", text: `No symbols found for file path matching "${filePath}".` }] };
|
|
10043
11240
|
}
|
|
10044
|
-
|
|
10045
|
-
|
|
11241
|
+
allMatches.sort((a2, b) => (a2.startLine ?? 0) - (b.startLine ?? 0));
|
|
11242
|
+
const total = allMatches.length;
|
|
11243
|
+
const matches = allMatches.slice(offset, offset + effectiveLimit);
|
|
11244
|
+
const hasMore = offset + effectiveLimit < total;
|
|
11245
|
+
return {
|
|
11246
|
+
content: [{
|
|
11247
|
+
type: "text",
|
|
11248
|
+
text: JSON.stringify({ symbols: matches, total, offset, limit: effectiveLimit, hasMore }, null, 2)
|
|
11249
|
+
}]
|
|
11250
|
+
};
|
|
10046
11251
|
}
|
|
10047
11252
|
// ── find_path ──────────────────────────────────────────────────────────
|
|
10048
11253
|
case "find_path": {
|
|
@@ -10088,15 +11293,23 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10088
11293
|
// ── list_exports ───────────────────────────────────────────────────────
|
|
10089
11294
|
case "list_exports": {
|
|
10090
11295
|
const kindFilter = a.kind;
|
|
10091
|
-
const
|
|
10092
|
-
const
|
|
11296
|
+
const offset = a.offset ?? 0;
|
|
11297
|
+
const effectiveLimit = Math.min(a.limit ?? 50, 500);
|
|
11298
|
+
const allExports = [];
|
|
10093
11299
|
for (const node of graph.allNodes()) {
|
|
10094
11300
|
if (!node.exported) continue;
|
|
10095
11301
|
if (kindFilter && node.kind !== kindFilter) continue;
|
|
10096
|
-
|
|
10097
|
-
if (exports$1.length >= limit) break;
|
|
11302
|
+
allExports.push({ kind: node.kind, name: node.name, filePath: node.filePath, startLine: node.startLine });
|
|
10098
11303
|
}
|
|
10099
|
-
|
|
11304
|
+
const total = allExports.length;
|
|
11305
|
+
const exports$1 = allExports.slice(offset, offset + effectiveLimit);
|
|
11306
|
+
const hasMore = offset + effectiveLimit < total;
|
|
11307
|
+
return {
|
|
11308
|
+
content: [{
|
|
11309
|
+
type: "text",
|
|
11310
|
+
text: JSON.stringify({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore }, null, 2)
|
|
11311
|
+
}]
|
|
11312
|
+
};
|
|
10100
11313
|
}
|
|
10101
11314
|
// ── routes ─────────────────────────────────────────────────────────────
|
|
10102
11315
|
case "routes": {
|
|
@@ -10110,8 +11323,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10110
11323
|
}
|
|
10111
11324
|
// ── clusters ───────────────────────────────────────────────────────────
|
|
10112
11325
|
case "clusters": {
|
|
10113
|
-
const
|
|
10114
|
-
const
|
|
11326
|
+
const offset = a.offset ?? 0;
|
|
11327
|
+
const effectiveLimit = Math.min(a.limit ?? 50, 500);
|
|
11328
|
+
const allClusters = [];
|
|
10115
11329
|
for (const node of graph.allNodes()) {
|
|
10116
11330
|
if (node.kind === "cluster") {
|
|
10117
11331
|
const members = [];
|
|
@@ -10123,35 +11337,50 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10123
11337
|
}
|
|
10124
11338
|
}
|
|
10125
11339
|
}
|
|
10126
|
-
|
|
11340
|
+
allClusters.push({
|
|
10127
11341
|
id: node.id,
|
|
10128
11342
|
name: node.name,
|
|
10129
11343
|
memberCount: node.metadata?.memberCount ?? members.length,
|
|
10130
11344
|
topSymbols: members.slice(0, 10)
|
|
10131
11345
|
});
|
|
10132
|
-
if (clusters.length >= limit) break;
|
|
10133
11346
|
}
|
|
10134
11347
|
}
|
|
10135
|
-
|
|
11348
|
+
const total = allClusters.length;
|
|
11349
|
+
const clusters = allClusters.slice(offset, offset + effectiveLimit);
|
|
11350
|
+
const hasMore = offset + effectiveLimit < total;
|
|
11351
|
+
return {
|
|
11352
|
+
content: [{
|
|
11353
|
+
type: "text",
|
|
11354
|
+
text: JSON.stringify({ clusters, total, offset, limit: effectiveLimit, hasMore }, null, 2)
|
|
11355
|
+
}]
|
|
11356
|
+
};
|
|
10136
11357
|
}
|
|
10137
11358
|
// ── flows ──────────────────────────────────────────────────────────────
|
|
10138
11359
|
case "flows": {
|
|
10139
|
-
const
|
|
10140
|
-
const
|
|
11360
|
+
const offset = a.offset ?? 0;
|
|
11361
|
+
const effectiveLimit = Math.min(a.limit ?? 50, 500);
|
|
11362
|
+
const allFlows = [];
|
|
10141
11363
|
for (const node of graph.allNodes()) {
|
|
10142
11364
|
if (node.kind === "flow") {
|
|
10143
11365
|
const steps = node.metadata?.steps;
|
|
10144
|
-
|
|
11366
|
+
allFlows.push({
|
|
10145
11367
|
id: node.id,
|
|
10146
11368
|
name: node.name,
|
|
10147
11369
|
entryPoint: node.metadata?.entryPoint,
|
|
10148
11370
|
steps: steps ?? [],
|
|
10149
11371
|
stepCount: Array.isArray(steps) ? steps.length : 0
|
|
10150
11372
|
});
|
|
10151
|
-
if (flows.length >= limit) break;
|
|
10152
11373
|
}
|
|
10153
11374
|
}
|
|
10154
|
-
|
|
11375
|
+
const total = allFlows.length;
|
|
11376
|
+
const flows = allFlows.slice(offset, offset + effectiveLimit);
|
|
11377
|
+
const hasMore = offset + effectiveLimit < total;
|
|
11378
|
+
return {
|
|
11379
|
+
content: [{
|
|
11380
|
+
type: "text",
|
|
11381
|
+
text: JSON.stringify({ flows, total, offset, limit: effectiveLimit, hasMore }, null, 2)
|
|
11382
|
+
}]
|
|
11383
|
+
};
|
|
10155
11384
|
}
|
|
10156
11385
|
// ── detect_changes ─────────────────────────────────────────────────────
|
|
10157
11386
|
case "detect_changes": {
|
|
@@ -10179,7 +11408,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10179
11408
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
10180
11409
|
for (const node of graph.allNodes()) {
|
|
10181
11410
|
if (!node.filePath) continue;
|
|
10182
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
11411
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path33.sep, "");
|
|
10183
11412
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
10184
11413
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
10185
11414
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -10401,6 +11630,57 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10401
11630
|
}]
|
|
10402
11631
|
};
|
|
10403
11632
|
}
|
|
11633
|
+
// ── explain_relationship ───────────────────────────────────────────────
|
|
11634
|
+
case "explain_relationship": {
|
|
11635
|
+
const fromName = a.from;
|
|
11636
|
+
const toName = a.to;
|
|
11637
|
+
const result = explainRelationship(graph, fromName, toName);
|
|
11638
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11639
|
+
}
|
|
11640
|
+
// ── pr_impact ──────────────────────────────────────────────────────────
|
|
11641
|
+
case "pr_impact": {
|
|
11642
|
+
const maxHops = a.maxHops ?? 5;
|
|
11643
|
+
let changedFiles = a.changedFiles ?? [];
|
|
11644
|
+
if (a.diff && typeof a.diff === "string") {
|
|
11645
|
+
const diffFiles = parseDiffFiles(a.diff);
|
|
11646
|
+
changedFiles = [.../* @__PURE__ */ new Set([...changedFiles, ...diffFiles])];
|
|
11647
|
+
}
|
|
11648
|
+
if (changedFiles.length === 0) {
|
|
11649
|
+
return {
|
|
11650
|
+
content: [{
|
|
11651
|
+
type: "text",
|
|
11652
|
+
text: JSON.stringify({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
|
|
11653
|
+
}]
|
|
11654
|
+
};
|
|
11655
|
+
}
|
|
11656
|
+
const result = computePRImpact(graph, changedFiles, maxHops);
|
|
11657
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11658
|
+
}
|
|
11659
|
+
// ── similar_symbols ────────────────────────────────────────────────────
|
|
11660
|
+
case "similar_symbols": {
|
|
11661
|
+
const symbolName = a.symbol;
|
|
11662
|
+
const limit = a.limit ?? 10;
|
|
11663
|
+
const result = findSimilarSymbols(graph, symbolName, limit);
|
|
11664
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11665
|
+
}
|
|
11666
|
+
// ── health_report ──────────────────────────────────────────────────────
|
|
11667
|
+
case "health_report": {
|
|
11668
|
+
const scope = a.scope ?? ".";
|
|
11669
|
+
const result = computeHealthReport(graph, scope);
|
|
11670
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11671
|
+
}
|
|
11672
|
+
// ── suggest_tests ──────────────────────────────────────────────────────
|
|
11673
|
+
case "suggest_tests": {
|
|
11674
|
+
const sym = a.symbol;
|
|
11675
|
+
const result = suggestTests(graph, sym);
|
|
11676
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11677
|
+
}
|
|
11678
|
+
// ── cluster_summary ────────────────────────────────────────────────────
|
|
11679
|
+
case "cluster_summary": {
|
|
11680
|
+
const cluster = a.cluster;
|
|
11681
|
+
const result = summarizeCluster(graph, cluster);
|
|
11682
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11683
|
+
}
|
|
10404
11684
|
default:
|
|
10405
11685
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
10406
11686
|
}
|
|
@@ -10487,20 +11767,20 @@ function parseDiff(diffText) {
|
|
|
10487
11767
|
// src/cli/main.ts
|
|
10488
11768
|
init_metadata();
|
|
10489
11769
|
async function writeSkillFiles(graph, workspaceRoot, projectName) {
|
|
10490
|
-
const outputDir =
|
|
11770
|
+
const outputDir = path33.join(workspaceRoot, ".claude", "skills", "code-intel");
|
|
10491
11771
|
const areas = buildAreaMap(graph, workspaceRoot);
|
|
10492
11772
|
if (areas.length === 0) return { skills: [], outputDir };
|
|
10493
|
-
|
|
10494
|
-
|
|
11773
|
+
fs33.rmSync(outputDir, { recursive: true, force: true });
|
|
11774
|
+
fs33.mkdirSync(outputDir, { recursive: true });
|
|
10495
11775
|
const skills = [];
|
|
10496
11776
|
const usedNames = /* @__PURE__ */ new Set();
|
|
10497
11777
|
for (const area of areas) {
|
|
10498
11778
|
const kebab = uniqueKebab(area.label, usedNames);
|
|
10499
11779
|
usedNames.add(kebab);
|
|
10500
11780
|
const content = renderSkill(area, projectName, kebab);
|
|
10501
|
-
const dir =
|
|
10502
|
-
|
|
10503
|
-
|
|
11781
|
+
const dir = path33.join(outputDir, kebab);
|
|
11782
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
11783
|
+
fs33.writeFileSync(path33.join(dir, "SKILL.md"), content, "utf-8");
|
|
10504
11784
|
skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
|
|
10505
11785
|
}
|
|
10506
11786
|
return { skills, outputDir };
|
|
@@ -10680,8 +11960,8 @@ var BLOCK_START = "<!-- code-intel:start -->";
|
|
|
10680
11960
|
var BLOCK_END = "<!-- code-intel:end -->";
|
|
10681
11961
|
function writeContextFiles(workspaceRoot, projectName, stats, skills) {
|
|
10682
11962
|
const block = buildBlock(projectName, stats, skills);
|
|
10683
|
-
upsertFile(
|
|
10684
|
-
upsertFile(
|
|
11963
|
+
upsertFile(path33.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
|
|
11964
|
+
upsertFile(path33.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
|
|
10685
11965
|
}
|
|
10686
11966
|
function buildBlock(projectName, stats, skills) {
|
|
10687
11967
|
const skillRows = skills.map(
|
|
@@ -10735,7 +12015,7 @@ ${skillTable}
|
|
|
10735
12015
|
${BLOCK_END}`;
|
|
10736
12016
|
}
|
|
10737
12017
|
function upsertFile(filePath, block, fileName) {
|
|
10738
|
-
if (!
|
|
12018
|
+
if (!fs33.existsSync(filePath)) {
|
|
10739
12019
|
const newContent = [
|
|
10740
12020
|
`# ${fileName}`,
|
|
10741
12021
|
"",
|
|
@@ -10746,17 +12026,17 @@ function upsertFile(filePath, block, fileName) {
|
|
|
10746
12026
|
"<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
|
|
10747
12027
|
""
|
|
10748
12028
|
].join("\n");
|
|
10749
|
-
|
|
12029
|
+
fs33.writeFileSync(filePath, newContent, "utf-8");
|
|
10750
12030
|
return;
|
|
10751
12031
|
}
|
|
10752
|
-
const existing =
|
|
12032
|
+
const existing = fs33.readFileSync(filePath, "utf-8");
|
|
10753
12033
|
const startIdx = findLineMarker(existing, BLOCK_START);
|
|
10754
12034
|
const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
|
|
10755
12035
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
10756
12036
|
const before = existing.slice(0, startIdx);
|
|
10757
12037
|
const after = existing.slice(endIdx + BLOCK_END.length);
|
|
10758
12038
|
const updated = (before + block + after).trimEnd() + "\n";
|
|
10759
|
-
|
|
12039
|
+
fs33.writeFileSync(filePath, updated, "utf-8");
|
|
10760
12040
|
return;
|
|
10761
12041
|
}
|
|
10762
12042
|
const appended = [
|
|
@@ -10769,7 +12049,7 @@ function upsertFile(filePath, block, fileName) {
|
|
|
10769
12049
|
block,
|
|
10770
12050
|
""
|
|
10771
12051
|
].join("\n");
|
|
10772
|
-
|
|
12052
|
+
fs33.writeFileSync(filePath, appended, "utf-8");
|
|
10773
12053
|
}
|
|
10774
12054
|
function findLineMarker(content, marker, startFrom = 0) {
|
|
10775
12055
|
let idx = content.indexOf(marker, startFrom);
|
|
@@ -10811,14 +12091,14 @@ function getChangedFilesSince(workspaceRoot, baseHash) {
|
|
|
10811
12091
|
function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
|
|
10812
12092
|
const changed = [];
|
|
10813
12093
|
for (const absPath of allFilePaths) {
|
|
10814
|
-
const rel =
|
|
12094
|
+
const rel = path33.relative(workspaceRoot, absPath);
|
|
10815
12095
|
const stored = storedMtimes[rel];
|
|
10816
12096
|
if (stored === void 0) {
|
|
10817
12097
|
changed.push(absPath);
|
|
10818
12098
|
continue;
|
|
10819
12099
|
}
|
|
10820
12100
|
try {
|
|
10821
|
-
const { mtimeMs } =
|
|
12101
|
+
const { mtimeMs } = fs33.statSync(absPath);
|
|
10822
12102
|
if (mtimeMs > stored) changed.push(absPath);
|
|
10823
12103
|
} catch {
|
|
10824
12104
|
changed.push(absPath);
|
|
@@ -10830,8 +12110,8 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
|
|
|
10830
12110
|
const snap = {};
|
|
10831
12111
|
for (const absPath of filePaths) {
|
|
10832
12112
|
try {
|
|
10833
|
-
const { mtimeMs } =
|
|
10834
|
-
snap[
|
|
12113
|
+
const { mtimeMs } = fs33.statSync(absPath);
|
|
12114
|
+
snap[path33.relative(workspaceRoot, absPath)] = mtimeMs;
|
|
10835
12115
|
} catch {
|
|
10836
12116
|
}
|
|
10837
12117
|
}
|
|
@@ -10842,8 +12122,8 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
|
|
|
10842
12122
|
if (prevCommitHash) {
|
|
10843
12123
|
const changed = getChangedFilesSince(workspaceRoot, prevCommitHash);
|
|
10844
12124
|
if (changed !== null) {
|
|
10845
|
-
const scanSet = new Set(allFilePaths.map((p) =>
|
|
10846
|
-
const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) =>
|
|
12125
|
+
const scanSet = new Set(allFilePaths.map((p) => path33.relative(workspaceRoot, p)));
|
|
12126
|
+
const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path33.join(workspaceRoot, rel));
|
|
10847
12127
|
if (total > 0 && changedInScan.length / total > 0.2) {
|
|
10848
12128
|
return { incremental: false, fallbackReason: `changed files (${changedInScan.length}) > 20% of total (${total})` };
|
|
10849
12129
|
}
|
|
@@ -10864,7 +12144,135 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
|
|
|
10864
12144
|
}
|
|
10865
12145
|
|
|
10866
12146
|
// src/cli/main.ts
|
|
12147
|
+
init_graph_from_db();
|
|
10867
12148
|
init_group_registry();
|
|
12149
|
+
init_group_sync();
|
|
12150
|
+
function expandGlob(root, pattern) {
|
|
12151
|
+
const parts = pattern.replace(/\/\*\*?$/, "").split("/").filter(Boolean);
|
|
12152
|
+
if (parts.length === 0) return [];
|
|
12153
|
+
const dir = path33.join(root, ...parts);
|
|
12154
|
+
if (!fs33.existsSync(dir)) return [];
|
|
12155
|
+
return fs33.readdirSync(dir).map((entry) => path33.join(dir, entry)).filter((p) => {
|
|
12156
|
+
try {
|
|
12157
|
+
return fs33.statSync(p).isDirectory();
|
|
12158
|
+
} catch {
|
|
12159
|
+
return false;
|
|
12160
|
+
}
|
|
12161
|
+
});
|
|
12162
|
+
}
|
|
12163
|
+
function resolvePackages(root, patterns) {
|
|
12164
|
+
const packages = [];
|
|
12165
|
+
for (const pattern of patterns) {
|
|
12166
|
+
const dirs = expandGlob(root, pattern);
|
|
12167
|
+
for (const dir of dirs) {
|
|
12168
|
+
const pkgJsonPath = path33.join(dir, "package.json");
|
|
12169
|
+
if (!fs33.existsSync(pkgJsonPath)) continue;
|
|
12170
|
+
try {
|
|
12171
|
+
const pkgJson = JSON.parse(fs33.readFileSync(pkgJsonPath, "utf-8"));
|
|
12172
|
+
const name = pkgJson.name ?? path33.basename(dir);
|
|
12173
|
+
packages.push({ name, path: dir });
|
|
12174
|
+
} catch {
|
|
12175
|
+
}
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
return packages;
|
|
12179
|
+
}
|
|
12180
|
+
async function detectWorkspace(root) {
|
|
12181
|
+
const turboJsonPath = path33.join(root, "turbo.json");
|
|
12182
|
+
if (fs33.existsSync(turboJsonPath)) {
|
|
12183
|
+
let patterns = [];
|
|
12184
|
+
const pkgJsonPath = path33.join(root, "package.json");
|
|
12185
|
+
if (fs33.existsSync(pkgJsonPath)) {
|
|
12186
|
+
try {
|
|
12187
|
+
const pkgJson = JSON.parse(fs33.readFileSync(pkgJsonPath, "utf-8"));
|
|
12188
|
+
if (pkgJson.workspaces) {
|
|
12189
|
+
patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
|
|
12190
|
+
}
|
|
12191
|
+
} catch {
|
|
12192
|
+
}
|
|
12193
|
+
}
|
|
12194
|
+
if (patterns.length === 0) {
|
|
12195
|
+
const fallbackDir = path33.join(root, "packages");
|
|
12196
|
+
if (fs33.existsSync(fallbackDir)) patterns = ["packages/*"];
|
|
12197
|
+
}
|
|
12198
|
+
const packages = resolvePackages(root, patterns);
|
|
12199
|
+
return { type: "turborepo", root, packages };
|
|
12200
|
+
}
|
|
12201
|
+
const npmPkgJsonPath = path33.join(root, "package.json");
|
|
12202
|
+
if (fs33.existsSync(npmPkgJsonPath)) {
|
|
12203
|
+
try {
|
|
12204
|
+
const pkgJson = JSON.parse(fs33.readFileSync(npmPkgJsonPath, "utf-8"));
|
|
12205
|
+
if (pkgJson.workspaces) {
|
|
12206
|
+
const patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
|
|
12207
|
+
const packages = resolvePackages(root, patterns);
|
|
12208
|
+
return { type: "npm", root, packages };
|
|
12209
|
+
}
|
|
12210
|
+
} catch {
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
const pnpmYamlPath = path33.join(root, "pnpm-workspace.yaml");
|
|
12214
|
+
if (fs33.existsSync(pnpmYamlPath)) {
|
|
12215
|
+
const patterns = [];
|
|
12216
|
+
try {
|
|
12217
|
+
const content = fs33.readFileSync(pnpmYamlPath, "utf-8");
|
|
12218
|
+
let inPackages = false;
|
|
12219
|
+
for (const line of content.split("\n")) {
|
|
12220
|
+
if (/^packages\s*:/.test(line)) {
|
|
12221
|
+
inPackages = true;
|
|
12222
|
+
continue;
|
|
12223
|
+
}
|
|
12224
|
+
if (inPackages) {
|
|
12225
|
+
if (/^\s*-\s+/.test(line)) {
|
|
12226
|
+
patterns.push(line.replace(/^\s*-\s+/, "").replace(/['"]/g, "").trim());
|
|
12227
|
+
} else if (line.trim() && !/^\s/.test(line)) {
|
|
12228
|
+
inPackages = false;
|
|
12229
|
+
}
|
|
12230
|
+
}
|
|
12231
|
+
}
|
|
12232
|
+
} catch {
|
|
12233
|
+
}
|
|
12234
|
+
const packages = resolvePackages(root, patterns);
|
|
12235
|
+
return { type: "pnpm", root, packages };
|
|
12236
|
+
}
|
|
12237
|
+
const nxJsonPath = path33.join(root, "nx.json");
|
|
12238
|
+
if (fs33.existsSync(nxJsonPath)) {
|
|
12239
|
+
const packages = [];
|
|
12240
|
+
const scanForProjects = (dir, depth) => {
|
|
12241
|
+
if (depth > 2) return;
|
|
12242
|
+
let entries;
|
|
12243
|
+
try {
|
|
12244
|
+
entries = fs33.readdirSync(dir);
|
|
12245
|
+
} catch {
|
|
12246
|
+
return;
|
|
12247
|
+
}
|
|
12248
|
+
for (const entry of entries) {
|
|
12249
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
12250
|
+
const fullPath = path33.join(dir, entry);
|
|
12251
|
+
try {
|
|
12252
|
+
if (!fs33.statSync(fullPath).isDirectory()) continue;
|
|
12253
|
+
} catch {
|
|
12254
|
+
continue;
|
|
12255
|
+
}
|
|
12256
|
+
const projectJsonPath = path33.join(fullPath, "project.json");
|
|
12257
|
+
if (fs33.existsSync(projectJsonPath)) {
|
|
12258
|
+
try {
|
|
12259
|
+
const proj = JSON.parse(fs33.readFileSync(projectJsonPath, "utf-8"));
|
|
12260
|
+
const name = proj.name ?? path33.basename(fullPath);
|
|
12261
|
+
packages.push({ name, path: fullPath });
|
|
12262
|
+
} catch {
|
|
12263
|
+
}
|
|
12264
|
+
} else {
|
|
12265
|
+
scanForProjects(fullPath, depth + 1);
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
};
|
|
12269
|
+
scanForProjects(root, 1);
|
|
12270
|
+
return { type: "nx", root, packages };
|
|
12271
|
+
}
|
|
12272
|
+
return null;
|
|
12273
|
+
}
|
|
12274
|
+
|
|
12275
|
+
// src/cli/main.ts
|
|
10868
12276
|
init_users_db();
|
|
10869
12277
|
var migrations = [
|
|
10870
12278
|
{
|
|
@@ -10957,17 +12365,17 @@ var MigrationRunner = class {
|
|
|
10957
12365
|
autoBackupBeforeMigration() {
|
|
10958
12366
|
try {
|
|
10959
12367
|
const dbFile = this.db.name;
|
|
10960
|
-
if (!dbFile || !
|
|
10961
|
-
const backupDir =
|
|
10962
|
-
|
|
12368
|
+
if (!dbFile || !fs33.existsSync(dbFile)) return;
|
|
12369
|
+
const backupDir = path33.join(os12.homedir(), ".code-intel", "backups", "pre-migration");
|
|
12370
|
+
fs33.mkdirSync(backupDir, { recursive: true });
|
|
10963
12371
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
10964
|
-
const baseName =
|
|
10965
|
-
const backupPath =
|
|
12372
|
+
const baseName = path33.basename(dbFile, ".db");
|
|
12373
|
+
const backupPath = path33.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
|
|
10966
12374
|
try {
|
|
10967
12375
|
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
10968
12376
|
} catch {
|
|
10969
12377
|
}
|
|
10970
|
-
|
|
12378
|
+
fs33.copyFileSync(dbFile, backupPath);
|
|
10971
12379
|
} catch {
|
|
10972
12380
|
}
|
|
10973
12381
|
}
|
|
@@ -11183,7 +12591,7 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
|
|
|
11183
12591
|
Docs: https://github.com/vohongtho/code-intel-platform
|
|
11184
12592
|
`);
|
|
11185
12593
|
async function analyzeWorkspace(targetPath, options) {
|
|
11186
|
-
const workspaceRoot =
|
|
12594
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
11187
12595
|
if (!options?.silent) console.log(`Analyzing: ${workspaceRoot}`);
|
|
11188
12596
|
logger_default.info(`analyze started: ${workspaceRoot}`);
|
|
11189
12597
|
if (options?.force) {
|
|
@@ -11204,14 +12612,14 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11204
12612
|
];
|
|
11205
12613
|
for (const f of wipeFiles) {
|
|
11206
12614
|
try {
|
|
11207
|
-
if (
|
|
12615
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
11208
12616
|
} catch {
|
|
11209
12617
|
}
|
|
11210
12618
|
}
|
|
11211
12619
|
}
|
|
11212
12620
|
if (!options?.skipGit) {
|
|
11213
|
-
const gitDir =
|
|
11214
|
-
if (!
|
|
12621
|
+
const gitDir = path33.join(workspaceRoot, ".git");
|
|
12622
|
+
if (!fs33.existsSync(gitDir)) {
|
|
11215
12623
|
logger_default.warn(`${workspaceRoot} is not a Git repository`);
|
|
11216
12624
|
}
|
|
11217
12625
|
}
|
|
@@ -11322,17 +12730,17 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11322
12730
|
const result = await runPipeline(phases, context2);
|
|
11323
12731
|
if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
|
|
11324
12732
|
const dbPath = getDbPath(workspaceRoot);
|
|
11325
|
-
if (
|
|
12733
|
+
if (fs33.existsSync(dbPath)) {
|
|
11326
12734
|
try {
|
|
11327
12735
|
const db = new DbManager(dbPath);
|
|
11328
12736
|
await db.init();
|
|
11329
12737
|
for (const absPath of incrementalChangedFiles) {
|
|
11330
|
-
const rel =
|
|
12738
|
+
const rel = path33.relative(workspaceRoot, absPath);
|
|
11331
12739
|
await removeNodesForFile(rel, db);
|
|
11332
12740
|
}
|
|
11333
12741
|
const { upsertNodes: upsertNodesBatch } = await Promise.resolve().then(() => (init_graph_loader(), graph_loader_exports));
|
|
11334
12742
|
const changedRelPaths = new Set(
|
|
11335
|
-
incrementalChangedFiles.map((f) =>
|
|
12743
|
+
incrementalChangedFiles.map((f) => path33.relative(workspaceRoot, f))
|
|
11336
12744
|
);
|
|
11337
12745
|
const nodesToUpsert = [...graph.allNodes()].filter(
|
|
11338
12746
|
(n) => changedRelPaths.has(n.filePath)
|
|
@@ -11357,7 +12765,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11357
12765
|
mergedMtimes = newMtimes;
|
|
11358
12766
|
}
|
|
11359
12767
|
const currentCommitHash = getCurrentCommitHash(workspaceRoot) ?? void 0;
|
|
11360
|
-
const repoName =
|
|
12768
|
+
const repoName = path33.basename(workspaceRoot);
|
|
11361
12769
|
const indexVersion = v4();
|
|
11362
12770
|
saveMetadata(workspaceRoot, {
|
|
11363
12771
|
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11395,7 +12803,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11395
12803
|
];
|
|
11396
12804
|
for (const f of newStaleFiles) {
|
|
11397
12805
|
try {
|
|
11398
|
-
if (
|
|
12806
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
11399
12807
|
} catch {
|
|
11400
12808
|
}
|
|
11401
12809
|
}
|
|
@@ -11412,21 +12820,21 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11412
12820
|
];
|
|
11413
12821
|
for (const f of staleFiles) {
|
|
11414
12822
|
try {
|
|
11415
|
-
if (
|
|
12823
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
11416
12824
|
} catch {
|
|
11417
12825
|
}
|
|
11418
12826
|
}
|
|
11419
12827
|
for (const f of newStaleFiles) {
|
|
11420
12828
|
if (f === dbPathNew) continue;
|
|
11421
|
-
if (
|
|
12829
|
+
if (fs33.existsSync(f)) {
|
|
11422
12830
|
const dest = f.replace(dbPathNew, dbPath);
|
|
11423
12831
|
try {
|
|
11424
|
-
|
|
12832
|
+
fs33.renameSync(f, dest);
|
|
11425
12833
|
} catch {
|
|
11426
12834
|
}
|
|
11427
12835
|
}
|
|
11428
12836
|
}
|
|
11429
|
-
|
|
12837
|
+
fs33.renameSync(dbPathNew, dbPath);
|
|
11430
12838
|
stopSpinner();
|
|
11431
12839
|
logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
|
|
11432
12840
|
if (!options?.silent) {
|
|
@@ -11447,7 +12855,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11447
12855
|
const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
|
|
11448
12856
|
for (const f of staleVdb) {
|
|
11449
12857
|
try {
|
|
11450
|
-
if (
|
|
12858
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
11451
12859
|
} catch {
|
|
11452
12860
|
}
|
|
11453
12861
|
}
|
|
@@ -11517,6 +12925,32 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
11517
12925
|
\u2705 Done in ${durStr} \u2014 ${graph.size.nodes} nodes \xB7 ${graph.size.edges} edges \xB7 ${context2.filePaths.length} files`);
|
|
11518
12926
|
}
|
|
11519
12927
|
logger_default.info(`analyze complete: ${graph.size.nodes} nodes, ${graph.size.edges} edges, ${context2.filePaths.length} files, ${result.totalDuration}ms`);
|
|
12928
|
+
if (!options?.noGroupSync) {
|
|
12929
|
+
const registry = loadRegistry();
|
|
12930
|
+
const repoEntry = registry.find((r) => r.path === workspaceRoot);
|
|
12931
|
+
if (repoEntry) {
|
|
12932
|
+
const allGroups = listGroups();
|
|
12933
|
+
for (const g of allGroups) {
|
|
12934
|
+
const group = loadGroup(g.name);
|
|
12935
|
+
if (!group) continue;
|
|
12936
|
+
const isMember = group.members.some((m) => m.registryName === repoEntry.name);
|
|
12937
|
+
if (!isMember) continue;
|
|
12938
|
+
if (!options?.silent) console.log(` \u2839 Syncing group '${g.name}'\u2026`);
|
|
12939
|
+
try {
|
|
12940
|
+
const { syncGroup: doSyncGroup } = await Promise.resolve().then(() => (init_group_sync(), group_sync_exports));
|
|
12941
|
+
const { saveSyncResult: doSaveSyncResult } = await Promise.resolve().then(() => (init_group_registry(), group_registry_exports));
|
|
12942
|
+
const syncResult = await doSyncGroup(group);
|
|
12943
|
+
doSaveSyncResult(syncResult);
|
|
12944
|
+
group.lastSync = syncResult.syncedAt;
|
|
12945
|
+
const { saveGroup: doSaveGroup } = await Promise.resolve().then(() => (init_group_registry(), group_registry_exports));
|
|
12946
|
+
doSaveGroup(group);
|
|
12947
|
+
if (!options?.silent) console.log(` \u2705 Group '${g.name}' synced`);
|
|
12948
|
+
} catch (err) {
|
|
12949
|
+
if (!options?.silent) console.warn(` \u26A0 Group sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12950
|
+
}
|
|
12951
|
+
}
|
|
12952
|
+
}
|
|
12953
|
+
}
|
|
11520
12954
|
return { graph, result, repoName, workspaceRoot };
|
|
11521
12955
|
}
|
|
11522
12956
|
program.command("setup").description("Configure MCP server for your editors (one-time setup)").addHelpText("after", `
|
|
@@ -11545,8 +12979,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
11545
12979
|
const configFile = `${configDir}/claude_desktop_config.json`;
|
|
11546
12980
|
try {
|
|
11547
12981
|
let existing = {};
|
|
11548
|
-
if (
|
|
11549
|
-
existing = JSON.parse(
|
|
12982
|
+
if (fs33.existsSync(configFile)) {
|
|
12983
|
+
existing = JSON.parse(fs33.readFileSync(configFile, "utf-8"));
|
|
11550
12984
|
}
|
|
11551
12985
|
const merged = {
|
|
11552
12986
|
...existing,
|
|
@@ -11555,8 +12989,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
11555
12989
|
...mcpConfig.mcpServers
|
|
11556
12990
|
}
|
|
11557
12991
|
};
|
|
11558
|
-
|
|
11559
|
-
|
|
12992
|
+
fs33.mkdirSync(configDir, { recursive: true });
|
|
12993
|
+
fs33.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
11560
12994
|
console.log(`
|
|
11561
12995
|
\u2705 Written to ${configFile}`);
|
|
11562
12996
|
} catch (err) {
|
|
@@ -11570,7 +13004,7 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
11570
13004
|
console.log('\n To verify in VS Code: open Command Palette \u2192 "MCP: List Servers" and confirm code-intel is Running.');
|
|
11571
13005
|
console.log("\n Next: run `code-intel analyze` inside your project to build the knowledge graph.\n");
|
|
11572
13006
|
});
|
|
11573
|
-
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)").addHelpText("after", `
|
|
13007
|
+
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").addHelpText("after", `
|
|
11574
13008
|
Parses your source code with tree-sitter, builds a Knowledge Graph of
|
|
11575
13009
|
symbols and their relationships, persists it to .code-intel/graph.db,
|
|
11576
13010
|
and auto-generates AGENTS.md + CLAUDE.md context blocks.
|
|
@@ -11626,10 +13060,10 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
|
|
|
11626
13060
|
$ code-intel mcp
|
|
11627
13061
|
$ code-intel mcp ./my-project
|
|
11628
13062
|
`).action(async (targetPath) => {
|
|
11629
|
-
const workspaceRoot =
|
|
11630
|
-
const repoName =
|
|
13063
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13064
|
+
const repoName = path33.basename(workspaceRoot);
|
|
11631
13065
|
const dbPath = getDbPath(workspaceRoot);
|
|
11632
|
-
const existingIndex =
|
|
13066
|
+
const existingIndex = fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
11633
13067
|
if (existingIndex) {
|
|
11634
13068
|
const graph = createKnowledgeGraph();
|
|
11635
13069
|
const db = new DbManager(dbPath);
|
|
@@ -11660,10 +13094,10 @@ program.command("serve").description("Start the local HTTP server + web UI for g
|
|
|
11660
13094
|
$ code-intel serve --port 8080
|
|
11661
13095
|
$ code-intel serve --force
|
|
11662
13096
|
`).action(async (targetPath, options) => {
|
|
11663
|
-
const workspaceRoot =
|
|
11664
|
-
const repoName =
|
|
13097
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13098
|
+
const repoName = path33.basename(workspaceRoot);
|
|
11665
13099
|
const dbPath = getDbPath(workspaceRoot);
|
|
11666
|
-
const existingIndex = !options.force &&
|
|
13100
|
+
const existingIndex = !options.force && fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
11667
13101
|
if (existingIndex) {
|
|
11668
13102
|
const meta = loadMetadata(workspaceRoot);
|
|
11669
13103
|
if (meta.parser === "regex" || meta.parser === void 0) {
|
|
@@ -11697,10 +13131,10 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
11697
13131
|
`).action(async (targetPath, options) => {
|
|
11698
13132
|
const { FileWatcher: FileWatcher2 } = await Promise.resolve().then(() => (init_file_watcher(), file_watcher_exports));
|
|
11699
13133
|
const { IncrementalIndexer: IncrementalIndexer2 } = await Promise.resolve().then(() => (init_incremental_indexer(), incremental_indexer_exports));
|
|
11700
|
-
const workspaceRoot =
|
|
11701
|
-
const repoName =
|
|
13134
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13135
|
+
const repoName = path33.basename(workspaceRoot);
|
|
11702
13136
|
const dbPath = getDbPath(workspaceRoot);
|
|
11703
|
-
const existingIndex = !options.force &&
|
|
13137
|
+
const existingIndex = !options.force && fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
11704
13138
|
let graph;
|
|
11705
13139
|
if (existingIndex) {
|
|
11706
13140
|
const meta = loadMetadata(workspaceRoot);
|
|
@@ -11735,7 +13169,7 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
11735
13169
|
type: "graph:updated",
|
|
11736
13170
|
indexVersion: meta?.indexVersion ?? "unknown",
|
|
11737
13171
|
stats: { nodes: graph.size.nodes, edges: graph.size.edges },
|
|
11738
|
-
changedFiles: changedFiles.map((f) =>
|
|
13172
|
+
changedFiles: changedFiles.map((f) => path33.relative(workspaceRoot, f)),
|
|
11739
13173
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11740
13174
|
});
|
|
11741
13175
|
}
|
|
@@ -11786,7 +13220,7 @@ program.command("status").description("Show index freshness and statistics for a
|
|
|
11786
13220
|
$ code-intel status
|
|
11787
13221
|
$ code-intel status ./my-project
|
|
11788
13222
|
`).action((targetPath) => {
|
|
11789
|
-
const workspaceRoot =
|
|
13223
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
11790
13224
|
const meta = loadMetadata(workspaceRoot);
|
|
11791
13225
|
if (!meta) {
|
|
11792
13226
|
console.log(`
|
|
@@ -11810,18 +13244,18 @@ function trashDirName(repoPath) {
|
|
|
11810
13244
|
return `.code-intel-trash-${date}`;
|
|
11811
13245
|
}
|
|
11812
13246
|
function softDeleteCodeIntel(repoPath) {
|
|
11813
|
-
const codeIntelDir =
|
|
11814
|
-
if (!
|
|
13247
|
+
const codeIntelDir = path33.join(repoPath, ".code-intel");
|
|
13248
|
+
if (!fs33.existsSync(codeIntelDir)) return;
|
|
11815
13249
|
const trashName = trashDirName();
|
|
11816
|
-
const trashDir =
|
|
13250
|
+
const trashDir = path33.join(repoPath, trashName);
|
|
11817
13251
|
let dest = trashDir;
|
|
11818
13252
|
let counter = 1;
|
|
11819
|
-
while (
|
|
13253
|
+
while (fs33.existsSync(dest)) {
|
|
11820
13254
|
dest = `${trashDir}-${counter++}`;
|
|
11821
13255
|
}
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
13256
|
+
fs33.renameSync(codeIntelDir, dest);
|
|
13257
|
+
fs33.writeFileSync(
|
|
13258
|
+
path33.join(dest, "TRASH_META.json"),
|
|
11825
13259
|
JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
|
|
11826
13260
|
);
|
|
11827
13261
|
console.log(` \u2713 Moved to trash: ${dest}`);
|
|
@@ -11830,15 +13264,15 @@ function softDeleteCodeIntel(repoPath) {
|
|
|
11830
13264
|
function purgeStaleTrashes(repoPath) {
|
|
11831
13265
|
const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
|
|
11832
13266
|
try {
|
|
11833
|
-
for (const entry of
|
|
13267
|
+
for (const entry of fs33.readdirSync(repoPath)) {
|
|
11834
13268
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
11835
|
-
const fullPath =
|
|
11836
|
-
const metaPath =
|
|
11837
|
-
if (
|
|
13269
|
+
const fullPath = path33.join(repoPath, entry);
|
|
13270
|
+
const metaPath = path33.join(fullPath, "TRASH_META.json");
|
|
13271
|
+
if (fs33.existsSync(metaPath)) {
|
|
11838
13272
|
try {
|
|
11839
|
-
const meta = JSON.parse(
|
|
13273
|
+
const meta = JSON.parse(fs33.readFileSync(metaPath, "utf-8"));
|
|
11840
13274
|
if (new Date(meta.deletedAt).getTime() < cutoff) {
|
|
11841
|
-
|
|
13275
|
+
fs33.rmSync(fullPath, { recursive: true, force: true });
|
|
11842
13276
|
console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
|
|
11843
13277
|
}
|
|
11844
13278
|
} catch {
|
|
@@ -11865,18 +13299,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
11865
13299
|
if (opts.listTrash) {
|
|
11866
13300
|
const repos = loadRegistry();
|
|
11867
13301
|
const roots = repos.map((r) => r.path);
|
|
11868
|
-
if (roots.length === 0) roots.push(
|
|
13302
|
+
if (roots.length === 0) roots.push(path33.resolve("."));
|
|
11869
13303
|
let found = 0;
|
|
11870
13304
|
for (const root of roots) {
|
|
11871
13305
|
try {
|
|
11872
|
-
for (const entry of
|
|
13306
|
+
for (const entry of fs33.readdirSync(root)) {
|
|
11873
13307
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
11874
|
-
const fullPath =
|
|
11875
|
-
const metaPath =
|
|
13308
|
+
const fullPath = path33.join(root, entry);
|
|
13309
|
+
const metaPath = path33.join(fullPath, "TRASH_META.json");
|
|
11876
13310
|
let deletedAt = "unknown";
|
|
11877
|
-
if (
|
|
13311
|
+
if (fs33.existsSync(metaPath)) {
|
|
11878
13312
|
try {
|
|
11879
|
-
deletedAt = JSON.parse(
|
|
13313
|
+
deletedAt = JSON.parse(fs33.readFileSync(metaPath, "utf-8")).deletedAt;
|
|
11880
13314
|
} catch {
|
|
11881
13315
|
}
|
|
11882
13316
|
}
|
|
@@ -11904,9 +13338,9 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
11904
13338
|
}
|
|
11905
13339
|
for (const r of repos) {
|
|
11906
13340
|
if (opts.purge) {
|
|
11907
|
-
const codeIntelDir =
|
|
11908
|
-
if (
|
|
11909
|
-
|
|
13341
|
+
const codeIntelDir = path33.join(r.path, ".code-intel");
|
|
13342
|
+
if (fs33.existsSync(codeIntelDir)) {
|
|
13343
|
+
fs33.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
11910
13344
|
console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
|
|
11911
13345
|
}
|
|
11912
13346
|
} else {
|
|
@@ -11920,11 +13354,11 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
11920
13354
|
`);
|
|
11921
13355
|
return;
|
|
11922
13356
|
}
|
|
11923
|
-
const workspaceRoot =
|
|
13357
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
11924
13358
|
if (opts.purge) {
|
|
11925
|
-
const codeIntelDir =
|
|
11926
|
-
if (
|
|
11927
|
-
|
|
13359
|
+
const codeIntelDir = path33.join(workspaceRoot, ".code-intel");
|
|
13360
|
+
if (fs33.existsSync(codeIntelDir)) {
|
|
13361
|
+
fs33.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
11928
13362
|
console.log(`
|
|
11929
13363
|
\u2713 Hard-deleted ${codeIntelDir}`);
|
|
11930
13364
|
}
|
|
@@ -11936,16 +13370,16 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
11936
13370
|
console.log(" Index cleaned.\n");
|
|
11937
13371
|
});
|
|
11938
13372
|
async function loadOrAnalyzeWorkspace(targetPath) {
|
|
11939
|
-
const workspaceRoot =
|
|
13373
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
11940
13374
|
const dbPath = getDbPath(workspaceRoot);
|
|
11941
|
-
const existingIndex =
|
|
13375
|
+
const existingIndex = fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
11942
13376
|
if (existingIndex) {
|
|
11943
13377
|
const graph = createKnowledgeGraph();
|
|
11944
13378
|
const db = new DbManager(dbPath);
|
|
11945
13379
|
await db.init();
|
|
11946
13380
|
await loadGraphFromDB(graph, db);
|
|
11947
13381
|
db.close();
|
|
11948
|
-
return { graph, workspaceRoot, repoName:
|
|
13382
|
+
return { graph, workspaceRoot, repoName: path33.basename(workspaceRoot) };
|
|
11949
13383
|
}
|
|
11950
13384
|
return analyzeWorkspace(targetPath, { silent: true });
|
|
11951
13385
|
}
|
|
@@ -12373,9 +13807,9 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
12373
13807
|
console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT IN REGISTRY`);
|
|
12374
13808
|
continue;
|
|
12375
13809
|
}
|
|
12376
|
-
const metaPath =
|
|
13810
|
+
const metaPath = path33.join(regEntry.path, ".code-intel", "meta.json");
|
|
12377
13811
|
try {
|
|
12378
|
-
const meta = JSON.parse(
|
|
13812
|
+
const meta = JSON.parse(fs33.readFileSync(metaPath, "utf-8"));
|
|
12379
13813
|
const indexedAt = meta.indexedAt;
|
|
12380
13814
|
const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
|
|
12381
13815
|
const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
|
|
@@ -12391,6 +13825,94 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
12391
13825
|
}
|
|
12392
13826
|
}
|
|
12393
13827
|
});
|
|
13828
|
+
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) => {
|
|
13829
|
+
const root = path33.resolve(targetPath ?? ".");
|
|
13830
|
+
const ws = await detectWorkspace(root);
|
|
13831
|
+
if (!ws) {
|
|
13832
|
+
console.error(`
|
|
13833
|
+
\u2717 No workspace config found in: ${root}`);
|
|
13834
|
+
console.error(` Expected: package.json with workspaces, pnpm-workspace.yaml, nx.json, or turbo.json
|
|
13835
|
+
`);
|
|
13836
|
+
process.exit(1);
|
|
13837
|
+
}
|
|
13838
|
+
const groupName = opts.name ?? path33.basename(root);
|
|
13839
|
+
console.log(`
|
|
13840
|
+
\u25C8 Workspace detected: ${ws.type}`);
|
|
13841
|
+
console.log(` Group name : ${groupName}`);
|
|
13842
|
+
console.log(` Packages (${ws.packages.length}):
|
|
13843
|
+
`);
|
|
13844
|
+
for (const pkg of ws.packages) {
|
|
13845
|
+
console.log(` ${pkg.name.padEnd(35)} ${pkg.path}`);
|
|
13846
|
+
}
|
|
13847
|
+
if (!opts.yes) {
|
|
13848
|
+
const answer = await new Promise((resolve) => {
|
|
13849
|
+
process.stdout.write("\n Proceed? [y/N] ");
|
|
13850
|
+
process.stdin.once("data", (data) => resolve(data.toString().trim().toLowerCase()));
|
|
13851
|
+
});
|
|
13852
|
+
if (answer !== "y" && answer !== "yes") {
|
|
13853
|
+
console.log("\n Aborted.\n");
|
|
13854
|
+
process.exit(0);
|
|
13855
|
+
}
|
|
13856
|
+
}
|
|
13857
|
+
if (groupExists(groupName)) {
|
|
13858
|
+
console.log(`
|
|
13859
|
+
\u2139 Group "${groupName}" already exists \u2014 will update.`);
|
|
13860
|
+
} else {
|
|
13861
|
+
saveGroup({ name: groupName, createdAt: (/* @__PURE__ */ new Date()).toISOString(), members: [] });
|
|
13862
|
+
console.log(`
|
|
13863
|
+
\u2705 Group "${groupName}" created.`);
|
|
13864
|
+
}
|
|
13865
|
+
const parallel = Math.max(1, parseInt(opts.parallel, 10));
|
|
13866
|
+
const results = [];
|
|
13867
|
+
if (opts.analyze !== false) {
|
|
13868
|
+
console.log(`
|
|
13869
|
+
Analyzing ${ws.packages.length} packages (parallel: ${parallel})\u2026
|
|
13870
|
+
`);
|
|
13871
|
+
for (let i = 0; i < ws.packages.length; i += parallel) {
|
|
13872
|
+
const chunk = ws.packages.slice(i, i + parallel);
|
|
13873
|
+
await Promise.all(chunk.map(async (pkg, j) => {
|
|
13874
|
+
const idx = i + j + 1;
|
|
13875
|
+
console.log(` [${idx}/${ws.packages.length}] Analyzing ${pkg.name}\u2026`);
|
|
13876
|
+
try {
|
|
13877
|
+
await analyzeWorkspace(pkg.path, { silent: true });
|
|
13878
|
+
results.push({ name: pkg.name, status: "\u2705" });
|
|
13879
|
+
} catch (err) {
|
|
13880
|
+
results.push({ name: pkg.name, status: `\u2717 ${err instanceof Error ? err.message : String(err)}` });
|
|
13881
|
+
}
|
|
13882
|
+
}));
|
|
13883
|
+
}
|
|
13884
|
+
} else {
|
|
13885
|
+
for (const pkg of ws.packages) {
|
|
13886
|
+
results.push({ name: pkg.name, status: "--no-analyze" });
|
|
13887
|
+
}
|
|
13888
|
+
}
|
|
13889
|
+
for (const pkg of ws.packages) {
|
|
13890
|
+
try {
|
|
13891
|
+
upsertRepo({ name: pkg.name, path: pkg.path, indexedAt: (/* @__PURE__ */ new Date()).toISOString(), stats: { nodes: 0, edges: 0, files: 0 } });
|
|
13892
|
+
addMember(groupName, { groupPath: pkg.name, registryName: pkg.name });
|
|
13893
|
+
} catch {
|
|
13894
|
+
}
|
|
13895
|
+
}
|
|
13896
|
+
console.log(`
|
|
13897
|
+
\u27F3 Syncing group "${groupName}"\u2026`);
|
|
13898
|
+
try {
|
|
13899
|
+
const group = loadGroup(groupName);
|
|
13900
|
+
const syncResult = await syncGroup(group);
|
|
13901
|
+
saveSyncResult(syncResult);
|
|
13902
|
+
group.lastSync = syncResult.syncedAt;
|
|
13903
|
+
saveGroup(group);
|
|
13904
|
+
console.log(` \u2705 Sync complete \u2014 ${syncResult.contracts.length} contracts, ${syncResult.links.length} cross-links`);
|
|
13905
|
+
} catch (err) {
|
|
13906
|
+
console.warn(` \u26A0 Sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
13907
|
+
}
|
|
13908
|
+
console.log(`
|
|
13909
|
+
Summary:
|
|
13910
|
+
`);
|
|
13911
|
+
for (const r of results) {
|
|
13912
|
+
console.log(` ${r.status.padEnd(4)} ${r.name}`);
|
|
13913
|
+
}
|
|
13914
|
+
console.log("");
|
|
13915
|
+
});
|
|
12394
13916
|
var userCmd = program.command("user").description("Manage local user accounts");
|
|
12395
13917
|
userCmd.command("create <username>").description("Create a new local user account").requiredOption("--role <role>", "Role: admin | analyst | viewer | repo-owner").option("--password <password>", "Password (prompted if omitted)").addHelpText("after", `
|
|
12396
13918
|
Examples:
|
|
@@ -12592,7 +14114,7 @@ backupCmd.command("create [path]").description("Create an encrypted backup of th
|
|
|
12592
14114
|
$ code-intel backup create
|
|
12593
14115
|
$ code-intel backup create ./my-project
|
|
12594
14116
|
`).action((targetPath = ".") => {
|
|
12595
|
-
const repoPath =
|
|
14117
|
+
const repoPath = path33.resolve(targetPath);
|
|
12596
14118
|
const svc = new BackupService();
|
|
12597
14119
|
try {
|
|
12598
14120
|
const entry = svc.createBackup(repoPath);
|
|
@@ -12621,7 +14143,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
|
|
|
12621
14143
|
Backups (${entries.length}):
|
|
12622
14144
|
`);
|
|
12623
14145
|
for (const e of entries) {
|
|
12624
|
-
const exists =
|
|
14146
|
+
const exists = fs33.existsSync(e.path);
|
|
12625
14147
|
const status = exists ? "\u2713" : "\u2717 (missing)";
|
|
12626
14148
|
console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
|
|
12627
14149
|
}
|
|
@@ -12634,7 +14156,7 @@ backupCmd.command("restore <id>").description("Restore a backup by ID").option("
|
|
|
12634
14156
|
`).action((id, opts) => {
|
|
12635
14157
|
const svc = new BackupService();
|
|
12636
14158
|
try {
|
|
12637
|
-
const targetPath = opts.target ?
|
|
14159
|
+
const targetPath = opts.target ? path33.resolve(opts.target) : void 0;
|
|
12638
14160
|
svc.restoreBackup(id, targetPath);
|
|
12639
14161
|
console.log(`
|
|
12640
14162
|
\u2705 Backup "${id}" restored successfully.
|
|
@@ -12653,8 +14175,8 @@ program.command("migrate").description("Manage database schema migrations").opti
|
|
|
12653
14175
|
$ code-intel migrate
|
|
12654
14176
|
$ code-intel migrate --rollback
|
|
12655
14177
|
`).action((opts) => {
|
|
12656
|
-
const dbPath = opts.db ??
|
|
12657
|
-
if (!
|
|
14178
|
+
const dbPath = opts.db ?? path33.join(os12.homedir(), ".code-intel", "users.db");
|
|
14179
|
+
if (!fs33.existsSync(dbPath)) {
|
|
12658
14180
|
console.error(`
|
|
12659
14181
|
\u2717 Database not found: ${dbPath}
|
|
12660
14182
|
Run \`code-intel serve\` or \`code-intel user create\` first.
|
|
@@ -12769,15 +14291,15 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
12769
14291
|
}
|
|
12770
14292
|
try {
|
|
12771
14293
|
const tokens = await pollDeviceFlow3(config, deviceResponse);
|
|
12772
|
-
const tokenPath =
|
|
14294
|
+
const tokenPath = path33.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
12773
14295
|
const tokenData = {
|
|
12774
14296
|
accessToken: tokens.accessToken,
|
|
12775
14297
|
refreshToken: tokens.refreshToken,
|
|
12776
14298
|
server: serverUrl,
|
|
12777
14299
|
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12778
14300
|
};
|
|
12779
|
-
|
|
12780
|
-
|
|
14301
|
+
fs33.mkdirSync(path33.dirname(tokenPath), { recursive: true });
|
|
14302
|
+
fs33.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
|
|
12781
14303
|
console.log(` \u2705 Authenticated successfully!`);
|
|
12782
14304
|
console.log(` Token stored at: ${tokenPath}`);
|
|
12783
14305
|
console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
|
|
@@ -12790,13 +14312,13 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
12790
14312
|
}
|
|
12791
14313
|
});
|
|
12792
14314
|
authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
|
|
12793
|
-
const tokenPath =
|
|
12794
|
-
if (!
|
|
14315
|
+
const tokenPath = path33.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
14316
|
+
if (!fs33.existsSync(tokenPath)) {
|
|
12795
14317
|
console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
|
|
12796
14318
|
return;
|
|
12797
14319
|
}
|
|
12798
14320
|
try {
|
|
12799
|
-
const data = JSON.parse(
|
|
14321
|
+
const data = JSON.parse(fs33.readFileSync(tokenPath, "utf-8"));
|
|
12800
14322
|
console.log(`
|
|
12801
14323
|
\u2705 OIDC token stored`);
|
|
12802
14324
|
console.log(` Server : ${data.server ?? "unknown"}`);
|
|
@@ -12808,9 +14330,9 @@ authCmd.command("status").description("Show the current OIDC authentication stat
|
|
|
12808
14330
|
}
|
|
12809
14331
|
});
|
|
12810
14332
|
authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
|
|
12811
|
-
const tokenPath =
|
|
12812
|
-
if (
|
|
12813
|
-
|
|
14333
|
+
const tokenPath = path33.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
14334
|
+
if (fs33.existsSync(tokenPath)) {
|
|
14335
|
+
fs33.unlinkSync(tokenPath);
|
|
12814
14336
|
console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
|
|
12815
14337
|
} else {
|
|
12816
14338
|
console.log("\n No stored token found.\n");
|
|
@@ -12934,8 +14456,8 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
12934
14456
|
$ code-intel config-validate ./config.json
|
|
12935
14457
|
$ code-intel config-validate ~/.code-intel/config.json
|
|
12936
14458
|
`).action((file) => {
|
|
12937
|
-
const filePath =
|
|
12938
|
-
if (!
|
|
14459
|
+
const filePath = path33.resolve(file);
|
|
14460
|
+
if (!fs33.existsSync(filePath)) {
|
|
12939
14461
|
console.error(`
|
|
12940
14462
|
\u2717 File not found: ${filePath}
|
|
12941
14463
|
`);
|
|
@@ -12943,7 +14465,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
12943
14465
|
}
|
|
12944
14466
|
let cfg;
|
|
12945
14467
|
try {
|
|
12946
|
-
cfg = JSON.parse(
|
|
14468
|
+
cfg = JSON.parse(fs33.readFileSync(filePath, "utf-8"));
|
|
12947
14469
|
} catch (err) {
|
|
12948
14470
|
console.error(`
|
|
12949
14471
|
\u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
|
|
@@ -12964,7 +14486,7 @@ ${err instanceof Error ? err.message : err}
|
|
|
12964
14486
|
});
|
|
12965
14487
|
(function ensurePermissions() {
|
|
12966
14488
|
try {
|
|
12967
|
-
const dir =
|
|
14489
|
+
const dir = path33.join(os12.homedir(), ".code-intel");
|
|
12968
14490
|
secureMkdir(dir);
|
|
12969
14491
|
tightenDbFiles(dir);
|
|
12970
14492
|
} catch {
|
|
@@ -12981,22 +14503,22 @@ program.command("health").description("Run code health checks: dead code, circul
|
|
|
12981
14503
|
$ code-intel health --json
|
|
12982
14504
|
$ code-intel health --threshold 80
|
|
12983
14505
|
`).action(async (targetPath, opts) => {
|
|
12984
|
-
const workspaceRoot =
|
|
14506
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
12985
14507
|
const dbPath = getDbPath(workspaceRoot);
|
|
12986
14508
|
const meta = loadMetadata(workspaceRoot);
|
|
12987
|
-
if (!meta || !
|
|
14509
|
+
if (!meta || !fs33.existsSync(dbPath)) {
|
|
12988
14510
|
console.error(`
|
|
12989
14511
|
\u2717 ${workspaceRoot} is not indexed.`);
|
|
12990
14512
|
console.error(" Run `code-intel analyze` first to build the index.\n");
|
|
12991
14513
|
process.exit(1);
|
|
12992
14514
|
}
|
|
12993
|
-
const { computeHealthReport:
|
|
14515
|
+
const { computeHealthReport: computeHealthReport3 } = await Promise.resolve().then(() => (init_health_score(), health_score_exports));
|
|
12994
14516
|
const graph = createKnowledgeGraph();
|
|
12995
14517
|
const db = new DbManager(dbPath);
|
|
12996
14518
|
await db.init();
|
|
12997
14519
|
await loadGraphFromDB(graph, db);
|
|
12998
14520
|
db.close();
|
|
12999
|
-
const report =
|
|
14521
|
+
const report = computeHealthReport3(graph);
|
|
13000
14522
|
if (opts.json) {
|
|
13001
14523
|
console.log(JSON.stringify({
|
|
13002
14524
|
score: report.score,
|
|
@@ -13069,7 +14591,7 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
13069
14591
|
$ code-intel query --list
|
|
13070
14592
|
$ code-intel query --delete auth-search
|
|
13071
14593
|
`).action(async (gqlArg, opts) => {
|
|
13072
|
-
const workspaceRoot =
|
|
14594
|
+
const workspaceRoot = path33.resolve(opts.path);
|
|
13073
14595
|
const { saveQuery: saveQuery2, loadQuery: loadQuery2, listQueries: listQueries2, deleteQuery: deleteQuery2 } = await Promise.resolve().then(() => (init_saved_queries(), saved_queries_exports));
|
|
13074
14596
|
if (opts.list) {
|
|
13075
14597
|
const queries = listQueries2(workspaceRoot);
|
|
@@ -13128,14 +14650,14 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
13128
14650
|
}
|
|
13129
14651
|
gqlInput = content;
|
|
13130
14652
|
} else if (opts.file) {
|
|
13131
|
-
const filePath =
|
|
13132
|
-
if (!
|
|
14653
|
+
const filePath = path33.resolve(opts.file);
|
|
14654
|
+
if (!fs33.existsSync(filePath)) {
|
|
13133
14655
|
console.error(`
|
|
13134
14656
|
\u2717 File not found: ${filePath}
|
|
13135
14657
|
`);
|
|
13136
14658
|
process.exit(1);
|
|
13137
14659
|
}
|
|
13138
|
-
gqlInput =
|
|
14660
|
+
gqlInput = fs33.readFileSync(filePath, "utf-8");
|
|
13139
14661
|
} else if (gqlArg) {
|
|
13140
14662
|
gqlInput = gqlArg;
|
|
13141
14663
|
} else {
|
|
@@ -13249,6 +14771,101 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
13249
14771
|
`);
|
|
13250
14772
|
}
|
|
13251
14773
|
});
|
|
14774
|
+
program.command("pr-impact").description("Compute PR blast radius and risk scores using git diff").option("--base <ref>", "Base git ref (default: main)", "main").option("--head <ref>", "Head git ref (default: HEAD)", "HEAD").option("--fail-on <level>", "Exit code 1 if this risk level found: HIGH|MEDIUM", "").option("--format <fmt>", "Output format: text|json|sarif (default: text)", "text").option("--path <path>", "Repo path (default: current dir)").addHelpText("after", `
|
|
14775
|
+
Computes the blast radius for files changed between two git refs.
|
|
14776
|
+
|
|
14777
|
+
Examples:
|
|
14778
|
+
$ code-intel pr-impact --base main --head HEAD
|
|
14779
|
+
$ code-intel pr-impact --base main --head HEAD --fail-on HIGH
|
|
14780
|
+
$ code-intel pr-impact --base main --head HEAD --format sarif
|
|
14781
|
+
$ code-intel pr-impact --base main --head HEAD --format json
|
|
14782
|
+
`).action(async (opts) => {
|
|
14783
|
+
const repoPath = path33.resolve(opts.path ?? ".");
|
|
14784
|
+
const { execSync: execSync3 } = await import('child_process');
|
|
14785
|
+
let diff;
|
|
14786
|
+
try {
|
|
14787
|
+
diff = execSync3(`git diff --name-only ${opts.base}..${opts.head}`, {
|
|
14788
|
+
cwd: repoPath,
|
|
14789
|
+
encoding: "utf-8"
|
|
14790
|
+
});
|
|
14791
|
+
} catch (err) {
|
|
14792
|
+
console.error(`
|
|
14793
|
+
\u2717 git diff failed: ${err.message}
|
|
14794
|
+
`);
|
|
14795
|
+
process.exit(1);
|
|
14796
|
+
}
|
|
14797
|
+
const changedFiles = diff.trim().split("\n").filter(Boolean);
|
|
14798
|
+
if (changedFiles.length === 0) {
|
|
14799
|
+
if (opts.format === "json") {
|
|
14800
|
+
console.log(JSON.stringify({ changedSymbols: [], impactedSymbols: [], riskSummary: { HIGH: 0, MEDIUM: 0, LOW: 0 }, coverageGaps: [], filesToReview: [], crossRepoImpact: null }, null, 2));
|
|
14801
|
+
} else {
|
|
14802
|
+
console.log("\n \u25C8 PR Impact Report\n\n No changed files detected.\n");
|
|
14803
|
+
}
|
|
14804
|
+
return;
|
|
14805
|
+
}
|
|
14806
|
+
const { graph } = await loadOrAnalyzeWorkspace(repoPath);
|
|
14807
|
+
const { computePRImpact: computePRImpact2 } = await Promise.resolve().then(() => (init_pr_impact(), pr_impact_exports));
|
|
14808
|
+
const result = computePRImpact2(graph, changedFiles, 5);
|
|
14809
|
+
const fmt = (opts.format ?? "text").toLowerCase();
|
|
14810
|
+
if (fmt === "json") {
|
|
14811
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14812
|
+
} else if (fmt === "sarif") {
|
|
14813
|
+
const { buildSARIF: buildSARIF2 } = await Promise.resolve().then(() => (init_sarif_builder(), sarif_builder_exports));
|
|
14814
|
+
const sarif = buildSARIF2(result, _pkg.version);
|
|
14815
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
14816
|
+
} else {
|
|
14817
|
+
const highSymbols = result.changedSymbols.filter((s) => s.risk === "HIGH");
|
|
14818
|
+
const medSymbols = result.changedSymbols.filter((s) => s.risk === "MEDIUM");
|
|
14819
|
+
const lowSymbols = result.changedSymbols.filter((s) => s.risk === "LOW");
|
|
14820
|
+
console.log("\n \u25C8 PR Impact Report\n");
|
|
14821
|
+
console.log(` Changed files: ${changedFiles.length}`);
|
|
14822
|
+
console.log(` High-risk symbols: ${result.riskSummary.HIGH} | Medium: ${result.riskSummary.MEDIUM} | Low: ${result.riskSummary.LOW}
|
|
14823
|
+
`);
|
|
14824
|
+
if (highSymbols.length > 0) {
|
|
14825
|
+
console.log(" HIGH RISK:");
|
|
14826
|
+
for (const s of highSymbols) {
|
|
14827
|
+
const testNote = s.testCoverage ? "" : " no tests";
|
|
14828
|
+
console.log(` \u25C6 ${s.name.padEnd(24)} callers: ${s.callerCount}${testNote}`);
|
|
14829
|
+
}
|
|
14830
|
+
console.log("");
|
|
14831
|
+
}
|
|
14832
|
+
if (medSymbols.length > 0) {
|
|
14833
|
+
console.log(" MEDIUM RISK:");
|
|
14834
|
+
for (const s of medSymbols) {
|
|
14835
|
+
const testNote = s.testCoverage ? "" : " no tests";
|
|
14836
|
+
console.log(` \u25C6 ${s.name.padEnd(24)} callers: ${s.callerCount}${testNote}`);
|
|
14837
|
+
}
|
|
14838
|
+
console.log("");
|
|
14839
|
+
}
|
|
14840
|
+
if (lowSymbols.length > 0) {
|
|
14841
|
+
console.log(" LOW RISK:");
|
|
14842
|
+
for (const s of lowSymbols) {
|
|
14843
|
+
console.log(` \u25C6 ${s.name.padEnd(24)} callers: ${s.callerCount}`);
|
|
14844
|
+
}
|
|
14845
|
+
console.log("");
|
|
14846
|
+
}
|
|
14847
|
+
if (result.filesToReview.length > 0) {
|
|
14848
|
+
console.log(" Files to review:");
|
|
14849
|
+
for (const f of result.filesToReview) {
|
|
14850
|
+
console.log(` ${f}`);
|
|
14851
|
+
}
|
|
14852
|
+
console.log("");
|
|
14853
|
+
}
|
|
14854
|
+
if (result.coverageGaps.length > 0) {
|
|
14855
|
+
console.log(" Coverage gaps:");
|
|
14856
|
+
for (const gap of result.coverageGaps) {
|
|
14857
|
+
console.log(` ${gap}`);
|
|
14858
|
+
}
|
|
14859
|
+
console.log("");
|
|
14860
|
+
}
|
|
14861
|
+
}
|
|
14862
|
+
const failOn = (opts.failOn ?? "").toUpperCase();
|
|
14863
|
+
if (failOn === "HIGH" && result.riskSummary.HIGH > 0) {
|
|
14864
|
+
process.exit(1);
|
|
14865
|
+
} else if (failOn === "MEDIUM" && (result.riskSummary.HIGH > 0 || result.riskSummary.MEDIUM > 0)) {
|
|
14866
|
+
process.exit(1);
|
|
14867
|
+
}
|
|
14868
|
+
});
|
|
13252
14869
|
program.parse();
|
|
13253
14870
|
//# sourceMappingURL=main.js.map
|
|
13254
14871
|
//# sourceMappingURL=main.js.map
|