@vohongtho.infotech/code-intel 0.6.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 +1 -1
- package/dist/cli/main.js +1587 -816
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +490 -175
- 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);
|
|
@@ -3065,7 +3190,7 @@ var init_schema = __esm({
|
|
|
3065
3190
|
}
|
|
3066
3191
|
});
|
|
3067
3192
|
function writeNodeCSVs(graph, outputDir) {
|
|
3068
|
-
|
|
3193
|
+
fs33.mkdirSync(outputDir, { recursive: true });
|
|
3069
3194
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
3070
3195
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
3071
3196
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -3073,7 +3198,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
3073
3198
|
const table = NODE_TABLE_MAP[node.kind];
|
|
3074
3199
|
if (!tableBuffers.has(table)) {
|
|
3075
3200
|
tableBuffers.set(table, [header]);
|
|
3076
|
-
tableFilePaths.set(table,
|
|
3201
|
+
tableFilePaths.set(table, path33.join(outputDir, `${table}.csv`));
|
|
3077
3202
|
}
|
|
3078
3203
|
tableBuffers.get(table).push(
|
|
3079
3204
|
csvRow([
|
|
@@ -3093,12 +3218,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
3093
3218
|
);
|
|
3094
3219
|
}
|
|
3095
3220
|
for (const [table, lines] of tableBuffers) {
|
|
3096
|
-
|
|
3221
|
+
fs33.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
3097
3222
|
}
|
|
3098
3223
|
return tableFilePaths;
|
|
3099
3224
|
}
|
|
3100
3225
|
function writeEdgeCSV(graph, outputDir) {
|
|
3101
|
-
|
|
3226
|
+
fs33.mkdirSync(outputDir, { recursive: true });
|
|
3102
3227
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
3103
3228
|
const groups = /* @__PURE__ */ new Map();
|
|
3104
3229
|
for (const edge of graph.allEdges()) {
|
|
@@ -3109,7 +3234,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
3109
3234
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
3110
3235
|
const key = `${fromTable}->${toTable}`;
|
|
3111
3236
|
if (!groups.has(key)) {
|
|
3112
|
-
const filePath =
|
|
3237
|
+
const filePath = path33.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
3113
3238
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
3114
3239
|
}
|
|
3115
3240
|
groups.get(key).lines.push(
|
|
@@ -3124,7 +3249,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
3124
3249
|
}
|
|
3125
3250
|
const result = [];
|
|
3126
3251
|
for (const group of groups.values()) {
|
|
3127
|
-
|
|
3252
|
+
fs33.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
3128
3253
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
3129
3254
|
}
|
|
3130
3255
|
return result;
|
|
@@ -3167,7 +3292,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3167
3292
|
} catch {
|
|
3168
3293
|
}
|
|
3169
3294
|
}
|
|
3170
|
-
const tmpDir =
|
|
3295
|
+
const tmpDir = fs33.mkdtempSync(path33.join(os12.tmpdir(), "code-intel-csv-"));
|
|
3171
3296
|
try {
|
|
3172
3297
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
3173
3298
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -3186,8 +3311,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3186
3311
|
}
|
|
3187
3312
|
let nodeCount = 0;
|
|
3188
3313
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
3189
|
-
if (!
|
|
3190
|
-
const stat =
|
|
3314
|
+
if (!fs33.existsSync(csvPath)) continue;
|
|
3315
|
+
const stat = fs33.statSync(csvPath);
|
|
3191
3316
|
if (stat.size < 50) continue;
|
|
3192
3317
|
try {
|
|
3193
3318
|
await dbManager.execute(
|
|
@@ -3200,8 +3325,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3200
3325
|
}
|
|
3201
3326
|
let edgeCount = 0;
|
|
3202
3327
|
for (const group of edgeGroups) {
|
|
3203
|
-
if (!
|
|
3204
|
-
const stat =
|
|
3328
|
+
if (!fs33.existsSync(group.filePath)) continue;
|
|
3329
|
+
const stat = fs33.statSync(group.filePath);
|
|
3205
3330
|
if (stat.size < 50) continue;
|
|
3206
3331
|
try {
|
|
3207
3332
|
await dbManager.execute(
|
|
@@ -3215,7 +3340,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3215
3340
|
return { nodeCount, edgeCount };
|
|
3216
3341
|
} finally {
|
|
3217
3342
|
try {
|
|
3218
|
-
|
|
3343
|
+
fs33.rmSync(tmpDir, { recursive: true, force: true });
|
|
3219
3344
|
} catch {
|
|
3220
3345
|
}
|
|
3221
3346
|
}
|
|
@@ -3317,15 +3442,15 @@ var init_graph_loader = __esm({
|
|
|
3317
3442
|
});
|
|
3318
3443
|
function loadRegistry() {
|
|
3319
3444
|
try {
|
|
3320
|
-
const data =
|
|
3445
|
+
const data = fs33.readFileSync(REPOS_FILE, "utf-8");
|
|
3321
3446
|
return JSON.parse(data);
|
|
3322
3447
|
} catch {
|
|
3323
3448
|
return [];
|
|
3324
3449
|
}
|
|
3325
3450
|
}
|
|
3326
3451
|
function saveRegistry(entries) {
|
|
3327
|
-
|
|
3328
|
-
|
|
3452
|
+
fs33.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
3453
|
+
fs33.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
3329
3454
|
}
|
|
3330
3455
|
function upsertRepo(entry) {
|
|
3331
3456
|
const entries = loadRegistry();
|
|
@@ -3344,28 +3469,28 @@ function removeRepo(repoPath) {
|
|
|
3344
3469
|
var GLOBAL_DIR, REPOS_FILE;
|
|
3345
3470
|
var init_repo_registry = __esm({
|
|
3346
3471
|
"src/storage/repo-registry.ts"() {
|
|
3347
|
-
GLOBAL_DIR =
|
|
3348
|
-
REPOS_FILE =
|
|
3472
|
+
GLOBAL_DIR = path33.join(os12.homedir(), ".code-intel");
|
|
3473
|
+
REPOS_FILE = path33.join(GLOBAL_DIR, "repos.json");
|
|
3349
3474
|
}
|
|
3350
3475
|
});
|
|
3351
3476
|
function saveMetadata(repoDir, metadata) {
|
|
3352
|
-
const metaDir =
|
|
3353
|
-
|
|
3354
|
-
|
|
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));
|
|
3355
3480
|
}
|
|
3356
3481
|
function loadMetadata(repoDir) {
|
|
3357
3482
|
try {
|
|
3358
|
-
const data =
|
|
3483
|
+
const data = fs33.readFileSync(path33.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
3359
3484
|
return JSON.parse(data);
|
|
3360
3485
|
} catch {
|
|
3361
3486
|
return null;
|
|
3362
3487
|
}
|
|
3363
3488
|
}
|
|
3364
3489
|
function getDbPath(repoDir) {
|
|
3365
|
-
return
|
|
3490
|
+
return path33.join(repoDir, ".code-intel", "graph.db");
|
|
3366
3491
|
}
|
|
3367
3492
|
function getVectorDbPath(repoDir) {
|
|
3368
|
-
return
|
|
3493
|
+
return path33.join(repoDir, ".code-intel", "vector.db");
|
|
3369
3494
|
}
|
|
3370
3495
|
var init_metadata = __esm({
|
|
3371
3496
|
"src/storage/metadata.ts"() {
|
|
@@ -3421,27 +3546,27 @@ __export(group_registry_exports, {
|
|
|
3421
3546
|
saveSyncResult: () => saveSyncResult
|
|
3422
3547
|
});
|
|
3423
3548
|
function groupFile(name) {
|
|
3424
|
-
return
|
|
3549
|
+
return path33.join(GROUPS_DIR, `${name}.json`);
|
|
3425
3550
|
}
|
|
3426
3551
|
function loadGroup(name) {
|
|
3427
3552
|
try {
|
|
3428
|
-
return JSON.parse(
|
|
3553
|
+
return JSON.parse(fs33.readFileSync(groupFile(name), "utf-8"));
|
|
3429
3554
|
} catch {
|
|
3430
3555
|
return null;
|
|
3431
3556
|
}
|
|
3432
3557
|
}
|
|
3433
3558
|
function saveGroup(group) {
|
|
3434
|
-
|
|
3435
|
-
|
|
3559
|
+
fs33.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
3560
|
+
fs33.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
3436
3561
|
}
|
|
3437
3562
|
function listGroups() {
|
|
3438
3563
|
const groups = [];
|
|
3439
3564
|
try {
|
|
3440
|
-
for (const file of
|
|
3565
|
+
for (const file of fs33.readdirSync(GROUPS_DIR)) {
|
|
3441
3566
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
3442
3567
|
try {
|
|
3443
3568
|
const g = JSON.parse(
|
|
3444
|
-
|
|
3569
|
+
fs33.readFileSync(path33.join(GROUPS_DIR, file), "utf-8")
|
|
3445
3570
|
);
|
|
3446
3571
|
groups.push(g);
|
|
3447
3572
|
} catch {
|
|
@@ -3453,16 +3578,16 @@ function listGroups() {
|
|
|
3453
3578
|
}
|
|
3454
3579
|
function deleteGroup(name) {
|
|
3455
3580
|
try {
|
|
3456
|
-
|
|
3581
|
+
fs33.unlinkSync(groupFile(name));
|
|
3457
3582
|
} catch {
|
|
3458
3583
|
}
|
|
3459
3584
|
try {
|
|
3460
|
-
|
|
3585
|
+
fs33.unlinkSync(path33.join(GROUPS_DIR, `${name}.sync.json`));
|
|
3461
3586
|
} catch {
|
|
3462
3587
|
}
|
|
3463
3588
|
}
|
|
3464
3589
|
function groupExists(name) {
|
|
3465
|
-
return
|
|
3590
|
+
return fs33.existsSync(groupFile(name));
|
|
3466
3591
|
}
|
|
3467
3592
|
function addMember(groupName, member) {
|
|
3468
3593
|
const group = loadGroup(groupName);
|
|
@@ -3488,16 +3613,16 @@ function removeMember(groupName, groupPath) {
|
|
|
3488
3613
|
return group;
|
|
3489
3614
|
}
|
|
3490
3615
|
function saveSyncResult(result) {
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3616
|
+
fs33.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
3617
|
+
fs33.writeFileSync(
|
|
3618
|
+
path33.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
3494
3619
|
JSON.stringify(result, null, 2) + "\n"
|
|
3495
3620
|
);
|
|
3496
3621
|
}
|
|
3497
3622
|
function loadSyncResult(groupName) {
|
|
3498
3623
|
try {
|
|
3499
3624
|
return JSON.parse(
|
|
3500
|
-
|
|
3625
|
+
fs33.readFileSync(path33.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
3501
3626
|
);
|
|
3502
3627
|
} catch {
|
|
3503
3628
|
return null;
|
|
@@ -3506,90 +3631,597 @@ function loadSyncResult(groupName) {
|
|
|
3506
3631
|
var GROUPS_DIR;
|
|
3507
3632
|
var init_group_registry = __esm({
|
|
3508
3633
|
"src/multi-repo/group-registry.ts"() {
|
|
3509
|
-
GROUPS_DIR =
|
|
3634
|
+
GROUPS_DIR = path33.join(os12.homedir(), ".code-intel", "groups");
|
|
3510
3635
|
}
|
|
3511
3636
|
});
|
|
3512
3637
|
|
|
3513
|
-
// src/
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
AppError = class extends Error {
|
|
3530
|
-
constructor(code, message, hint, statusCode = 500, docs) {
|
|
3531
|
-
super(message);
|
|
3532
|
-
this.code = code;
|
|
3533
|
-
this.hint = hint;
|
|
3534
|
-
this.statusCode = statusCode;
|
|
3535
|
-
this.docs = docs;
|
|
3536
|
-
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;
|
|
3537
3654
|
}
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
fs28.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
3547
|
-
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 = [];
|
|
3548
3663
|
try {
|
|
3549
|
-
|
|
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`);
|
|
3550
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);
|
|
3551
3680
|
}
|
|
3552
3681
|
}
|
|
3553
|
-
}
|
|
3554
|
-
function secureChmodFile(file) {
|
|
3555
|
-
if (process.platform === "win32") return;
|
|
3556
3682
|
try {
|
|
3557
|
-
|
|
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
|
+
}
|
|
3558
3701
|
} catch {
|
|
3559
3702
|
}
|
|
3560
3703
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
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);
|
|
3574
3729
|
}
|
|
3575
3730
|
}
|
|
3576
3731
|
}
|
|
3732
|
+
walk2(root, 0);
|
|
3733
|
+
return results;
|
|
3577
3734
|
}
|
|
3578
|
-
var
|
|
3579
|
-
|
|
3580
|
-
"src/shared/fs-secure.ts"() {
|
|
3581
|
-
SECURE_DIR_MODE = 448;
|
|
3582
|
-
SECURE_FILE_MODE = 384;
|
|
3735
|
+
var init_file_scanner = __esm({
|
|
3736
|
+
"src/multi-repo/schema-parsers/file-scanner.ts"() {
|
|
3583
3737
|
}
|
|
3584
3738
|
});
|
|
3585
|
-
function
|
|
3586
|
-
|
|
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;
|
|
3587
3750
|
}
|
|
3588
|
-
function
|
|
3589
|
-
|
|
3590
|
-
|
|
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
|
+
}
|
|
3591
3782
|
}
|
|
3592
|
-
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;
|
|
3593
4225
|
}
|
|
3594
4226
|
var BCRYPT_ROUNDS, UsersDB, _usersDB;
|
|
3595
4227
|
var init_users_db = __esm({
|
|
@@ -3599,7 +4231,7 @@ var init_users_db = __esm({
|
|
|
3599
4231
|
UsersDB = class {
|
|
3600
4232
|
db;
|
|
3601
4233
|
constructor(dbPath) {
|
|
3602
|
-
const dir =
|
|
4234
|
+
const dir = path33.dirname(dbPath);
|
|
3603
4235
|
secureMkdir(dir);
|
|
3604
4236
|
this.db = new Database(dbPath);
|
|
3605
4237
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3876,7 +4508,7 @@ function getScryptN() {
|
|
|
3876
4508
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
3877
4509
|
}
|
|
3878
4510
|
function getSecretsPath() {
|
|
3879
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ??
|
|
4511
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path33.join(os12.homedir(), ".code-intel", ".secrets");
|
|
3880
4512
|
}
|
|
3881
4513
|
function getMasterPassword() {
|
|
3882
4514
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
@@ -3918,12 +4550,12 @@ function decryptSecrets(encrypted) {
|
|
|
3918
4550
|
return JSON.parse(plaintext.toString("utf8"));
|
|
3919
4551
|
}
|
|
3920
4552
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
3921
|
-
if (!
|
|
3922
|
-
const blob =
|
|
4553
|
+
if (!fs33.existsSync(secretsPath)) return {};
|
|
4554
|
+
const blob = fs33.readFileSync(secretsPath);
|
|
3923
4555
|
return decryptSecrets(blob);
|
|
3924
4556
|
}
|
|
3925
4557
|
function saveSecrets(blob, secretsPath = getSecretsPath()) {
|
|
3926
|
-
secureMkdir(
|
|
4558
|
+
secureMkdir(path33.dirname(secretsPath));
|
|
3927
4559
|
const encrypted = encryptSecrets(blob);
|
|
3928
4560
|
secureWriteFile(secretsPath, encrypted);
|
|
3929
4561
|
secureChmodFile(secretsPath);
|
|
@@ -4057,6 +4689,9 @@ function requireAuth(req, res, next) {
|
|
|
4057
4689
|
}
|
|
4058
4690
|
next();
|
|
4059
4691
|
}
|
|
4692
|
+
function meetsRole(userRole, required) {
|
|
4693
|
+
return (ROLE_RANK[userRole] ?? 0) >= (ROLE_RANK[required] ?? 0);
|
|
4694
|
+
}
|
|
4060
4695
|
function requireRole(...roles) {
|
|
4061
4696
|
return (req, res, next) => {
|
|
4062
4697
|
if (!req.user) {
|
|
@@ -4071,7 +4706,8 @@ function requireRole(...roles) {
|
|
|
4071
4706
|
});
|
|
4072
4707
|
return;
|
|
4073
4708
|
}
|
|
4074
|
-
|
|
4709
|
+
const allowed = roles.some((r) => meetsRole(req.user.role, r));
|
|
4710
|
+
if (!allowed) {
|
|
4075
4711
|
res.status(403).json({
|
|
4076
4712
|
error: {
|
|
4077
4713
|
code: ErrorCodes.FORBIDDEN,
|
|
@@ -4180,7 +4816,7 @@ function clearSessionCookie() {
|
|
|
4180
4816
|
async function verifyPassword(plain, hash) {
|
|
4181
4817
|
return bcrypt.compare(plain, hash);
|
|
4182
4818
|
}
|
|
4183
|
-
var sessionStore, SESSION_COOKIE_NAME;
|
|
4819
|
+
var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
|
|
4184
4820
|
var init_middleware = __esm({
|
|
4185
4821
|
"src/auth/middleware.ts"() {
|
|
4186
4822
|
init_users_db();
|
|
@@ -4188,6 +4824,12 @@ var init_middleware = __esm({
|
|
|
4188
4824
|
init_secret_store();
|
|
4189
4825
|
sessionStore = /* @__PURE__ */ new Map();
|
|
4190
4826
|
SESSION_COOKIE_NAME = "code_intel_session";
|
|
4827
|
+
ROLE_RANK = {
|
|
4828
|
+
viewer: 1,
|
|
4829
|
+
"repo-owner": 2,
|
|
4830
|
+
analyst: 3,
|
|
4831
|
+
admin: 4
|
|
4832
|
+
};
|
|
4191
4833
|
}
|
|
4192
4834
|
});
|
|
4193
4835
|
|
|
@@ -5268,6 +5910,117 @@ var init_websocket_server = __esm({
|
|
|
5268
5910
|
}
|
|
5269
5911
|
});
|
|
5270
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
|
+
|
|
5271
6024
|
// src/health/dead-code.ts
|
|
5272
6025
|
function detectDeadCode(graph) {
|
|
5273
6026
|
const results = [];
|
|
@@ -5623,10 +6376,10 @@ var init_file_watcher = __esm({
|
|
|
5623
6376
|
}
|
|
5624
6377
|
// ── private ─────────────────────────────────────────────────────────────────
|
|
5625
6378
|
readCodeIntelIgnore() {
|
|
5626
|
-
const ignoreFile =
|
|
6379
|
+
const ignoreFile = path33.join(this.workspaceRoot, ".codeintelignore");
|
|
5627
6380
|
try {
|
|
5628
|
-
if (!
|
|
5629
|
-
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("#"));
|
|
5630
6383
|
} catch {
|
|
5631
6384
|
return [];
|
|
5632
6385
|
}
|
|
@@ -5678,7 +6431,7 @@ var init_incremental_indexer = __esm({
|
|
|
5678
6431
|
}
|
|
5679
6432
|
const nodeIdsToRemove = /* @__PURE__ */ new Set();
|
|
5680
6433
|
for (const absPath of changedFiles) {
|
|
5681
|
-
const relPath2 =
|
|
6434
|
+
const relPath2 = path33.relative(workspaceRoot, absPath);
|
|
5682
6435
|
for (const id of nodesByFilePath.get(relPath2) ?? []) nodeIdsToRemove.add(id);
|
|
5683
6436
|
for (const id of nodesByFilePath.get(absPath) ?? []) nodeIdsToRemove.add(id);
|
|
5684
6437
|
}
|
|
@@ -5686,12 +6439,12 @@ var init_incremental_indexer = __esm({
|
|
|
5686
6439
|
graph.removeNodeCascade(id);
|
|
5687
6440
|
nodesRemoved++;
|
|
5688
6441
|
}
|
|
5689
|
-
if (
|
|
6442
|
+
if (fs33.existsSync(dbPath)) {
|
|
5690
6443
|
try {
|
|
5691
6444
|
const db = new DbManager(dbPath);
|
|
5692
6445
|
await db.init();
|
|
5693
6446
|
for (const absPath of changedFiles) {
|
|
5694
|
-
const relPath2 =
|
|
6447
|
+
const relPath2 = path33.relative(workspaceRoot, absPath);
|
|
5695
6448
|
await removeNodesForFile(relPath2, db);
|
|
5696
6449
|
}
|
|
5697
6450
|
db.close();
|
|
@@ -5701,7 +6454,7 @@ var init_incremental_indexer = __esm({
|
|
|
5701
6454
|
}
|
|
5702
6455
|
const existingFiles = changedFiles.filter((f) => {
|
|
5703
6456
|
try {
|
|
5704
|
-
return
|
|
6457
|
+
return fs33.statSync(f).isFile();
|
|
5705
6458
|
} catch {
|
|
5706
6459
|
return false;
|
|
5707
6460
|
}
|
|
@@ -5723,13 +6476,13 @@ var init_incremental_indexer = __esm({
|
|
|
5723
6476
|
await runPipeline([noopScan, parsePhase, resolvePhase], context2);
|
|
5724
6477
|
}
|
|
5725
6478
|
const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
|
|
5726
|
-
if (
|
|
6479
|
+
if (fs33.existsSync(dbPath) && existingFiles.length > 0) {
|
|
5727
6480
|
try {
|
|
5728
6481
|
const db = new DbManager(dbPath);
|
|
5729
6482
|
await db.init();
|
|
5730
|
-
const changedRelPaths = new Set(changedFiles.map((f) =>
|
|
6483
|
+
const changedRelPaths = new Set(changedFiles.map((f) => path33.relative(workspaceRoot, f)));
|
|
5731
6484
|
const nodesToUpsert = [...graph.allNodes()].filter(
|
|
5732
|
-
(n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(
|
|
6485
|
+
(n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path33.relative(workspaceRoot, n.filePath))
|
|
5733
6486
|
);
|
|
5734
6487
|
await upsertNodes(nodesToUpsert, db);
|
|
5735
6488
|
db.close();
|
|
@@ -5760,35 +6513,35 @@ __export(saved_queries_exports, {
|
|
|
5760
6513
|
saveQuery: () => saveQuery
|
|
5761
6514
|
});
|
|
5762
6515
|
function getQueriesDir(workspaceRoot) {
|
|
5763
|
-
return
|
|
6516
|
+
return path33.join(workspaceRoot, ".code-intel", "queries");
|
|
5764
6517
|
}
|
|
5765
6518
|
function ensureQueriesDir(workspaceRoot) {
|
|
5766
6519
|
const dir = getQueriesDir(workspaceRoot);
|
|
5767
|
-
if (!
|
|
5768
|
-
|
|
6520
|
+
if (!fs33.existsSync(dir)) {
|
|
6521
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
5769
6522
|
}
|
|
5770
6523
|
return dir;
|
|
5771
6524
|
}
|
|
5772
6525
|
function saveQuery(workspaceRoot, name, gql) {
|
|
5773
6526
|
const dir = ensureQueriesDir(workspaceRoot);
|
|
5774
|
-
const filePath =
|
|
5775
|
-
|
|
6527
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6528
|
+
fs33.writeFileSync(filePath, gql, "utf-8");
|
|
5776
6529
|
}
|
|
5777
6530
|
function loadQuery(workspaceRoot, name) {
|
|
5778
6531
|
const dir = getQueriesDir(workspaceRoot);
|
|
5779
|
-
const filePath =
|
|
5780
|
-
if (!
|
|
5781
|
-
return
|
|
6532
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6533
|
+
if (!fs33.existsSync(filePath)) return null;
|
|
6534
|
+
return fs33.readFileSync(filePath, "utf-8");
|
|
5782
6535
|
}
|
|
5783
6536
|
function listQueries(workspaceRoot) {
|
|
5784
6537
|
const dir = getQueriesDir(workspaceRoot);
|
|
5785
|
-
if (!
|
|
5786
|
-
const files =
|
|
6538
|
+
if (!fs33.existsSync(dir)) return [];
|
|
6539
|
+
const files = fs33.readdirSync(dir).filter((f) => f.endsWith(".gql"));
|
|
5787
6540
|
return files.map((f) => {
|
|
5788
|
-
const filePath =
|
|
6541
|
+
const filePath = path33.join(dir, f);
|
|
5789
6542
|
const name = f.replace(/\.gql$/, "");
|
|
5790
|
-
const content =
|
|
5791
|
-
const stat =
|
|
6543
|
+
const content = fs33.readFileSync(filePath, "utf-8");
|
|
6544
|
+
const stat = fs33.statSync(filePath);
|
|
5792
6545
|
return {
|
|
5793
6546
|
name,
|
|
5794
6547
|
content,
|
|
@@ -5799,146 +6552,85 @@ function listQueries(workspaceRoot) {
|
|
|
5799
6552
|
}
|
|
5800
6553
|
function deleteQuery(workspaceRoot, name) {
|
|
5801
6554
|
const dir = getQueriesDir(workspaceRoot);
|
|
5802
|
-
const filePath =
|
|
5803
|
-
if (!
|
|
5804
|
-
|
|
6555
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6556
|
+
if (!fs33.existsSync(filePath)) return false;
|
|
6557
|
+
fs33.unlinkSync(filePath);
|
|
5805
6558
|
return true;
|
|
5806
6559
|
}
|
|
5807
6560
|
function queryExists(workspaceRoot, name) {
|
|
5808
6561
|
const dir = getQueriesDir(workspaceRoot);
|
|
5809
|
-
const filePath =
|
|
5810
|
-
return
|
|
6562
|
+
const filePath = path33.join(dir, `${name}.gql`);
|
|
6563
|
+
return fs33.existsSync(filePath);
|
|
5811
6564
|
}
|
|
5812
6565
|
var init_saved_queries = __esm({
|
|
5813
6566
|
"src/query/saved-queries.ts"() {
|
|
5814
6567
|
}
|
|
5815
6568
|
});
|
|
5816
6569
|
|
|
5817
|
-
// src/cli/
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
const
|
|
5824
|
-
const
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
if (edge) yield edge;
|
|
5872
|
-
}
|
|
5873
|
-
},
|
|
5874
|
-
*findEdgesFrom(sourceId) {
|
|
5875
|
-
const ids = edgesFromNode.get(sourceId);
|
|
5876
|
-
if (!ids) return;
|
|
5877
|
-
for (const id of ids) {
|
|
5878
|
-
const edge = edges.get(id);
|
|
5879
|
-
if (edge) yield edge;
|
|
5880
|
-
}
|
|
5881
|
-
},
|
|
5882
|
-
*findEdgesTo(targetId) {
|
|
5883
|
-
const ids = edgesToNode.get(targetId);
|
|
5884
|
-
if (!ids) return;
|
|
5885
|
-
for (const id of ids) {
|
|
5886
|
-
const edge = edges.get(id);
|
|
5887
|
-
if (edge) yield edge;
|
|
5888
|
-
}
|
|
5889
|
-
},
|
|
5890
|
-
removeNodeCascade(id) {
|
|
5891
|
-
const fromEdges = edgesFromNode.get(id);
|
|
5892
|
-
if (fromEdges) {
|
|
5893
|
-
for (const edgeId of [...fromEdges]) {
|
|
5894
|
-
const edge = edges.get(edgeId);
|
|
5895
|
-
if (edge) {
|
|
5896
|
-
unindexEdge(edge);
|
|
5897
|
-
edges.delete(edgeId);
|
|
5898
|
-
}
|
|
5899
|
-
}
|
|
5900
|
-
}
|
|
5901
|
-
const toEdges = edgesToNode.get(id);
|
|
5902
|
-
if (toEdges) {
|
|
5903
|
-
for (const edgeId of [...toEdges]) {
|
|
5904
|
-
const edge = edges.get(edgeId);
|
|
5905
|
-
if (edge) {
|
|
5906
|
-
unindexEdge(edge);
|
|
5907
|
-
edges.delete(edgeId);
|
|
5908
|
-
}
|
|
5909
|
-
}
|
|
5910
|
-
}
|
|
5911
|
-
edgesFromNode.delete(id);
|
|
5912
|
-
edgesToNode.delete(id);
|
|
5913
|
-
nodes.delete(id);
|
|
5914
|
-
},
|
|
5915
|
-
removeEdge(id) {
|
|
5916
|
-
const edge = edges.get(id);
|
|
5917
|
-
if (edge) {
|
|
5918
|
-
unindexEdge(edge);
|
|
5919
|
-
edges.delete(id);
|
|
5920
|
-
}
|
|
5921
|
-
},
|
|
5922
|
-
*allNodes() {
|
|
5923
|
-
yield* nodes.values();
|
|
5924
|
-
},
|
|
5925
|
-
*allEdges() {
|
|
5926
|
-
yield* edges.values();
|
|
5927
|
-
},
|
|
5928
|
-
get size() {
|
|
5929
|
-
return { nodes: nodes.size, edges: edges.size };
|
|
5930
|
-
},
|
|
5931
|
-
clear() {
|
|
5932
|
-
nodes.clear();
|
|
5933
|
-
edges.clear();
|
|
5934
|
-
edgesByKind.clear();
|
|
5935
|
-
edgesFromNode.clear();
|
|
5936
|
-
edgesToNode.clear();
|
|
5937
|
-
}
|
|
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 }
|
|
6593
|
+
}
|
|
6594
|
+
}
|
|
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
|
+
]
|
|
6619
|
+
}
|
|
6620
|
+
},
|
|
6621
|
+
results
|
|
6622
|
+
}
|
|
6623
|
+
]
|
|
5938
6624
|
};
|
|
5939
6625
|
}
|
|
6626
|
+
var init_sarif_builder = __esm({
|
|
6627
|
+
"src/cli/sarif-builder.ts"() {
|
|
6628
|
+
}
|
|
6629
|
+
});
|
|
5940
6630
|
|
|
5941
6631
|
// src/cli/main.ts
|
|
6632
|
+
init_logger();
|
|
6633
|
+
init_knowledge_graph();
|
|
5942
6634
|
init_orchestrator();
|
|
5943
6635
|
|
|
5944
6636
|
// src/pipeline/phases/scan-phase.ts
|
|
@@ -5977,7 +6669,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
5977
6669
|
]);
|
|
5978
6670
|
function loadIgnorePatterns(workspaceRoot) {
|
|
5979
6671
|
try {
|
|
5980
|
-
const raw =
|
|
6672
|
+
const raw = fs33.readFileSync(path33.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
5981
6673
|
const extras = /* @__PURE__ */ new Set();
|
|
5982
6674
|
for (const line of raw.split("\n")) {
|
|
5983
6675
|
const trimmed = line.trim();
|
|
@@ -6001,7 +6693,7 @@ var scanPhase = {
|
|
|
6001
6693
|
function walk2(dir) {
|
|
6002
6694
|
let entries;
|
|
6003
6695
|
try {
|
|
6004
|
-
entries =
|
|
6696
|
+
entries = fs33.readdirSync(dir, { withFileTypes: true });
|
|
6005
6697
|
} catch {
|
|
6006
6698
|
return;
|
|
6007
6699
|
}
|
|
@@ -6010,15 +6702,15 @@ var scanPhase = {
|
|
|
6010
6702
|
if (entry.name.startsWith(".")) continue;
|
|
6011
6703
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6012
6704
|
if (extraIgnore.has(entry.name)) continue;
|
|
6013
|
-
walk2(
|
|
6705
|
+
walk2(path33.join(dir, entry.name));
|
|
6014
6706
|
} else if (entry.isFile()) {
|
|
6015
6707
|
const name = entry.name;
|
|
6016
6708
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
6017
|
-
const ext =
|
|
6709
|
+
const ext = path33.extname(name);
|
|
6018
6710
|
if (!extensions.has(ext)) continue;
|
|
6019
|
-
const fullPath =
|
|
6711
|
+
const fullPath = path33.join(dir, name);
|
|
6020
6712
|
try {
|
|
6021
|
-
const stat =
|
|
6713
|
+
const stat = fs33.statSync(fullPath);
|
|
6022
6714
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
6023
6715
|
} catch {
|
|
6024
6716
|
continue;
|
|
@@ -6045,20 +6737,20 @@ var structurePhase = {
|
|
|
6045
6737
|
const dirs = /* @__PURE__ */ new Set();
|
|
6046
6738
|
let structDone = 0;
|
|
6047
6739
|
for (const filePath of context2.filePaths) {
|
|
6048
|
-
const relativePath =
|
|
6740
|
+
const relativePath = path33.relative(context2.workspaceRoot, filePath);
|
|
6049
6741
|
const lang = detectLanguage(filePath);
|
|
6050
6742
|
context2.graph.addNode({
|
|
6051
6743
|
id: generateNodeId("file", relativePath, relativePath),
|
|
6052
6744
|
kind: "file",
|
|
6053
|
-
name:
|
|
6745
|
+
name: path33.basename(filePath),
|
|
6054
6746
|
filePath: relativePath,
|
|
6055
6747
|
metadata: lang ? { language: lang } : void 0
|
|
6056
6748
|
});
|
|
6057
|
-
let dir =
|
|
6749
|
+
let dir = path33.dirname(relativePath);
|
|
6058
6750
|
while (dir && dir !== "." && dir !== "") {
|
|
6059
6751
|
if (dirs.has(dir)) break;
|
|
6060
6752
|
dirs.add(dir);
|
|
6061
|
-
dir =
|
|
6753
|
+
dir = path33.dirname(dir);
|
|
6062
6754
|
}
|
|
6063
6755
|
structDone++;
|
|
6064
6756
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -6067,7 +6759,7 @@ var structurePhase = {
|
|
|
6067
6759
|
context2.graph.addNode({
|
|
6068
6760
|
id: generateNodeId("directory", dir, dir),
|
|
6069
6761
|
kind: "directory",
|
|
6070
|
-
name:
|
|
6762
|
+
name: path33.basename(dir),
|
|
6071
6763
|
filePath: dir
|
|
6072
6764
|
});
|
|
6073
6765
|
}
|
|
@@ -6178,22 +6870,22 @@ var flowPhase = {
|
|
|
6178
6870
|
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
6179
6871
|
const visited = /* @__PURE__ */ new Set();
|
|
6180
6872
|
while (queue.length > 0 && flowCount < maxFlows) {
|
|
6181
|
-
const { nodeId, path:
|
|
6182
|
-
if (
|
|
6873
|
+
const { nodeId, path: path34 } = queue.shift();
|
|
6874
|
+
if (path34.length > maxDepth) continue;
|
|
6183
6875
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
6184
|
-
if (callEdges.length === 0 &&
|
|
6876
|
+
if (callEdges.length === 0 && path34.length >= 3) {
|
|
6185
6877
|
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
6186
6878
|
graph.addNode({
|
|
6187
6879
|
id: flowId,
|
|
6188
6880
|
kind: "flow",
|
|
6189
6881
|
name: `${ep.name} flow ${flowCount}`,
|
|
6190
6882
|
filePath: ep.filePath,
|
|
6191
|
-
metadata: { steps:
|
|
6883
|
+
metadata: { steps: path34, entryPoint: ep.name }
|
|
6192
6884
|
});
|
|
6193
|
-
for (let i = 0; i <
|
|
6885
|
+
for (let i = 0; i < path34.length; i++) {
|
|
6194
6886
|
graph.addEdge({
|
|
6195
|
-
id: generateEdgeId(
|
|
6196
|
-
source:
|
|
6887
|
+
id: generateEdgeId(path34[i], flowId, `step_of_${i}`),
|
|
6888
|
+
source: path34[i],
|
|
6197
6889
|
target: flowId,
|
|
6198
6890
|
kind: "step_of",
|
|
6199
6891
|
weight: 1,
|
|
@@ -6206,7 +6898,7 @@ var flowPhase = {
|
|
|
6206
6898
|
for (const edge of callEdges) {
|
|
6207
6899
|
if (visited.has(edge.target)) continue;
|
|
6208
6900
|
visited.add(edge.target);
|
|
6209
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6901
|
+
queue.push({ nodeId: edge.target, path: [...path34, edge.target] });
|
|
6210
6902
|
}
|
|
6211
6903
|
}
|
|
6212
6904
|
}
|
|
@@ -6224,7 +6916,7 @@ var LLMGovernanceLogger = class {
|
|
|
6224
6916
|
}
|
|
6225
6917
|
/** Path to the JSONL log file. */
|
|
6226
6918
|
getLogPath() {
|
|
6227
|
-
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");
|
|
6228
6920
|
}
|
|
6229
6921
|
/**
|
|
6230
6922
|
* Append an entry to the governance log.
|
|
@@ -6240,8 +6932,8 @@ var LLMGovernanceLogger = class {
|
|
|
6240
6932
|
...entry
|
|
6241
6933
|
};
|
|
6242
6934
|
const logPath = this.getLogPath();
|
|
6243
|
-
|
|
6244
|
-
|
|
6935
|
+
fs33.mkdirSync(path33.dirname(logPath), { recursive: true });
|
|
6936
|
+
fs33.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
6245
6937
|
} catch {
|
|
6246
6938
|
}
|
|
6247
6939
|
}
|
|
@@ -6251,7 +6943,7 @@ var LLMGovernanceLogger = class {
|
|
|
6251
6943
|
*/
|
|
6252
6944
|
readLog(limit = 100) {
|
|
6253
6945
|
try {
|
|
6254
|
-
const raw =
|
|
6946
|
+
const raw = fs33.readFileSync(this.getLogPath(), "utf-8");
|
|
6255
6947
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
6256
6948
|
return lines.map((l) => JSON.parse(l));
|
|
6257
6949
|
} catch {
|
|
@@ -6565,7 +7257,7 @@ var LANG_QUERIES2 = {
|
|
|
6565
7257
|
};
|
|
6566
7258
|
function workerScriptPath() {
|
|
6567
7259
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6568
|
-
return
|
|
7260
|
+
return path33.join(path33.dirname(thisFile), "parse-worker.js");
|
|
6569
7261
|
}
|
|
6570
7262
|
var parsePhaseParallel = {
|
|
6571
7263
|
name: "parse",
|
|
@@ -6581,14 +7273,14 @@ var parsePhaseParallel = {
|
|
|
6581
7273
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
6582
7274
|
await Promise.all(batch.map(async (filePath) => {
|
|
6583
7275
|
try {
|
|
6584
|
-
const source = await
|
|
7276
|
+
const source = await fs33.promises.readFile(filePath, "utf-8");
|
|
6585
7277
|
context2.fileCache.set(filePath, source);
|
|
6586
7278
|
} catch {
|
|
6587
7279
|
}
|
|
6588
7280
|
}));
|
|
6589
7281
|
}
|
|
6590
7282
|
const workerScript = workerScriptPath();
|
|
6591
|
-
const workerScriptExists =
|
|
7283
|
+
const workerScriptExists = fs33.existsSync(workerScript);
|
|
6592
7284
|
if (!workerScriptExists || workerCount === 1) {
|
|
6593
7285
|
logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
|
|
6594
7286
|
const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
|
|
@@ -6600,7 +7292,7 @@ var parsePhaseParallel = {
|
|
|
6600
7292
|
if (!lang) continue;
|
|
6601
7293
|
const source = context2.fileCache.get(filePath);
|
|
6602
7294
|
if (!source) continue;
|
|
6603
|
-
const relativePath =
|
|
7295
|
+
const relativePath = path33.relative(context2.workspaceRoot, filePath);
|
|
6604
7296
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
6605
7297
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
6606
7298
|
if (fileNode) fileNode.content = source.slice(0, 2e3);
|
|
@@ -6643,7 +7335,7 @@ var parsePhaseParallel = {
|
|
|
6643
7335
|
symbolCount += res.nodes.length;
|
|
6644
7336
|
if (res.usedTreeSitter) treeSitterCount++;
|
|
6645
7337
|
else regexCount++;
|
|
6646
|
-
const relativePath =
|
|
7338
|
+
const relativePath = path33.relative(context2.workspaceRoot, res.taskId);
|
|
6647
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);
|
|
6648
7340
|
if (funcs.length > 0) context2.fileFunctionIndex.set(relativePath, funcs);
|
|
6649
7341
|
parseDone++;
|
|
@@ -6670,7 +7362,7 @@ init_id_generator();
|
|
|
6670
7362
|
init_logger();
|
|
6671
7363
|
function workerScriptPath2() {
|
|
6672
7364
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6673
|
-
return
|
|
7365
|
+
return path33.join(path33.dirname(thisFile), "resolve-worker.js");
|
|
6674
7366
|
}
|
|
6675
7367
|
var resolvePhaseParallel = {
|
|
6676
7368
|
name: "resolve",
|
|
@@ -6682,11 +7374,11 @@ var resolvePhaseParallel = {
|
|
|
6682
7374
|
const fileFunctionIndex = context2.fileFunctionIndex ?? /* @__PURE__ */ new Map();
|
|
6683
7375
|
const fileIndex = {};
|
|
6684
7376
|
for (const fp of filePaths) {
|
|
6685
|
-
const rel =
|
|
7377
|
+
const rel = path33.relative(workspaceRoot, fp);
|
|
6686
7378
|
fileIndex[rel] = fp;
|
|
6687
7379
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
6688
7380
|
if (!fileIndex[noExt]) fileIndex[noExt] = fp;
|
|
6689
|
-
const base =
|
|
7381
|
+
const base = path33.basename(rel, path33.extname(rel));
|
|
6690
7382
|
if (!fileIndex[base]) fileIndex[base] = fp;
|
|
6691
7383
|
}
|
|
6692
7384
|
const symbolIndex = {};
|
|
@@ -6700,7 +7392,7 @@ var resolvePhaseParallel = {
|
|
|
6700
7392
|
}
|
|
6701
7393
|
const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
|
|
6702
7394
|
const workerScript = workerScriptPath2();
|
|
6703
|
-
const workerScriptExists =
|
|
7395
|
+
const workerScriptExists = fs33.existsSync(workerScript);
|
|
6704
7396
|
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os12.cpus().length - 1);
|
|
6705
7397
|
if (!workerScriptExists || workerCount === 1) {
|
|
6706
7398
|
logger_default.info(`[resolve-parallel] falling back to sequential`);
|
|
@@ -6713,7 +7405,7 @@ var resolvePhaseParallel = {
|
|
|
6713
7405
|
if (!lang) continue;
|
|
6714
7406
|
const source = fileCache.get(filePath);
|
|
6715
7407
|
if (!source) continue;
|
|
6716
|
-
const relativePath =
|
|
7408
|
+
const relativePath = path33.relative(workspaceRoot, filePath);
|
|
6717
7409
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
6718
7410
|
const funcList = fileFunctionIndex.get(relativePath) ?? [];
|
|
6719
7411
|
tasks.push({ taskId: filePath, filePath, relativePath, fileNodeId, source, funcList });
|
|
@@ -6836,7 +7528,7 @@ init_embedder();
|
|
|
6836
7528
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6837
7529
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
6838
7530
|
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6839
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
7531
|
+
const hasVectorDb = Boolean(vectorDbPath && fs33.existsSync(vectorDbPath));
|
|
6840
7532
|
if (!hasVectorDb) {
|
|
6841
7533
|
const bm25Results2 = await bm25Promise;
|
|
6842
7534
|
return {
|
|
@@ -6891,236 +7583,11 @@ init_storage();
|
|
|
6891
7583
|
init_metadata();
|
|
6892
7584
|
init_vector_index();
|
|
6893
7585
|
init_group_registry();
|
|
6894
|
-
|
|
6895
|
-
// src/multi-repo/group-sync.ts
|
|
6896
|
-
init_repo_registry();
|
|
6897
|
-
init_db_manager();
|
|
6898
|
-
|
|
6899
|
-
// src/multi-repo/graph-from-db.ts
|
|
6900
|
-
init_schema();
|
|
6901
|
-
var TABLE_TO_KIND = Object.fromEntries(
|
|
6902
|
-
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
6903
|
-
);
|
|
6904
|
-
function parseRow(row, kind) {
|
|
6905
|
-
return {
|
|
6906
|
-
id: String(row["id"] ?? ""),
|
|
6907
|
-
kind,
|
|
6908
|
-
name: String(row["name"] ?? ""),
|
|
6909
|
-
filePath: String(row["file_path"] ?? ""),
|
|
6910
|
-
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
6911
|
-
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
6912
|
-
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
6913
|
-
content: row["content"] ? String(row["content"]) : void 0,
|
|
6914
|
-
metadata: row["metadata"] ? (() => {
|
|
6915
|
-
try {
|
|
6916
|
-
return JSON.parse(String(row["metadata"]));
|
|
6917
|
-
} catch {
|
|
6918
|
-
return void 0;
|
|
6919
|
-
}
|
|
6920
|
-
})() : void 0
|
|
6921
|
-
};
|
|
6922
|
-
}
|
|
6923
|
-
async function loadGraphFromDB(graph, db) {
|
|
6924
|
-
for (const table of ALL_NODE_TABLES) {
|
|
6925
|
-
const kind = TABLE_TO_KIND[table];
|
|
6926
|
-
if (!kind) continue;
|
|
6927
|
-
let rows = [];
|
|
6928
|
-
try {
|
|
6929
|
-
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`);
|
|
6930
|
-
} catch {
|
|
6931
|
-
continue;
|
|
6932
|
-
}
|
|
6933
|
-
for (const row of rows) {
|
|
6934
|
-
const node = parseRow({
|
|
6935
|
-
id: row["n.id"],
|
|
6936
|
-
name: row["n.name"],
|
|
6937
|
-
file_path: row["n.file_path"],
|
|
6938
|
-
start_line: row["n.start_line"],
|
|
6939
|
-
end_line: row["n.end_line"],
|
|
6940
|
-
exported: row["n.exported"],
|
|
6941
|
-
content: row["n.content"],
|
|
6942
|
-
metadata: row["n.metadata"]
|
|
6943
|
-
}, kind);
|
|
6944
|
-
if (node.id && node.name) graph.addNode(node);
|
|
6945
|
-
}
|
|
6946
|
-
}
|
|
6947
|
-
try {
|
|
6948
|
-
const edgeRows = await db.query(
|
|
6949
|
-
`MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
|
|
6950
|
-
);
|
|
6951
|
-
for (const row of edgeRows) {
|
|
6952
|
-
const sourceId = String(row["a.id"] ?? "");
|
|
6953
|
-
const targetId = String(row["b.id"] ?? "");
|
|
6954
|
-
const kind = String(row["e.kind"] ?? "");
|
|
6955
|
-
if (!sourceId || !targetId || !kind) continue;
|
|
6956
|
-
const edge = {
|
|
6957
|
-
id: `${sourceId}::${kind}::${targetId}`,
|
|
6958
|
-
source: sourceId,
|
|
6959
|
-
target: targetId,
|
|
6960
|
-
kind,
|
|
6961
|
-
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
6962
|
-
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
6963
|
-
};
|
|
6964
|
-
graph.addEdge(edge);
|
|
6965
|
-
}
|
|
6966
|
-
} catch {
|
|
6967
|
-
}
|
|
6968
|
-
}
|
|
6969
|
-
|
|
6970
|
-
// src/multi-repo/group-sync.ts
|
|
6971
|
-
init_logger();
|
|
6972
|
-
function extractContracts(graph, repoName, repoPath) {
|
|
6973
|
-
const contracts = [];
|
|
6974
|
-
for (const node of graph.allNodes()) {
|
|
6975
|
-
if (node.exported === true && ["function", "class", "interface", "method", "type_alias", "constant", "enum", "struct", "trait"].includes(node.kind)) {
|
|
6976
|
-
contracts.push({
|
|
6977
|
-
repoName,
|
|
6978
|
-
repoPath,
|
|
6979
|
-
kind: "export",
|
|
6980
|
-
name: node.name,
|
|
6981
|
-
nodeId: node.id,
|
|
6982
|
-
nodeKind: node.kind,
|
|
6983
|
-
filePath: node.filePath,
|
|
6984
|
-
signature: node.content?.split("\n")[0]?.trim()
|
|
6985
|
-
});
|
|
6986
|
-
}
|
|
6987
|
-
if (node.kind === "route") {
|
|
6988
|
-
contracts.push({
|
|
6989
|
-
repoName,
|
|
6990
|
-
repoPath,
|
|
6991
|
-
kind: "route",
|
|
6992
|
-
name: node.name,
|
|
6993
|
-
nodeId: node.id,
|
|
6994
|
-
nodeKind: node.kind,
|
|
6995
|
-
filePath: node.filePath,
|
|
6996
|
-
signature: node.content?.split("\n")[0]?.trim()
|
|
6997
|
-
});
|
|
6998
|
-
}
|
|
6999
|
-
if (["interface", "type_alias"].includes(node.kind)) {
|
|
7000
|
-
const nameLower = node.name.toLowerCase();
|
|
7001
|
-
if (nameLower.includes("event") || nameLower.includes("message")) {
|
|
7002
|
-
contracts.push({
|
|
7003
|
-
repoName,
|
|
7004
|
-
repoPath,
|
|
7005
|
-
kind: "event",
|
|
7006
|
-
name: node.name,
|
|
7007
|
-
nodeId: node.id,
|
|
7008
|
-
nodeKind: node.kind,
|
|
7009
|
-
filePath: node.filePath
|
|
7010
|
-
});
|
|
7011
|
-
} else if (nameLower.includes("schema") || nameLower.includes("dto") || nameLower.includes("request") || nameLower.includes("response")) {
|
|
7012
|
-
contracts.push({
|
|
7013
|
-
repoName,
|
|
7014
|
-
repoPath,
|
|
7015
|
-
kind: "schema",
|
|
7016
|
-
name: node.name,
|
|
7017
|
-
nodeId: node.id,
|
|
7018
|
-
nodeKind: node.kind,
|
|
7019
|
-
filePath: node.filePath
|
|
7020
|
-
});
|
|
7021
|
-
}
|
|
7022
|
-
}
|
|
7023
|
-
}
|
|
7024
|
-
return contracts;
|
|
7025
|
-
}
|
|
7026
|
-
function matchContracts(allContracts) {
|
|
7027
|
-
const links = [];
|
|
7028
|
-
const byRepo = /* @__PURE__ */ new Map();
|
|
7029
|
-
for (const c of allContracts) {
|
|
7030
|
-
const arr = byRepo.get(c.repoName) ?? [];
|
|
7031
|
-
arr.push(c);
|
|
7032
|
-
byRepo.set(c.repoName, arr);
|
|
7033
|
-
}
|
|
7034
|
-
const repoNames = [...byRepo.keys()];
|
|
7035
|
-
for (let i = 0; i < repoNames.length; i++) {
|
|
7036
|
-
for (let j = 0; j < repoNames.length; j++) {
|
|
7037
|
-
if (i === j) continue;
|
|
7038
|
-
const providerContracts = byRepo.get(repoNames[i]);
|
|
7039
|
-
const consumerContracts = byRepo.get(repoNames[j]);
|
|
7040
|
-
const consumerByName = /* @__PURE__ */ new Map();
|
|
7041
|
-
for (const c of consumerContracts) consumerByName.set(c.name, c);
|
|
7042
|
-
for (const provider of providerContracts) {
|
|
7043
|
-
const consumer = consumerByName.get(provider.name);
|
|
7044
|
-
if (consumer) {
|
|
7045
|
-
const sameKind = provider.kind === consumer.kind;
|
|
7046
|
-
links.push({
|
|
7047
|
-
providerRepo: provider.repoName,
|
|
7048
|
-
providerContract: provider.name,
|
|
7049
|
-
consumerRepo: consumer.repoName,
|
|
7050
|
-
consumerContract: consumer.name,
|
|
7051
|
-
matchKind: provider.kind === "route" ? "route-match" : "name-match",
|
|
7052
|
-
confidence: sameKind ? 0.9 : 0.6
|
|
7053
|
-
});
|
|
7054
|
-
} else {
|
|
7055
|
-
const providerLC = provider.name.toLowerCase();
|
|
7056
|
-
for (const c of consumerContracts) {
|
|
7057
|
-
if (c.name.toLowerCase().includes(providerLC) || providerLC.includes(c.name.toLowerCase())) {
|
|
7058
|
-
if (c.name.length >= 4 && provider.name.length >= 4) {
|
|
7059
|
-
links.push({
|
|
7060
|
-
providerRepo: provider.repoName,
|
|
7061
|
-
providerContract: provider.name,
|
|
7062
|
-
consumerRepo: c.repoName,
|
|
7063
|
-
consumerContract: c.name,
|
|
7064
|
-
matchKind: "name-match",
|
|
7065
|
-
confidence: 0.4
|
|
7066
|
-
});
|
|
7067
|
-
}
|
|
7068
|
-
}
|
|
7069
|
-
}
|
|
7070
|
-
}
|
|
7071
|
-
}
|
|
7072
|
-
}
|
|
7073
|
-
}
|
|
7074
|
-
const seen = /* @__PURE__ */ new Map();
|
|
7075
|
-
for (const link of links) {
|
|
7076
|
-
const key = `${link.providerRepo}:${link.providerContract}:${link.consumerRepo}:${link.consumerContract}`;
|
|
7077
|
-
const existing = seen.get(key);
|
|
7078
|
-
if (!existing || link.confidence > existing.confidence) {
|
|
7079
|
-
seen.set(key, link);
|
|
7080
|
-
}
|
|
7081
|
-
}
|
|
7082
|
-
return [...seen.values()].sort((a, b) => b.confidence - a.confidence);
|
|
7083
|
-
}
|
|
7084
|
-
async function syncGroup(group) {
|
|
7085
|
-
const registry = loadRegistry();
|
|
7086
|
-
const allContracts = [];
|
|
7087
|
-
for (const member of group.members) {
|
|
7088
|
-
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7089
|
-
if (!regEntry) {
|
|
7090
|
-
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
7091
|
-
continue;
|
|
7092
|
-
}
|
|
7093
|
-
const dbPath = path30.join(regEntry.path, ".code-intel", "graph.db");
|
|
7094
|
-
if (!fs28.existsSync(dbPath)) {
|
|
7095
|
-
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
7096
|
-
continue;
|
|
7097
|
-
}
|
|
7098
|
-
const graph = createKnowledgeGraph();
|
|
7099
|
-
const db = new DbManager(dbPath);
|
|
7100
|
-
try {
|
|
7101
|
-
await db.init();
|
|
7102
|
-
await loadGraphFromDB(graph, db);
|
|
7103
|
-
db.close();
|
|
7104
|
-
} catch (err) {
|
|
7105
|
-
db.close();
|
|
7106
|
-
logger_default.warn(` \u26A0 Could not load graph for "${member.registryName}": ${err instanceof Error ? err.message : err}`);
|
|
7107
|
-
continue;
|
|
7108
|
-
}
|
|
7109
|
-
const contracts = extractContracts(graph, member.registryName, regEntry.path);
|
|
7110
|
-
logger_default.info(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
|
|
7111
|
-
allContracts.push(...contracts);
|
|
7112
|
-
}
|
|
7113
|
-
const links = matchContracts(allContracts);
|
|
7114
|
-
return {
|
|
7115
|
-
groupName: group.name,
|
|
7116
|
-
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7117
|
-
memberCount: group.members.length,
|
|
7118
|
-
contracts: allContracts,
|
|
7119
|
-
links
|
|
7120
|
-
};
|
|
7121
|
-
}
|
|
7586
|
+
init_group_sync();
|
|
7122
7587
|
init_repo_registry();
|
|
7123
7588
|
init_db_manager();
|
|
7589
|
+
init_knowledge_graph();
|
|
7590
|
+
init_graph_from_db();
|
|
7124
7591
|
async function queryGroup(group, query, limit = 20) {
|
|
7125
7592
|
const registry = loadRegistry();
|
|
7126
7593
|
const perRepo = [];
|
|
@@ -7128,8 +7595,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7128
7595
|
for (const member of group.members) {
|
|
7129
7596
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7130
7597
|
if (!regEntry) continue;
|
|
7131
|
-
const dbPath =
|
|
7132
|
-
if (!
|
|
7598
|
+
const dbPath = path33.join(regEntry.path, ".code-intel", "graph.db");
|
|
7599
|
+
if (!fs33.existsSync(dbPath)) continue;
|
|
7133
7600
|
const graph = createKnowledgeGraph();
|
|
7134
7601
|
const db = new DbManager(dbPath);
|
|
7135
7602
|
try {
|
|
@@ -7158,6 +7625,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7158
7625
|
}
|
|
7159
7626
|
|
|
7160
7627
|
// src/http/app.ts
|
|
7628
|
+
init_knowledge_graph();
|
|
7629
|
+
init_graph_from_db();
|
|
7161
7630
|
init_repo_registry();
|
|
7162
7631
|
init_logger();
|
|
7163
7632
|
init_codes();
|
|
@@ -7169,7 +7638,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
7169
7638
|
var JobsDB = class {
|
|
7170
7639
|
db;
|
|
7171
7640
|
constructor(dbPath) {
|
|
7172
|
-
|
|
7641
|
+
fs33.mkdirSync(path33.dirname(dbPath), { recursive: true });
|
|
7173
7642
|
this.db = new Database(dbPath);
|
|
7174
7643
|
this.db.pragma("journal_mode = WAL");
|
|
7175
7644
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -7311,7 +7780,7 @@ var JobsDB = class {
|
|
|
7311
7780
|
}
|
|
7312
7781
|
};
|
|
7313
7782
|
function getJobsDBPath() {
|
|
7314
|
-
return
|
|
7783
|
+
return path33.join(os12.homedir(), ".code-intel", "jobs.db");
|
|
7315
7784
|
}
|
|
7316
7785
|
var _jobsDB = null;
|
|
7317
7786
|
function getOrCreateJobsDB() {
|
|
@@ -7403,7 +7872,7 @@ var BACKUP_VERSION = "1.0";
|
|
|
7403
7872
|
var ALGORITHM = "aes-256-gcm";
|
|
7404
7873
|
var IV_LENGTH = 16;
|
|
7405
7874
|
function getBackupDir() {
|
|
7406
|
-
return
|
|
7875
|
+
return path33.join(os12.homedir(), ".code-intel", "backups");
|
|
7407
7876
|
}
|
|
7408
7877
|
function getBackupKey() {
|
|
7409
7878
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
@@ -7434,30 +7903,30 @@ var BackupService = class {
|
|
|
7434
7903
|
constructor(backupDir) {
|
|
7435
7904
|
this.backupDir = backupDir ?? getBackupDir();
|
|
7436
7905
|
this.key = getBackupKey();
|
|
7437
|
-
|
|
7906
|
+
fs33.mkdirSync(this.backupDir, { recursive: true });
|
|
7438
7907
|
}
|
|
7439
7908
|
/**
|
|
7440
7909
|
* Create a backup for a repository.
|
|
7441
7910
|
* Returns the backup entry.
|
|
7442
7911
|
*/
|
|
7443
7912
|
createBackup(repoPath) {
|
|
7444
|
-
const codeIntelDir =
|
|
7913
|
+
const codeIntelDir = path33.join(repoPath, ".code-intel");
|
|
7445
7914
|
const id = v4();
|
|
7446
7915
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7447
7916
|
const filesToBackup = [];
|
|
7448
7917
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
7449
7918
|
for (const f of candidates) {
|
|
7450
|
-
const fp =
|
|
7451
|
-
if (
|
|
7919
|
+
const fp = path33.join(codeIntelDir, f);
|
|
7920
|
+
if (fs33.existsSync(fp)) {
|
|
7452
7921
|
filesToBackup.push({ name: f, localPath: fp });
|
|
7453
7922
|
}
|
|
7454
7923
|
}
|
|
7455
|
-
const registryPath =
|
|
7456
|
-
if (
|
|
7924
|
+
const registryPath = path33.join(os12.homedir(), ".code-intel", "registry.json");
|
|
7925
|
+
if (fs33.existsSync(registryPath)) {
|
|
7457
7926
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
7458
7927
|
}
|
|
7459
|
-
const usersDbPath =
|
|
7460
|
-
if (
|
|
7928
|
+
const usersDbPath = path33.join(os12.homedir(), ".code-intel", "users.db");
|
|
7929
|
+
if (fs33.existsSync(usersDbPath)) {
|
|
7461
7930
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
7462
7931
|
}
|
|
7463
7932
|
if (filesToBackup.length === 0) {
|
|
@@ -7468,7 +7937,7 @@ var BackupService = class {
|
|
|
7468
7937
|
createdAt,
|
|
7469
7938
|
version: BACKUP_VERSION,
|
|
7470
7939
|
files: filesToBackup.map((f) => {
|
|
7471
|
-
const data =
|
|
7940
|
+
const data = fs33.readFileSync(f.localPath);
|
|
7472
7941
|
return {
|
|
7473
7942
|
name: f.name,
|
|
7474
7943
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -7482,7 +7951,7 @@ var BackupService = class {
|
|
|
7482
7951
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
7483
7952
|
parts.push(manifestLenBuf, manifestBuf);
|
|
7484
7953
|
for (const f of filesToBackup) {
|
|
7485
|
-
const data =
|
|
7954
|
+
const data = fs33.readFileSync(f.localPath);
|
|
7486
7955
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
7487
7956
|
const nameLenBuf = Buffer.alloc(2);
|
|
7488
7957
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -7493,8 +7962,8 @@ var BackupService = class {
|
|
|
7493
7962
|
const plaintext = Buffer.concat(parts);
|
|
7494
7963
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
7495
7964
|
const backupFileName = `backup-${id}.cib`;
|
|
7496
|
-
const backupPath =
|
|
7497
|
-
|
|
7965
|
+
const backupPath = path33.join(this.backupDir, backupFileName);
|
|
7966
|
+
fs33.writeFileSync(backupPath, encrypted);
|
|
7498
7967
|
const entry = {
|
|
7499
7968
|
id,
|
|
7500
7969
|
createdAt,
|
|
@@ -7521,9 +7990,9 @@ var BackupService = class {
|
|
|
7521
7990
|
async uploadToS3(entry) {
|
|
7522
7991
|
const cfg = getS3Config();
|
|
7523
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.");
|
|
7524
|
-
const fileName =
|
|
7993
|
+
const fileName = path33.basename(entry.path);
|
|
7525
7994
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
7526
|
-
const body =
|
|
7995
|
+
const body = fs33.readFileSync(entry.path);
|
|
7527
7996
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
7528
7997
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
7529
7998
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -7540,8 +8009,8 @@ var BackupService = class {
|
|
|
7540
8009
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
7541
8010
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
7542
8011
|
}
|
|
7543
|
-
|
|
7544
|
-
|
|
8012
|
+
fs33.mkdirSync(path33.dirname(destPath), { recursive: true });
|
|
8013
|
+
fs33.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
7545
8014
|
}
|
|
7546
8015
|
/**
|
|
7547
8016
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -7587,10 +8056,10 @@ var BackupService = class {
|
|
|
7587
8056
|
if (!entry) {
|
|
7588
8057
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
7589
8058
|
}
|
|
7590
|
-
if (!
|
|
8059
|
+
if (!fs33.existsSync(entry.path)) {
|
|
7591
8060
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
7592
8061
|
}
|
|
7593
|
-
const encrypted =
|
|
8062
|
+
const encrypted = fs33.readFileSync(entry.path);
|
|
7594
8063
|
let plaintext;
|
|
7595
8064
|
try {
|
|
7596
8065
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -7604,8 +8073,8 @@ var BackupService = class {
|
|
|
7604
8073
|
offset += manifestLen;
|
|
7605
8074
|
const manifest = JSON.parse(manifestStr);
|
|
7606
8075
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
7607
|
-
const codeIntelDir =
|
|
7608
|
-
|
|
8076
|
+
const codeIntelDir = path33.join(restoreBase, ".code-intel");
|
|
8077
|
+
fs33.mkdirSync(codeIntelDir, { recursive: true });
|
|
7609
8078
|
for (const fileEntry of manifest.files) {
|
|
7610
8079
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
7611
8080
|
offset += 2;
|
|
@@ -7622,18 +8091,18 @@ var BackupService = class {
|
|
|
7622
8091
|
}
|
|
7623
8092
|
let destPath;
|
|
7624
8093
|
if (name === "registry.json" || name === "users.db") {
|
|
7625
|
-
destPath =
|
|
8094
|
+
destPath = path33.join(os12.homedir(), ".code-intel", name);
|
|
7626
8095
|
} else {
|
|
7627
|
-
destPath =
|
|
8096
|
+
destPath = path33.join(codeIntelDir, name);
|
|
7628
8097
|
}
|
|
7629
|
-
|
|
8098
|
+
fs33.writeFileSync(destPath, data);
|
|
7630
8099
|
}
|
|
7631
8100
|
}
|
|
7632
8101
|
/**
|
|
7633
8102
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
7634
8103
|
*/
|
|
7635
8104
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
7636
|
-
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());
|
|
7637
8106
|
const keep = /* @__PURE__ */ new Set();
|
|
7638
8107
|
const now = /* @__PURE__ */ new Date();
|
|
7639
8108
|
const dailyCutoff = new Date(now);
|
|
@@ -7663,7 +8132,7 @@ var BackupService = class {
|
|
|
7663
8132
|
for (const e of entries) {
|
|
7664
8133
|
if (!keep.has(e.id)) {
|
|
7665
8134
|
try {
|
|
7666
|
-
|
|
8135
|
+
fs33.unlinkSync(e.path);
|
|
7667
8136
|
deleted++;
|
|
7668
8137
|
} catch {
|
|
7669
8138
|
}
|
|
@@ -7675,17 +8144,17 @@ var BackupService = class {
|
|
|
7675
8144
|
}
|
|
7676
8145
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
7677
8146
|
_indexPath() {
|
|
7678
|
-
return
|
|
8147
|
+
return path33.join(this.backupDir, "index.json");
|
|
7679
8148
|
}
|
|
7680
8149
|
_loadIndex() {
|
|
7681
8150
|
try {
|
|
7682
|
-
return JSON.parse(
|
|
8151
|
+
return JSON.parse(fs33.readFileSync(this._indexPath(), "utf-8"));
|
|
7683
8152
|
} catch {
|
|
7684
8153
|
return [];
|
|
7685
8154
|
}
|
|
7686
8155
|
}
|
|
7687
8156
|
_saveIndex(entries) {
|
|
7688
|
-
|
|
8157
|
+
fs33.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
7689
8158
|
}
|
|
7690
8159
|
_appendIndex(entry) {
|
|
7691
8160
|
const entries = this._loadIndex();
|
|
@@ -8192,6 +8661,30 @@ var openApiSpec = {
|
|
|
8192
8661
|
}
|
|
8193
8662
|
}
|
|
8194
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
|
+
},
|
|
8195
8688
|
"/query": {
|
|
8196
8689
|
post: {
|
|
8197
8690
|
tags: ["GQL"],
|
|
@@ -8302,11 +8795,11 @@ var openApiSpec = {
|
|
|
8302
8795
|
};
|
|
8303
8796
|
|
|
8304
8797
|
// src/http/app.ts
|
|
8305
|
-
var __dirname$1 =
|
|
8798
|
+
var __dirname$1 = path33.dirname(fileURLToPath(import.meta.url));
|
|
8306
8799
|
var WEB_DIST = (() => {
|
|
8307
|
-
const bundled =
|
|
8308
|
-
if (
|
|
8309
|
-
return
|
|
8800
|
+
const bundled = path33.resolve(__dirname$1, "..", "web");
|
|
8801
|
+
if (fs33.existsSync(bundled)) return bundled;
|
|
8802
|
+
return path33.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
8310
8803
|
})();
|
|
8311
8804
|
function getAllowedOrigins() {
|
|
8312
8805
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -8837,8 +9330,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8837
9330
|
const registry = loadRegistry();
|
|
8838
9331
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
8839
9332
|
if (!entry) return null;
|
|
8840
|
-
const dbPath =
|
|
8841
|
-
if (!
|
|
9333
|
+
const dbPath = path33.join(entry.path, ".code-intel", "graph.db");
|
|
9334
|
+
if (!fs33.existsSync(dbPath)) return null;
|
|
8842
9335
|
const repoGraph = createKnowledgeGraph();
|
|
8843
9336
|
const db = new DbManager(dbPath);
|
|
8844
9337
|
try {
|
|
@@ -8925,7 +9418,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8925
9418
|
return;
|
|
8926
9419
|
}
|
|
8927
9420
|
try {
|
|
8928
|
-
const content =
|
|
9421
|
+
const content = fs33.readFileSync(file_path, "utf-8");
|
|
8929
9422
|
res.json({ content });
|
|
8930
9423
|
} catch {
|
|
8931
9424
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -9183,8 +9676,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9183
9676
|
for (const member of group.members) {
|
|
9184
9677
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
9185
9678
|
if (!regEntry) continue;
|
|
9186
|
-
const dbPath =
|
|
9187
|
-
if (!
|
|
9679
|
+
const dbPath = path33.join(regEntry.path, ".code-intel", "graph.db");
|
|
9680
|
+
if (!fs33.existsSync(dbPath)) continue;
|
|
9188
9681
|
const db = new DbManager(dbPath);
|
|
9189
9682
|
try {
|
|
9190
9683
|
await db.init();
|
|
@@ -9196,6 +9689,45 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9196
9689
|
}
|
|
9197
9690
|
res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
|
|
9198
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
|
+
});
|
|
9199
9731
|
app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
|
|
9200
9732
|
const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
|
|
9201
9733
|
if (!file) {
|
|
@@ -9221,14 +9753,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9221
9753
|
});
|
|
9222
9754
|
return;
|
|
9223
9755
|
}
|
|
9224
|
-
let rawResolved =
|
|
9225
|
-
if (!
|
|
9226
|
-
rawResolved =
|
|
9756
|
+
let rawResolved = path33.normalize(file);
|
|
9757
|
+
if (!path33.isAbsolute(rawResolved) && workspaceRoot) {
|
|
9758
|
+
rawResolved = path33.join(workspaceRoot, rawResolved);
|
|
9227
9759
|
}
|
|
9228
|
-
const resolvedFile =
|
|
9760
|
+
const resolvedFile = path33.resolve(rawResolved);
|
|
9229
9761
|
function isInsideDir(fileAbs, dir) {
|
|
9230
|
-
const rel =
|
|
9231
|
-
return !rel.startsWith("..") && !
|
|
9762
|
+
const rel = path33.relative(path33.resolve(dir), fileAbs);
|
|
9763
|
+
return !rel.startsWith("..") && !path33.isAbsolute(rel);
|
|
9232
9764
|
}
|
|
9233
9765
|
if (workspaceRoot) {
|
|
9234
9766
|
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
@@ -9265,7 +9797,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9265
9797
|
}
|
|
9266
9798
|
let fileContent;
|
|
9267
9799
|
try {
|
|
9268
|
-
fileContent =
|
|
9800
|
+
fileContent = fs33.readFileSync(resolvedFile, "utf-8");
|
|
9269
9801
|
} catch {
|
|
9270
9802
|
res.status(404).json({
|
|
9271
9803
|
error: {
|
|
@@ -9296,7 +9828,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9296
9828
|
const contextStart = Math.max(1, startLine - 20);
|
|
9297
9829
|
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
9298
9830
|
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
9299
|
-
const ext =
|
|
9831
|
+
const ext = path33.extname(resolvedFile).toLowerCase();
|
|
9300
9832
|
const languageMap = {
|
|
9301
9833
|
".ts": "typescript",
|
|
9302
9834
|
".tsx": "typescript",
|
|
@@ -9431,10 +9963,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9431
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() } });
|
|
9432
9964
|
}
|
|
9433
9965
|
});
|
|
9434
|
-
if (
|
|
9966
|
+
if (fs33.existsSync(WEB_DIST)) {
|
|
9435
9967
|
app.use(express.static(WEB_DIST));
|
|
9436
9968
|
app.get("/{*path}", (_req, res) => {
|
|
9437
|
-
res.sendFile(
|
|
9969
|
+
res.sendFile(path33.join(WEB_DIST, "index.html"));
|
|
9438
9970
|
});
|
|
9439
9971
|
}
|
|
9440
9972
|
app.use("/admin", requireRole("admin"));
|
|
@@ -9589,6 +10121,7 @@ init_storage();
|
|
|
9589
10121
|
init_repo_registry();
|
|
9590
10122
|
init_metadata();
|
|
9591
10123
|
init_group_registry();
|
|
10124
|
+
init_group_sync();
|
|
9592
10125
|
init_tracing();
|
|
9593
10126
|
init_metrics();
|
|
9594
10127
|
|
|
@@ -9670,107 +10203,8 @@ function explainRelationship(graph, from, to) {
|
|
|
9670
10203
|
return { paths, sharedImports, heritage, summary };
|
|
9671
10204
|
}
|
|
9672
10205
|
|
|
9673
|
-
// src/
|
|
9674
|
-
|
|
9675
|
-
const files = [];
|
|
9676
|
-
for (const line of diff.split("\n")) {
|
|
9677
|
-
const match = line.match(/^\+\+\+ b\/(.+)/);
|
|
9678
|
-
if (match) {
|
|
9679
|
-
files.push(match[1]);
|
|
9680
|
-
}
|
|
9681
|
-
}
|
|
9682
|
-
return files;
|
|
9683
|
-
}
|
|
9684
|
-
function computePRImpact(graph, changedFiles, maxHops) {
|
|
9685
|
-
const changedSymbolIds = /* @__PURE__ */ new Set();
|
|
9686
|
-
for (const node of graph.allNodes()) {
|
|
9687
|
-
if (!node.filePath) continue;
|
|
9688
|
-
for (const changedFile of changedFiles) {
|
|
9689
|
-
if (node.filePath === changedFile || node.filePath.endsWith(changedFile) || changedFile.endsWith(node.filePath)) {
|
|
9690
|
-
changedSymbolIds.add(node.id);
|
|
9691
|
-
break;
|
|
9692
|
-
}
|
|
9693
|
-
}
|
|
9694
|
-
}
|
|
9695
|
-
const allBlastRadiusNodes = /* @__PURE__ */ new Set();
|
|
9696
|
-
const changedSymbols = [];
|
|
9697
|
-
for (const symbolId of changedSymbolIds) {
|
|
9698
|
-
const symbolNode = graph.getNode(symbolId);
|
|
9699
|
-
if (!symbolNode) continue;
|
|
9700
|
-
const blastRadius = /* @__PURE__ */ new Set();
|
|
9701
|
-
const queue = [{ id: symbolId, depth: 0 }];
|
|
9702
|
-
const visited = /* @__PURE__ */ new Set();
|
|
9703
|
-
while (queue.length > 0) {
|
|
9704
|
-
const { id, depth } = queue.shift();
|
|
9705
|
-
if (visited.has(id) || depth > maxHops) continue;
|
|
9706
|
-
visited.add(id);
|
|
9707
|
-
if (id !== symbolId) blastRadius.add(id);
|
|
9708
|
-
for (const edge of graph.findEdgesTo(id)) {
|
|
9709
|
-
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
9710
|
-
queue.push({ id: edge.source, depth: depth + 1 });
|
|
9711
|
-
}
|
|
9712
|
-
}
|
|
9713
|
-
}
|
|
9714
|
-
for (const id of blastRadius) allBlastRadiusNodes.add(id);
|
|
9715
|
-
const blastCount = blastRadius.size;
|
|
9716
|
-
let risk;
|
|
9717
|
-
if (blastCount > 50) {
|
|
9718
|
-
risk = "HIGH";
|
|
9719
|
-
} else if (blastCount >= 10) {
|
|
9720
|
-
risk = "MEDIUM";
|
|
9721
|
-
} else {
|
|
9722
|
-
risk = "LOW";
|
|
9723
|
-
}
|
|
9724
|
-
let callerCount = 0;
|
|
9725
|
-
for (const edge of graph.findEdgesTo(symbolId)) {
|
|
9726
|
-
if (edge.kind === "calls") callerCount++;
|
|
9727
|
-
}
|
|
9728
|
-
let testCoverage = false;
|
|
9729
|
-
for (const edge of graph.findEdgesTo(symbolId)) {
|
|
9730
|
-
if (edge.kind === "imports") {
|
|
9731
|
-
const callerNode = graph.getNode(edge.source);
|
|
9732
|
-
if (callerNode?.filePath && (callerNode.filePath.includes(".test.") || callerNode.filePath.includes(".spec."))) {
|
|
9733
|
-
testCoverage = true;
|
|
9734
|
-
break;
|
|
9735
|
-
}
|
|
9736
|
-
}
|
|
9737
|
-
}
|
|
9738
|
-
changedSymbols.push({ name: symbolNode.name, risk, callerCount, testCoverage });
|
|
9739
|
-
}
|
|
9740
|
-
const impactedSymbols = [];
|
|
9741
|
-
for (const id of allBlastRadiusNodes) {
|
|
9742
|
-
if (changedSymbolIds.has(id)) continue;
|
|
9743
|
-
const node = graph.getNode(id);
|
|
9744
|
-
if (node) {
|
|
9745
|
-
impactedSymbols.push({ name: node.name, filePath: node.filePath });
|
|
9746
|
-
}
|
|
9747
|
-
}
|
|
9748
|
-
const riskSummary = { HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
9749
|
-
for (const s of changedSymbols) {
|
|
9750
|
-
riskSummary[s.risk]++;
|
|
9751
|
-
}
|
|
9752
|
-
const coverageGaps = [];
|
|
9753
|
-
for (const s of changedSymbols) {
|
|
9754
|
-
if ((s.risk === "HIGH" || s.risk === "MEDIUM") && !s.testCoverage) {
|
|
9755
|
-
coverageGaps.push(`${s.name} has no test coverage`);
|
|
9756
|
-
}
|
|
9757
|
-
}
|
|
9758
|
-
const fileImpactCount = /* @__PURE__ */ new Map();
|
|
9759
|
-
for (const sym of impactedSymbols) {
|
|
9760
|
-
if (sym.filePath) {
|
|
9761
|
-
fileImpactCount.set(sym.filePath, (fileImpactCount.get(sym.filePath) ?? 0) + 1);
|
|
9762
|
-
}
|
|
9763
|
-
}
|
|
9764
|
-
const filesToReview = [...fileImpactCount.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([fp]) => fp);
|
|
9765
|
-
return {
|
|
9766
|
-
changedSymbols,
|
|
9767
|
-
impactedSymbols,
|
|
9768
|
-
riskSummary,
|
|
9769
|
-
coverageGaps,
|
|
9770
|
-
filesToReview,
|
|
9771
|
-
crossRepoImpact: null
|
|
9772
|
-
};
|
|
9773
|
-
}
|
|
10206
|
+
// src/mcp-server/server.ts
|
|
10207
|
+
init_pr_impact();
|
|
9774
10208
|
|
|
9775
10209
|
// src/query/similar-symbols.ts
|
|
9776
10210
|
function levenshtein(a, b) {
|
|
@@ -10061,22 +10495,22 @@ function suggestTests(graph, symbolName) {
|
|
|
10061
10495
|
const callPaths = [];
|
|
10062
10496
|
const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
|
|
10063
10497
|
while (pathQueue.length > 0 && callPaths.length < 5) {
|
|
10064
|
-
const { id, path:
|
|
10498
|
+
const { id, path: path34, depth } = pathQueue.shift();
|
|
10065
10499
|
let hasCallers = false;
|
|
10066
10500
|
for (const edge of graph.findEdgesTo(id)) {
|
|
10067
10501
|
if (edge.kind !== "calls") continue;
|
|
10068
10502
|
const callerNode = graph.getNode(edge.source);
|
|
10069
10503
|
if (!callerNode) continue;
|
|
10070
10504
|
hasCallers = true;
|
|
10071
|
-
const newPath = [callerNode.name, ...
|
|
10505
|
+
const newPath = [callerNode.name, ...path34];
|
|
10072
10506
|
if (depth + 1 >= 3 || callPaths.length >= 5) {
|
|
10073
10507
|
if (callPaths.length < 5) callPaths.push(newPath);
|
|
10074
10508
|
continue;
|
|
10075
10509
|
}
|
|
10076
10510
|
pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
|
|
10077
10511
|
}
|
|
10078
|
-
if (!hasCallers &&
|
|
10079
|
-
callPaths.push(
|
|
10512
|
+
if (!hasCallers && path34.length > 1) {
|
|
10513
|
+
callPaths.push(path34);
|
|
10080
10514
|
}
|
|
10081
10515
|
}
|
|
10082
10516
|
if (callPaths.length === 0) {
|
|
@@ -10974,7 +11408,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
10974
11408
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
10975
11409
|
for (const node of graph.allNodes()) {
|
|
10976
11410
|
if (!node.filePath) continue;
|
|
10977
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
11411
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path33.sep, "");
|
|
10978
11412
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
10979
11413
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
10980
11414
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -11333,20 +11767,20 @@ function parseDiff(diffText) {
|
|
|
11333
11767
|
// src/cli/main.ts
|
|
11334
11768
|
init_metadata();
|
|
11335
11769
|
async function writeSkillFiles(graph, workspaceRoot, projectName) {
|
|
11336
|
-
const outputDir =
|
|
11770
|
+
const outputDir = path33.join(workspaceRoot, ".claude", "skills", "code-intel");
|
|
11337
11771
|
const areas = buildAreaMap(graph, workspaceRoot);
|
|
11338
11772
|
if (areas.length === 0) return { skills: [], outputDir };
|
|
11339
|
-
|
|
11340
|
-
|
|
11773
|
+
fs33.rmSync(outputDir, { recursive: true, force: true });
|
|
11774
|
+
fs33.mkdirSync(outputDir, { recursive: true });
|
|
11341
11775
|
const skills = [];
|
|
11342
11776
|
const usedNames = /* @__PURE__ */ new Set();
|
|
11343
11777
|
for (const area of areas) {
|
|
11344
11778
|
const kebab = uniqueKebab(area.label, usedNames);
|
|
11345
11779
|
usedNames.add(kebab);
|
|
11346
11780
|
const content = renderSkill(area, projectName, kebab);
|
|
11347
|
-
const dir =
|
|
11348
|
-
|
|
11349
|
-
|
|
11781
|
+
const dir = path33.join(outputDir, kebab);
|
|
11782
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
11783
|
+
fs33.writeFileSync(path33.join(dir, "SKILL.md"), content, "utf-8");
|
|
11350
11784
|
skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
|
|
11351
11785
|
}
|
|
11352
11786
|
return { skills, outputDir };
|
|
@@ -11526,8 +11960,8 @@ var BLOCK_START = "<!-- code-intel:start -->";
|
|
|
11526
11960
|
var BLOCK_END = "<!-- code-intel:end -->";
|
|
11527
11961
|
function writeContextFiles(workspaceRoot, projectName, stats, skills) {
|
|
11528
11962
|
const block = buildBlock(projectName, stats, skills);
|
|
11529
|
-
upsertFile(
|
|
11530
|
-
upsertFile(
|
|
11963
|
+
upsertFile(path33.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
|
|
11964
|
+
upsertFile(path33.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
|
|
11531
11965
|
}
|
|
11532
11966
|
function buildBlock(projectName, stats, skills) {
|
|
11533
11967
|
const skillRows = skills.map(
|
|
@@ -11581,7 +12015,7 @@ ${skillTable}
|
|
|
11581
12015
|
${BLOCK_END}`;
|
|
11582
12016
|
}
|
|
11583
12017
|
function upsertFile(filePath, block, fileName) {
|
|
11584
|
-
if (!
|
|
12018
|
+
if (!fs33.existsSync(filePath)) {
|
|
11585
12019
|
const newContent = [
|
|
11586
12020
|
`# ${fileName}`,
|
|
11587
12021
|
"",
|
|
@@ -11592,17 +12026,17 @@ function upsertFile(filePath, block, fileName) {
|
|
|
11592
12026
|
"<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
|
|
11593
12027
|
""
|
|
11594
12028
|
].join("\n");
|
|
11595
|
-
|
|
12029
|
+
fs33.writeFileSync(filePath, newContent, "utf-8");
|
|
11596
12030
|
return;
|
|
11597
12031
|
}
|
|
11598
|
-
const existing =
|
|
12032
|
+
const existing = fs33.readFileSync(filePath, "utf-8");
|
|
11599
12033
|
const startIdx = findLineMarker(existing, BLOCK_START);
|
|
11600
12034
|
const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
|
|
11601
12035
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
11602
12036
|
const before = existing.slice(0, startIdx);
|
|
11603
12037
|
const after = existing.slice(endIdx + BLOCK_END.length);
|
|
11604
12038
|
const updated = (before + block + after).trimEnd() + "\n";
|
|
11605
|
-
|
|
12039
|
+
fs33.writeFileSync(filePath, updated, "utf-8");
|
|
11606
12040
|
return;
|
|
11607
12041
|
}
|
|
11608
12042
|
const appended = [
|
|
@@ -11615,7 +12049,7 @@ function upsertFile(filePath, block, fileName) {
|
|
|
11615
12049
|
block,
|
|
11616
12050
|
""
|
|
11617
12051
|
].join("\n");
|
|
11618
|
-
|
|
12052
|
+
fs33.writeFileSync(filePath, appended, "utf-8");
|
|
11619
12053
|
}
|
|
11620
12054
|
function findLineMarker(content, marker, startFrom = 0) {
|
|
11621
12055
|
let idx = content.indexOf(marker, startFrom);
|
|
@@ -11657,14 +12091,14 @@ function getChangedFilesSince(workspaceRoot, baseHash) {
|
|
|
11657
12091
|
function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
|
|
11658
12092
|
const changed = [];
|
|
11659
12093
|
for (const absPath of allFilePaths) {
|
|
11660
|
-
const rel =
|
|
12094
|
+
const rel = path33.relative(workspaceRoot, absPath);
|
|
11661
12095
|
const stored = storedMtimes[rel];
|
|
11662
12096
|
if (stored === void 0) {
|
|
11663
12097
|
changed.push(absPath);
|
|
11664
12098
|
continue;
|
|
11665
12099
|
}
|
|
11666
12100
|
try {
|
|
11667
|
-
const { mtimeMs } =
|
|
12101
|
+
const { mtimeMs } = fs33.statSync(absPath);
|
|
11668
12102
|
if (mtimeMs > stored) changed.push(absPath);
|
|
11669
12103
|
} catch {
|
|
11670
12104
|
changed.push(absPath);
|
|
@@ -11676,8 +12110,8 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
|
|
|
11676
12110
|
const snap = {};
|
|
11677
12111
|
for (const absPath of filePaths) {
|
|
11678
12112
|
try {
|
|
11679
|
-
const { mtimeMs } =
|
|
11680
|
-
snap[
|
|
12113
|
+
const { mtimeMs } = fs33.statSync(absPath);
|
|
12114
|
+
snap[path33.relative(workspaceRoot, absPath)] = mtimeMs;
|
|
11681
12115
|
} catch {
|
|
11682
12116
|
}
|
|
11683
12117
|
}
|
|
@@ -11688,8 +12122,8 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
|
|
|
11688
12122
|
if (prevCommitHash) {
|
|
11689
12123
|
const changed = getChangedFilesSince(workspaceRoot, prevCommitHash);
|
|
11690
12124
|
if (changed !== null) {
|
|
11691
|
-
const scanSet = new Set(allFilePaths.map((p) =>
|
|
11692
|
-
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));
|
|
11693
12127
|
if (total > 0 && changedInScan.length / total > 0.2) {
|
|
11694
12128
|
return { incremental: false, fallbackReason: `changed files (${changedInScan.length}) > 20% of total (${total})` };
|
|
11695
12129
|
}
|
|
@@ -11710,7 +12144,135 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
|
|
|
11710
12144
|
}
|
|
11711
12145
|
|
|
11712
12146
|
// src/cli/main.ts
|
|
12147
|
+
init_graph_from_db();
|
|
11713
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
|
|
11714
12276
|
init_users_db();
|
|
11715
12277
|
var migrations = [
|
|
11716
12278
|
{
|
|
@@ -11803,17 +12365,17 @@ var MigrationRunner = class {
|
|
|
11803
12365
|
autoBackupBeforeMigration() {
|
|
11804
12366
|
try {
|
|
11805
12367
|
const dbFile = this.db.name;
|
|
11806
|
-
if (!dbFile || !
|
|
11807
|
-
const backupDir =
|
|
11808
|
-
|
|
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 });
|
|
11809
12371
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
11810
|
-
const baseName =
|
|
11811
|
-
const backupPath =
|
|
12372
|
+
const baseName = path33.basename(dbFile, ".db");
|
|
12373
|
+
const backupPath = path33.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
|
|
11812
12374
|
try {
|
|
11813
12375
|
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
11814
12376
|
} catch {
|
|
11815
12377
|
}
|
|
11816
|
-
|
|
12378
|
+
fs33.copyFileSync(dbFile, backupPath);
|
|
11817
12379
|
} catch {
|
|
11818
12380
|
}
|
|
11819
12381
|
}
|
|
@@ -12029,7 +12591,7 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
|
|
|
12029
12591
|
Docs: https://github.com/vohongtho/code-intel-platform
|
|
12030
12592
|
`);
|
|
12031
12593
|
async function analyzeWorkspace(targetPath, options) {
|
|
12032
|
-
const workspaceRoot =
|
|
12594
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
12033
12595
|
if (!options?.silent) console.log(`Analyzing: ${workspaceRoot}`);
|
|
12034
12596
|
logger_default.info(`analyze started: ${workspaceRoot}`);
|
|
12035
12597
|
if (options?.force) {
|
|
@@ -12050,14 +12612,14 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12050
12612
|
];
|
|
12051
12613
|
for (const f of wipeFiles) {
|
|
12052
12614
|
try {
|
|
12053
|
-
if (
|
|
12615
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
12054
12616
|
} catch {
|
|
12055
12617
|
}
|
|
12056
12618
|
}
|
|
12057
12619
|
}
|
|
12058
12620
|
if (!options?.skipGit) {
|
|
12059
|
-
const gitDir =
|
|
12060
|
-
if (!
|
|
12621
|
+
const gitDir = path33.join(workspaceRoot, ".git");
|
|
12622
|
+
if (!fs33.existsSync(gitDir)) {
|
|
12061
12623
|
logger_default.warn(`${workspaceRoot} is not a Git repository`);
|
|
12062
12624
|
}
|
|
12063
12625
|
}
|
|
@@ -12168,17 +12730,17 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12168
12730
|
const result = await runPipeline(phases, context2);
|
|
12169
12731
|
if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
|
|
12170
12732
|
const dbPath = getDbPath(workspaceRoot);
|
|
12171
|
-
if (
|
|
12733
|
+
if (fs33.existsSync(dbPath)) {
|
|
12172
12734
|
try {
|
|
12173
12735
|
const db = new DbManager(dbPath);
|
|
12174
12736
|
await db.init();
|
|
12175
12737
|
for (const absPath of incrementalChangedFiles) {
|
|
12176
|
-
const rel =
|
|
12738
|
+
const rel = path33.relative(workspaceRoot, absPath);
|
|
12177
12739
|
await removeNodesForFile(rel, db);
|
|
12178
12740
|
}
|
|
12179
12741
|
const { upsertNodes: upsertNodesBatch } = await Promise.resolve().then(() => (init_graph_loader(), graph_loader_exports));
|
|
12180
12742
|
const changedRelPaths = new Set(
|
|
12181
|
-
incrementalChangedFiles.map((f) =>
|
|
12743
|
+
incrementalChangedFiles.map((f) => path33.relative(workspaceRoot, f))
|
|
12182
12744
|
);
|
|
12183
12745
|
const nodesToUpsert = [...graph.allNodes()].filter(
|
|
12184
12746
|
(n) => changedRelPaths.has(n.filePath)
|
|
@@ -12203,7 +12765,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12203
12765
|
mergedMtimes = newMtimes;
|
|
12204
12766
|
}
|
|
12205
12767
|
const currentCommitHash = getCurrentCommitHash(workspaceRoot) ?? void 0;
|
|
12206
|
-
const repoName =
|
|
12768
|
+
const repoName = path33.basename(workspaceRoot);
|
|
12207
12769
|
const indexVersion = v4();
|
|
12208
12770
|
saveMetadata(workspaceRoot, {
|
|
12209
12771
|
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12241,7 +12803,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12241
12803
|
];
|
|
12242
12804
|
for (const f of newStaleFiles) {
|
|
12243
12805
|
try {
|
|
12244
|
-
if (
|
|
12806
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
12245
12807
|
} catch {
|
|
12246
12808
|
}
|
|
12247
12809
|
}
|
|
@@ -12258,21 +12820,21 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12258
12820
|
];
|
|
12259
12821
|
for (const f of staleFiles) {
|
|
12260
12822
|
try {
|
|
12261
|
-
if (
|
|
12823
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
12262
12824
|
} catch {
|
|
12263
12825
|
}
|
|
12264
12826
|
}
|
|
12265
12827
|
for (const f of newStaleFiles) {
|
|
12266
12828
|
if (f === dbPathNew) continue;
|
|
12267
|
-
if (
|
|
12829
|
+
if (fs33.existsSync(f)) {
|
|
12268
12830
|
const dest = f.replace(dbPathNew, dbPath);
|
|
12269
12831
|
try {
|
|
12270
|
-
|
|
12832
|
+
fs33.renameSync(f, dest);
|
|
12271
12833
|
} catch {
|
|
12272
12834
|
}
|
|
12273
12835
|
}
|
|
12274
12836
|
}
|
|
12275
|
-
|
|
12837
|
+
fs33.renameSync(dbPathNew, dbPath);
|
|
12276
12838
|
stopSpinner();
|
|
12277
12839
|
logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
|
|
12278
12840
|
if (!options?.silent) {
|
|
@@ -12293,7 +12855,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12293
12855
|
const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
|
|
12294
12856
|
for (const f of staleVdb) {
|
|
12295
12857
|
try {
|
|
12296
|
-
if (
|
|
12858
|
+
if (fs33.existsSync(f)) fs33.unlinkSync(f);
|
|
12297
12859
|
} catch {
|
|
12298
12860
|
}
|
|
12299
12861
|
}
|
|
@@ -12363,6 +12925,32 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12363
12925
|
\u2705 Done in ${durStr} \u2014 ${graph.size.nodes} nodes \xB7 ${graph.size.edges} edges \xB7 ${context2.filePaths.length} files`);
|
|
12364
12926
|
}
|
|
12365
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
|
+
}
|
|
12366
12954
|
return { graph, result, repoName, workspaceRoot };
|
|
12367
12955
|
}
|
|
12368
12956
|
program.command("setup").description("Configure MCP server for your editors (one-time setup)").addHelpText("after", `
|
|
@@ -12391,8 +12979,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
12391
12979
|
const configFile = `${configDir}/claude_desktop_config.json`;
|
|
12392
12980
|
try {
|
|
12393
12981
|
let existing = {};
|
|
12394
|
-
if (
|
|
12395
|
-
existing = JSON.parse(
|
|
12982
|
+
if (fs33.existsSync(configFile)) {
|
|
12983
|
+
existing = JSON.parse(fs33.readFileSync(configFile, "utf-8"));
|
|
12396
12984
|
}
|
|
12397
12985
|
const merged = {
|
|
12398
12986
|
...existing,
|
|
@@ -12401,8 +12989,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
12401
12989
|
...mcpConfig.mcpServers
|
|
12402
12990
|
}
|
|
12403
12991
|
};
|
|
12404
|
-
|
|
12405
|
-
|
|
12992
|
+
fs33.mkdirSync(configDir, { recursive: true });
|
|
12993
|
+
fs33.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
12406
12994
|
console.log(`
|
|
12407
12995
|
\u2705 Written to ${configFile}`);
|
|
12408
12996
|
} catch (err) {
|
|
@@ -12416,7 +13004,7 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
12416
13004
|
console.log('\n To verify in VS Code: open Command Palette \u2192 "MCP: List Servers" and confirm code-intel is Running.');
|
|
12417
13005
|
console.log("\n Next: run `code-intel analyze` inside your project to build the knowledge graph.\n");
|
|
12418
13006
|
});
|
|
12419
|
-
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", `
|
|
12420
13008
|
Parses your source code with tree-sitter, builds a Knowledge Graph of
|
|
12421
13009
|
symbols and their relationships, persists it to .code-intel/graph.db,
|
|
12422
13010
|
and auto-generates AGENTS.md + CLAUDE.md context blocks.
|
|
@@ -12472,10 +13060,10 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
|
|
|
12472
13060
|
$ code-intel mcp
|
|
12473
13061
|
$ code-intel mcp ./my-project
|
|
12474
13062
|
`).action(async (targetPath) => {
|
|
12475
|
-
const workspaceRoot =
|
|
12476
|
-
const repoName =
|
|
13063
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13064
|
+
const repoName = path33.basename(workspaceRoot);
|
|
12477
13065
|
const dbPath = getDbPath(workspaceRoot);
|
|
12478
|
-
const existingIndex =
|
|
13066
|
+
const existingIndex = fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
12479
13067
|
if (existingIndex) {
|
|
12480
13068
|
const graph = createKnowledgeGraph();
|
|
12481
13069
|
const db = new DbManager(dbPath);
|
|
@@ -12506,10 +13094,10 @@ program.command("serve").description("Start the local HTTP server + web UI for g
|
|
|
12506
13094
|
$ code-intel serve --port 8080
|
|
12507
13095
|
$ code-intel serve --force
|
|
12508
13096
|
`).action(async (targetPath, options) => {
|
|
12509
|
-
const workspaceRoot =
|
|
12510
|
-
const repoName =
|
|
13097
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13098
|
+
const repoName = path33.basename(workspaceRoot);
|
|
12511
13099
|
const dbPath = getDbPath(workspaceRoot);
|
|
12512
|
-
const existingIndex = !options.force &&
|
|
13100
|
+
const existingIndex = !options.force && fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
12513
13101
|
if (existingIndex) {
|
|
12514
13102
|
const meta = loadMetadata(workspaceRoot);
|
|
12515
13103
|
if (meta.parser === "regex" || meta.parser === void 0) {
|
|
@@ -12543,10 +13131,10 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
12543
13131
|
`).action(async (targetPath, options) => {
|
|
12544
13132
|
const { FileWatcher: FileWatcher2 } = await Promise.resolve().then(() => (init_file_watcher(), file_watcher_exports));
|
|
12545
13133
|
const { IncrementalIndexer: IncrementalIndexer2 } = await Promise.resolve().then(() => (init_incremental_indexer(), incremental_indexer_exports));
|
|
12546
|
-
const workspaceRoot =
|
|
12547
|
-
const repoName =
|
|
13134
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13135
|
+
const repoName = path33.basename(workspaceRoot);
|
|
12548
13136
|
const dbPath = getDbPath(workspaceRoot);
|
|
12549
|
-
const existingIndex = !options.force &&
|
|
13137
|
+
const existingIndex = !options.force && fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
12550
13138
|
let graph;
|
|
12551
13139
|
if (existingIndex) {
|
|
12552
13140
|
const meta = loadMetadata(workspaceRoot);
|
|
@@ -12581,7 +13169,7 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
12581
13169
|
type: "graph:updated",
|
|
12582
13170
|
indexVersion: meta?.indexVersion ?? "unknown",
|
|
12583
13171
|
stats: { nodes: graph.size.nodes, edges: graph.size.edges },
|
|
12584
|
-
changedFiles: changedFiles.map((f) =>
|
|
13172
|
+
changedFiles: changedFiles.map((f) => path33.relative(workspaceRoot, f)),
|
|
12585
13173
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12586
13174
|
});
|
|
12587
13175
|
}
|
|
@@ -12632,7 +13220,7 @@ program.command("status").description("Show index freshness and statistics for a
|
|
|
12632
13220
|
$ code-intel status
|
|
12633
13221
|
$ code-intel status ./my-project
|
|
12634
13222
|
`).action((targetPath) => {
|
|
12635
|
-
const workspaceRoot =
|
|
13223
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
12636
13224
|
const meta = loadMetadata(workspaceRoot);
|
|
12637
13225
|
if (!meta) {
|
|
12638
13226
|
console.log(`
|
|
@@ -12656,18 +13244,18 @@ function trashDirName(repoPath) {
|
|
|
12656
13244
|
return `.code-intel-trash-${date}`;
|
|
12657
13245
|
}
|
|
12658
13246
|
function softDeleteCodeIntel(repoPath) {
|
|
12659
|
-
const codeIntelDir =
|
|
12660
|
-
if (!
|
|
13247
|
+
const codeIntelDir = path33.join(repoPath, ".code-intel");
|
|
13248
|
+
if (!fs33.existsSync(codeIntelDir)) return;
|
|
12661
13249
|
const trashName = trashDirName();
|
|
12662
|
-
const trashDir =
|
|
13250
|
+
const trashDir = path33.join(repoPath, trashName);
|
|
12663
13251
|
let dest = trashDir;
|
|
12664
13252
|
let counter = 1;
|
|
12665
|
-
while (
|
|
13253
|
+
while (fs33.existsSync(dest)) {
|
|
12666
13254
|
dest = `${trashDir}-${counter++}`;
|
|
12667
13255
|
}
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
13256
|
+
fs33.renameSync(codeIntelDir, dest);
|
|
13257
|
+
fs33.writeFileSync(
|
|
13258
|
+
path33.join(dest, "TRASH_META.json"),
|
|
12671
13259
|
JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
|
|
12672
13260
|
);
|
|
12673
13261
|
console.log(` \u2713 Moved to trash: ${dest}`);
|
|
@@ -12676,15 +13264,15 @@ function softDeleteCodeIntel(repoPath) {
|
|
|
12676
13264
|
function purgeStaleTrashes(repoPath) {
|
|
12677
13265
|
const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
|
|
12678
13266
|
try {
|
|
12679
|
-
for (const entry of
|
|
13267
|
+
for (const entry of fs33.readdirSync(repoPath)) {
|
|
12680
13268
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
12681
|
-
const fullPath =
|
|
12682
|
-
const metaPath =
|
|
12683
|
-
if (
|
|
13269
|
+
const fullPath = path33.join(repoPath, entry);
|
|
13270
|
+
const metaPath = path33.join(fullPath, "TRASH_META.json");
|
|
13271
|
+
if (fs33.existsSync(metaPath)) {
|
|
12684
13272
|
try {
|
|
12685
|
-
const meta = JSON.parse(
|
|
13273
|
+
const meta = JSON.parse(fs33.readFileSync(metaPath, "utf-8"));
|
|
12686
13274
|
if (new Date(meta.deletedAt).getTime() < cutoff) {
|
|
12687
|
-
|
|
13275
|
+
fs33.rmSync(fullPath, { recursive: true, force: true });
|
|
12688
13276
|
console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
|
|
12689
13277
|
}
|
|
12690
13278
|
} catch {
|
|
@@ -12711,18 +13299,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
12711
13299
|
if (opts.listTrash) {
|
|
12712
13300
|
const repos = loadRegistry();
|
|
12713
13301
|
const roots = repos.map((r) => r.path);
|
|
12714
|
-
if (roots.length === 0) roots.push(
|
|
13302
|
+
if (roots.length === 0) roots.push(path33.resolve("."));
|
|
12715
13303
|
let found = 0;
|
|
12716
13304
|
for (const root of roots) {
|
|
12717
13305
|
try {
|
|
12718
|
-
for (const entry of
|
|
13306
|
+
for (const entry of fs33.readdirSync(root)) {
|
|
12719
13307
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
12720
|
-
const fullPath =
|
|
12721
|
-
const metaPath =
|
|
13308
|
+
const fullPath = path33.join(root, entry);
|
|
13309
|
+
const metaPath = path33.join(fullPath, "TRASH_META.json");
|
|
12722
13310
|
let deletedAt = "unknown";
|
|
12723
|
-
if (
|
|
13311
|
+
if (fs33.existsSync(metaPath)) {
|
|
12724
13312
|
try {
|
|
12725
|
-
deletedAt = JSON.parse(
|
|
13313
|
+
deletedAt = JSON.parse(fs33.readFileSync(metaPath, "utf-8")).deletedAt;
|
|
12726
13314
|
} catch {
|
|
12727
13315
|
}
|
|
12728
13316
|
}
|
|
@@ -12750,9 +13338,9 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
12750
13338
|
}
|
|
12751
13339
|
for (const r of repos) {
|
|
12752
13340
|
if (opts.purge) {
|
|
12753
|
-
const codeIntelDir =
|
|
12754
|
-
if (
|
|
12755
|
-
|
|
13341
|
+
const codeIntelDir = path33.join(r.path, ".code-intel");
|
|
13342
|
+
if (fs33.existsSync(codeIntelDir)) {
|
|
13343
|
+
fs33.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
12756
13344
|
console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
|
|
12757
13345
|
}
|
|
12758
13346
|
} else {
|
|
@@ -12766,11 +13354,11 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
12766
13354
|
`);
|
|
12767
13355
|
return;
|
|
12768
13356
|
}
|
|
12769
|
-
const workspaceRoot =
|
|
13357
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
12770
13358
|
if (opts.purge) {
|
|
12771
|
-
const codeIntelDir =
|
|
12772
|
-
if (
|
|
12773
|
-
|
|
13359
|
+
const codeIntelDir = path33.join(workspaceRoot, ".code-intel");
|
|
13360
|
+
if (fs33.existsSync(codeIntelDir)) {
|
|
13361
|
+
fs33.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
12774
13362
|
console.log(`
|
|
12775
13363
|
\u2713 Hard-deleted ${codeIntelDir}`);
|
|
12776
13364
|
}
|
|
@@ -12782,16 +13370,16 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
12782
13370
|
console.log(" Index cleaned.\n");
|
|
12783
13371
|
});
|
|
12784
13372
|
async function loadOrAnalyzeWorkspace(targetPath) {
|
|
12785
|
-
const workspaceRoot =
|
|
13373
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
12786
13374
|
const dbPath = getDbPath(workspaceRoot);
|
|
12787
|
-
const existingIndex =
|
|
13375
|
+
const existingIndex = fs33.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
12788
13376
|
if (existingIndex) {
|
|
12789
13377
|
const graph = createKnowledgeGraph();
|
|
12790
13378
|
const db = new DbManager(dbPath);
|
|
12791
13379
|
await db.init();
|
|
12792
13380
|
await loadGraphFromDB(graph, db);
|
|
12793
13381
|
db.close();
|
|
12794
|
-
return { graph, workspaceRoot, repoName:
|
|
13382
|
+
return { graph, workspaceRoot, repoName: path33.basename(workspaceRoot) };
|
|
12795
13383
|
}
|
|
12796
13384
|
return analyzeWorkspace(targetPath, { silent: true });
|
|
12797
13385
|
}
|
|
@@ -13219,9 +13807,9 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
13219
13807
|
console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT IN REGISTRY`);
|
|
13220
13808
|
continue;
|
|
13221
13809
|
}
|
|
13222
|
-
const metaPath =
|
|
13810
|
+
const metaPath = path33.join(regEntry.path, ".code-intel", "meta.json");
|
|
13223
13811
|
try {
|
|
13224
|
-
const meta = JSON.parse(
|
|
13812
|
+
const meta = JSON.parse(fs33.readFileSync(metaPath, "utf-8"));
|
|
13225
13813
|
const indexedAt = meta.indexedAt;
|
|
13226
13814
|
const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
|
|
13227
13815
|
const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
|
|
@@ -13237,6 +13825,94 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
13237
13825
|
}
|
|
13238
13826
|
}
|
|
13239
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
|
+
});
|
|
13240
13916
|
var userCmd = program.command("user").description("Manage local user accounts");
|
|
13241
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", `
|
|
13242
13918
|
Examples:
|
|
@@ -13438,7 +14114,7 @@ backupCmd.command("create [path]").description("Create an encrypted backup of th
|
|
|
13438
14114
|
$ code-intel backup create
|
|
13439
14115
|
$ code-intel backup create ./my-project
|
|
13440
14116
|
`).action((targetPath = ".") => {
|
|
13441
|
-
const repoPath =
|
|
14117
|
+
const repoPath = path33.resolve(targetPath);
|
|
13442
14118
|
const svc = new BackupService();
|
|
13443
14119
|
try {
|
|
13444
14120
|
const entry = svc.createBackup(repoPath);
|
|
@@ -13467,7 +14143,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
|
|
|
13467
14143
|
Backups (${entries.length}):
|
|
13468
14144
|
`);
|
|
13469
14145
|
for (const e of entries) {
|
|
13470
|
-
const exists =
|
|
14146
|
+
const exists = fs33.existsSync(e.path);
|
|
13471
14147
|
const status = exists ? "\u2713" : "\u2717 (missing)";
|
|
13472
14148
|
console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
|
|
13473
14149
|
}
|
|
@@ -13480,7 +14156,7 @@ backupCmd.command("restore <id>").description("Restore a backup by ID").option("
|
|
|
13480
14156
|
`).action((id, opts) => {
|
|
13481
14157
|
const svc = new BackupService();
|
|
13482
14158
|
try {
|
|
13483
|
-
const targetPath = opts.target ?
|
|
14159
|
+
const targetPath = opts.target ? path33.resolve(opts.target) : void 0;
|
|
13484
14160
|
svc.restoreBackup(id, targetPath);
|
|
13485
14161
|
console.log(`
|
|
13486
14162
|
\u2705 Backup "${id}" restored successfully.
|
|
@@ -13499,8 +14175,8 @@ program.command("migrate").description("Manage database schema migrations").opti
|
|
|
13499
14175
|
$ code-intel migrate
|
|
13500
14176
|
$ code-intel migrate --rollback
|
|
13501
14177
|
`).action((opts) => {
|
|
13502
|
-
const dbPath = opts.db ??
|
|
13503
|
-
if (!
|
|
14178
|
+
const dbPath = opts.db ?? path33.join(os12.homedir(), ".code-intel", "users.db");
|
|
14179
|
+
if (!fs33.existsSync(dbPath)) {
|
|
13504
14180
|
console.error(`
|
|
13505
14181
|
\u2717 Database not found: ${dbPath}
|
|
13506
14182
|
Run \`code-intel serve\` or \`code-intel user create\` first.
|
|
@@ -13615,15 +14291,15 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
13615
14291
|
}
|
|
13616
14292
|
try {
|
|
13617
14293
|
const tokens = await pollDeviceFlow3(config, deviceResponse);
|
|
13618
|
-
const tokenPath =
|
|
14294
|
+
const tokenPath = path33.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
13619
14295
|
const tokenData = {
|
|
13620
14296
|
accessToken: tokens.accessToken,
|
|
13621
14297
|
refreshToken: tokens.refreshToken,
|
|
13622
14298
|
server: serverUrl,
|
|
13623
14299
|
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13624
14300
|
};
|
|
13625
|
-
|
|
13626
|
-
|
|
14301
|
+
fs33.mkdirSync(path33.dirname(tokenPath), { recursive: true });
|
|
14302
|
+
fs33.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
|
|
13627
14303
|
console.log(` \u2705 Authenticated successfully!`);
|
|
13628
14304
|
console.log(` Token stored at: ${tokenPath}`);
|
|
13629
14305
|
console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
|
|
@@ -13636,13 +14312,13 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
13636
14312
|
}
|
|
13637
14313
|
});
|
|
13638
14314
|
authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
|
|
13639
|
-
const tokenPath =
|
|
13640
|
-
if (!
|
|
14315
|
+
const tokenPath = path33.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
14316
|
+
if (!fs33.existsSync(tokenPath)) {
|
|
13641
14317
|
console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
|
|
13642
14318
|
return;
|
|
13643
14319
|
}
|
|
13644
14320
|
try {
|
|
13645
|
-
const data = JSON.parse(
|
|
14321
|
+
const data = JSON.parse(fs33.readFileSync(tokenPath, "utf-8"));
|
|
13646
14322
|
console.log(`
|
|
13647
14323
|
\u2705 OIDC token stored`);
|
|
13648
14324
|
console.log(` Server : ${data.server ?? "unknown"}`);
|
|
@@ -13654,9 +14330,9 @@ authCmd.command("status").description("Show the current OIDC authentication stat
|
|
|
13654
14330
|
}
|
|
13655
14331
|
});
|
|
13656
14332
|
authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
|
|
13657
|
-
const tokenPath =
|
|
13658
|
-
if (
|
|
13659
|
-
|
|
14333
|
+
const tokenPath = path33.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
14334
|
+
if (fs33.existsSync(tokenPath)) {
|
|
14335
|
+
fs33.unlinkSync(tokenPath);
|
|
13660
14336
|
console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
|
|
13661
14337
|
} else {
|
|
13662
14338
|
console.log("\n No stored token found.\n");
|
|
@@ -13780,8 +14456,8 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
13780
14456
|
$ code-intel config-validate ./config.json
|
|
13781
14457
|
$ code-intel config-validate ~/.code-intel/config.json
|
|
13782
14458
|
`).action((file) => {
|
|
13783
|
-
const filePath =
|
|
13784
|
-
if (!
|
|
14459
|
+
const filePath = path33.resolve(file);
|
|
14460
|
+
if (!fs33.existsSync(filePath)) {
|
|
13785
14461
|
console.error(`
|
|
13786
14462
|
\u2717 File not found: ${filePath}
|
|
13787
14463
|
`);
|
|
@@ -13789,7 +14465,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
13789
14465
|
}
|
|
13790
14466
|
let cfg;
|
|
13791
14467
|
try {
|
|
13792
|
-
cfg = JSON.parse(
|
|
14468
|
+
cfg = JSON.parse(fs33.readFileSync(filePath, "utf-8"));
|
|
13793
14469
|
} catch (err) {
|
|
13794
14470
|
console.error(`
|
|
13795
14471
|
\u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
|
|
@@ -13810,7 +14486,7 @@ ${err instanceof Error ? err.message : err}
|
|
|
13810
14486
|
});
|
|
13811
14487
|
(function ensurePermissions() {
|
|
13812
14488
|
try {
|
|
13813
|
-
const dir =
|
|
14489
|
+
const dir = path33.join(os12.homedir(), ".code-intel");
|
|
13814
14490
|
secureMkdir(dir);
|
|
13815
14491
|
tightenDbFiles(dir);
|
|
13816
14492
|
} catch {
|
|
@@ -13827,10 +14503,10 @@ program.command("health").description("Run code health checks: dead code, circul
|
|
|
13827
14503
|
$ code-intel health --json
|
|
13828
14504
|
$ code-intel health --threshold 80
|
|
13829
14505
|
`).action(async (targetPath, opts) => {
|
|
13830
|
-
const workspaceRoot =
|
|
14506
|
+
const workspaceRoot = path33.resolve(targetPath);
|
|
13831
14507
|
const dbPath = getDbPath(workspaceRoot);
|
|
13832
14508
|
const meta = loadMetadata(workspaceRoot);
|
|
13833
|
-
if (!meta || !
|
|
14509
|
+
if (!meta || !fs33.existsSync(dbPath)) {
|
|
13834
14510
|
console.error(`
|
|
13835
14511
|
\u2717 ${workspaceRoot} is not indexed.`);
|
|
13836
14512
|
console.error(" Run `code-intel analyze` first to build the index.\n");
|
|
@@ -13915,7 +14591,7 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
13915
14591
|
$ code-intel query --list
|
|
13916
14592
|
$ code-intel query --delete auth-search
|
|
13917
14593
|
`).action(async (gqlArg, opts) => {
|
|
13918
|
-
const workspaceRoot =
|
|
14594
|
+
const workspaceRoot = path33.resolve(opts.path);
|
|
13919
14595
|
const { saveQuery: saveQuery2, loadQuery: loadQuery2, listQueries: listQueries2, deleteQuery: deleteQuery2 } = await Promise.resolve().then(() => (init_saved_queries(), saved_queries_exports));
|
|
13920
14596
|
if (opts.list) {
|
|
13921
14597
|
const queries = listQueries2(workspaceRoot);
|
|
@@ -13974,14 +14650,14 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
13974
14650
|
}
|
|
13975
14651
|
gqlInput = content;
|
|
13976
14652
|
} else if (opts.file) {
|
|
13977
|
-
const filePath =
|
|
13978
|
-
if (!
|
|
14653
|
+
const filePath = path33.resolve(opts.file);
|
|
14654
|
+
if (!fs33.existsSync(filePath)) {
|
|
13979
14655
|
console.error(`
|
|
13980
14656
|
\u2717 File not found: ${filePath}
|
|
13981
14657
|
`);
|
|
13982
14658
|
process.exit(1);
|
|
13983
14659
|
}
|
|
13984
|
-
gqlInput =
|
|
14660
|
+
gqlInput = fs33.readFileSync(filePath, "utf-8");
|
|
13985
14661
|
} else if (gqlArg) {
|
|
13986
14662
|
gqlInput = gqlArg;
|
|
13987
14663
|
} else {
|
|
@@ -14095,6 +14771,101 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
14095
14771
|
`);
|
|
14096
14772
|
}
|
|
14097
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
|
+
});
|
|
14098
14869
|
program.parse();
|
|
14099
14870
|
//# sourceMappingURL=main.js.map
|
|
14100
14871
|
//# sourceMappingURL=main.js.map
|