@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/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 fs28, { readFileSync, existsSync } from 'fs';
11
- import path30, { dirname, join } from 'path';
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 = path30.join(os12.homedir(), ".code-intel", "logs");
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 (!fs28.existsSync(_Logger.LOG_DIR)) {
339
- fs28.mkdirSync(_Logger.LOG_DIR, { recursive: true });
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: path30.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
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, path31) {
543
+ function dfs(name, path34) {
419
544
  if (visiting.has(name)) {
420
- const cycleStart = path31.indexOf(name);
421
- const cycle = path31.slice(cycleStart).concat(name);
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
- path31.push(name);
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, path31)) return true;
556
+ if (dfs(dep, path34)) return true;
432
557
  }
433
558
  }
434
559
  visiting.delete(name);
435
560
  visited.add(name);
436
- path31.pop();
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 = path30.dirname(fileURLToPath(import.meta.url));
821
+ const fileDir = path33.dirname(fileURLToPath(import.meta.url));
697
822
  const candidates = [
698
- path30.join(fileDir, "wasm"),
823
+ path33.join(fileDir, "wasm"),
699
824
  // dist/index.js → dist/wasm/
700
- path30.join(fileDir, "../wasm")
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 = path30.join(_bundledWasmDir, bundled);
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 path31 = wasmPath(lang);
755
- if (!path31) {
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(path31);
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 fs28.promises.readFile(filePath, "utf-8");
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 = path30.relative(context2.workspaceRoot, filePath);
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 = path30.relative(context2.workspaceRoot, filePath);
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 = path30.relative(workspaceRoot, fp);
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 = path30.basename(rel, path30.extname(rel));
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 = path30.relative(workspaceRoot, filePath);
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 = path30.dirname(relativePath);
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 = path30.join(fromDir, cleanedNoJs + ext);
2503
- const normalized = path30.normalize(candidate);
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 = path30.relative(workspaceRoot, absPath);
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
- fs28.mkdirSync(path30.dirname(this.dbPath), { recursive: true });
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
- fs28.mkdirSync(outputDir, { recursive: true });
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, path30.join(outputDir, `${table}.csv`));
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
- fs28.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
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
- fs28.mkdirSync(outputDir, { recursive: true });
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 = path30.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
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
- fs28.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
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 = fs28.mkdtempSync(path30.join(os12.tmpdir(), "code-intel-csv-"));
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 (!fs28.existsSync(csvPath)) continue;
3190
- const stat = fs28.statSync(csvPath);
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 (!fs28.existsSync(group.filePath)) continue;
3204
- const stat = fs28.statSync(group.filePath);
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
- fs28.rmSync(tmpDir, { recursive: true, force: true });
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 = fs28.readFileSync(REPOS_FILE, "utf-8");
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
- fs28.mkdirSync(GLOBAL_DIR, { recursive: true });
3328
- fs28.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
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 = path30.join(os12.homedir(), ".code-intel");
3348
- REPOS_FILE = path30.join(GLOBAL_DIR, "repos.json");
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 = path30.join(repoDir, ".code-intel");
3353
- fs28.mkdirSync(metaDir, { recursive: true });
3354
- fs28.writeFileSync(path30.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
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 = fs28.readFileSync(path30.join(repoDir, ".code-intel", "meta.json"), "utf-8");
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 path30.join(repoDir, ".code-intel", "graph.db");
3490
+ return path33.join(repoDir, ".code-intel", "graph.db");
3366
3491
  }
3367
3492
  function getVectorDbPath(repoDir) {
3368
- return path30.join(repoDir, ".code-intel", "vector.db");
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 path30.join(GROUPS_DIR, `${name}.json`);
3549
+ return path33.join(GROUPS_DIR, `${name}.json`);
3425
3550
  }
3426
3551
  function loadGroup(name) {
3427
3552
  try {
3428
- return JSON.parse(fs28.readFileSync(groupFile(name), "utf-8"));
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
- fs28.mkdirSync(GROUPS_DIR, { recursive: true });
3435
- fs28.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
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 fs28.readdirSync(GROUPS_DIR)) {
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
- fs28.readFileSync(path30.join(GROUPS_DIR, file), "utf-8")
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
- fs28.unlinkSync(groupFile(name));
3581
+ fs33.unlinkSync(groupFile(name));
3457
3582
  } catch {
3458
3583
  }
3459
3584
  try {
3460
- fs28.unlinkSync(path30.join(GROUPS_DIR, `${name}.sync.json`));
3585
+ fs33.unlinkSync(path33.join(GROUPS_DIR, `${name}.sync.json`));
3461
3586
  } catch {
3462
3587
  }
3463
3588
  }
3464
3589
  function groupExists(name) {
3465
- return fs28.existsSync(groupFile(name));
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
- fs28.mkdirSync(GROUPS_DIR, { recursive: true });
3492
- fs28.writeFileSync(
3493
- path30.join(GROUPS_DIR, `${result.groupName}.sync.json`),
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
- fs28.readFileSync(path30.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
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 = path30.join(os12.homedir(), ".code-intel", "groups");
3634
+ GROUPS_DIR = path33.join(os12.homedir(), ".code-intel", "groups");
3510
3635
  }
3511
3636
  });
3512
3637
 
3513
- // src/errors/codes.ts
3514
- var ErrorCodes, AppError;
3515
- var init_codes = __esm({
3516
- "src/errors/codes.ts"() {
3517
- ErrorCodes = {
3518
- UNAUTHORIZED: "CI-1000",
3519
- FORBIDDEN: "CI-1001",
3520
- NOT_FOUND: "CI-1002",
3521
- ANALYSIS_IN_PROGRESS: "CI-1003",
3522
- INDEX_NOT_FOUND: "CI-1004",
3523
- DB_CORRUPTED: "CI-1042",
3524
- RATE_LIMIT_EXCEEDED: "CI-1100",
3525
- PAYLOAD_TOO_LARGE: "CI-1101",
3526
- INVALID_REQUEST: "CI-1200",
3527
- INTERNAL_ERROR: "CI-5000"
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
- code;
3539
- hint;
3540
- statusCode;
3541
- docs;
3542
- };
3543
- }
3544
- });
3545
- function secureMkdir(dir) {
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
- fs28.chmodSync(dir, SECURE_DIR_MODE);
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
- fs28.chmodSync(file, SECURE_FILE_MODE);
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
- function secureWriteFile(file, data) {
3562
- secureMkdir(path30.dirname(file));
3563
- fs28.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
3564
- secureChmodFile(file);
3565
- }
3566
- function tightenDbFiles(dir) {
3567
- if (process.platform === "win32") return;
3568
- if (!fs28.existsSync(dir)) return;
3569
- for (const name of fs28.readdirSync(dir)) {
3570
- if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
3571
- try {
3572
- fs28.chmodSync(path30.join(dir, name), SECURE_FILE_MODE);
3573
- } catch {
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 SECURE_DIR_MODE, SECURE_FILE_MODE;
3579
- var init_fs_secure = __esm({
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 getUsersDBPath() {
3586
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path30.join(os12.homedir(), ".code-intel", "users.db");
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 getOrCreateUsersDB() {
3589
- if (!_usersDB) {
3590
- _usersDB = new UsersDB(getUsersDBPath());
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 _usersDB;
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 = path30.dirname(dbPath);
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"] ?? path30.join(os12.homedir(), ".code-intel", ".secrets");
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 (!fs28.existsSync(secretsPath)) return {};
3922
- const blob = fs28.readFileSync(secretsPath);
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(path30.dirname(secretsPath));
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
- if (!roles.includes(req.user.role)) {
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 = path30.join(this.workspaceRoot, ".codeintelignore");
6379
+ const ignoreFile = path33.join(this.workspaceRoot, ".codeintelignore");
5627
6380
  try {
5628
- if (!fs28.existsSync(ignoreFile)) return [];
5629
- return fs28.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
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 = path30.relative(workspaceRoot, absPath);
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 (fs28.existsSync(dbPath)) {
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 = path30.relative(workspaceRoot, absPath);
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 fs28.statSync(f).isFile();
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 (fs28.existsSync(dbPath) && existingFiles.length > 0) {
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) => path30.relative(workspaceRoot, 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(path30.relative(workspaceRoot, n.filePath))
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 path30.join(workspaceRoot, ".code-intel", "queries");
6516
+ return path33.join(workspaceRoot, ".code-intel", "queries");
5764
6517
  }
5765
6518
  function ensureQueriesDir(workspaceRoot) {
5766
6519
  const dir = getQueriesDir(workspaceRoot);
5767
- if (!fs28.existsSync(dir)) {
5768
- fs28.mkdirSync(dir, { recursive: true });
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 = path30.join(dir, `${name}.gql`);
5775
- fs28.writeFileSync(filePath, gql, "utf-8");
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 = path30.join(dir, `${name}.gql`);
5780
- if (!fs28.existsSync(filePath)) return null;
5781
- return fs28.readFileSync(filePath, "utf-8");
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 (!fs28.existsSync(dir)) return [];
5786
- const files = fs28.readdirSync(dir).filter((f) => f.endsWith(".gql"));
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 = path30.join(dir, f);
6541
+ const filePath = path33.join(dir, f);
5789
6542
  const name = f.replace(/\.gql$/, "");
5790
- const content = fs28.readFileSync(filePath, "utf-8");
5791
- const stat = fs28.statSync(filePath);
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 = path30.join(dir, `${name}.gql`);
5803
- if (!fs28.existsSync(filePath)) return false;
5804
- fs28.unlinkSync(filePath);
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 = path30.join(dir, `${name}.gql`);
5810
- return fs28.existsSync(filePath);
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/main.ts
5818
- init_logger();
5819
-
5820
- // src/graph/knowledge-graph.ts
5821
- function createKnowledgeGraph() {
5822
- const nodes = /* @__PURE__ */ new Map();
5823
- const edges = /* @__PURE__ */ new Map();
5824
- const edgesByKind = /* @__PURE__ */ new Map();
5825
- const edgesFromNode = /* @__PURE__ */ new Map();
5826
- const edgesToNode = /* @__PURE__ */ new Map();
5827
- function indexEdge(edge) {
5828
- let kindSet = edgesByKind.get(edge.kind);
5829
- if (!kindSet) {
5830
- kindSet = /* @__PURE__ */ new Set();
5831
- edgesByKind.set(edge.kind, kindSet);
5832
- }
5833
- kindSet.add(edge.id);
5834
- let fromSet = edgesFromNode.get(edge.source);
5835
- if (!fromSet) {
5836
- fromSet = /* @__PURE__ */ new Set();
5837
- edgesFromNode.set(edge.source, fromSet);
5838
- }
5839
- fromSet.add(edge.id);
5840
- let toSet = edgesToNode.get(edge.target);
5841
- if (!toSet) {
5842
- toSet = /* @__PURE__ */ new Set();
5843
- edgesToNode.set(edge.target, toSet);
5844
- }
5845
- toSet.add(edge.id);
5846
- }
5847
- function unindexEdge(edge) {
5848
- edgesByKind.get(edge.kind)?.delete(edge.id);
5849
- edgesFromNode.get(edge.source)?.delete(edge.id);
5850
- edgesToNode.get(edge.target)?.delete(edge.id);
5851
- }
5852
- return {
5853
- addNode(node) {
5854
- nodes.set(node.id, node);
5855
- },
5856
- addEdge(edge) {
5857
- edges.set(edge.id, edge);
5858
- indexEdge(edge);
5859
- },
5860
- getNode(id) {
5861
- return nodes.get(id);
5862
- },
5863
- getEdge(id) {
5864
- return edges.get(id);
5865
- },
5866
- *findEdgesByKind(kind) {
5867
- const ids = edgesByKind.get(kind);
5868
- if (!ids) return;
5869
- for (const id of ids) {
5870
- const edge = edges.get(id);
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 = fs28.readFileSync(path30.join(workspaceRoot, ".codeintelignore"), "utf-8");
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 = fs28.readdirSync(dir, { withFileTypes: true });
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(path30.join(dir, entry.name));
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 = path30.extname(name);
6709
+ const ext = path33.extname(name);
6018
6710
  if (!extensions.has(ext)) continue;
6019
- const fullPath = path30.join(dir, name);
6711
+ const fullPath = path33.join(dir, name);
6020
6712
  try {
6021
- const stat = fs28.statSync(fullPath);
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 = path30.relative(context2.workspaceRoot, filePath);
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: path30.basename(filePath),
6745
+ name: path33.basename(filePath),
6054
6746
  filePath: relativePath,
6055
6747
  metadata: lang ? { language: lang } : void 0
6056
6748
  });
6057
- let dir = path30.dirname(relativePath);
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 = path30.dirname(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: path30.basename(dir),
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: path31 } = queue.shift();
6182
- if (path31.length > maxDepth) continue;
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 && path31.length >= 3) {
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: path31, entryPoint: ep.name }
6883
+ metadata: { steps: path34, entryPoint: ep.name }
6192
6884
  });
6193
- for (let i = 0; i < path31.length; i++) {
6885
+ for (let i = 0; i < path34.length; i++) {
6194
6886
  graph.addEdge({
6195
- id: generateEdgeId(path31[i], flowId, `step_of_${i}`),
6196
- source: path31[i],
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: [...path31, edge.target] });
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"] ?? path30.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
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
- fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
6244
- fs28.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
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 = fs28.readFileSync(this.getLogPath(), "utf-8");
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 path30.join(path30.dirname(thisFile), "parse-worker.js");
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 fs28.promises.readFile(filePath, "utf-8");
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 = fs28.existsSync(workerScript);
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 = path30.relative(context2.workspaceRoot, filePath);
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 = path30.relative(context2.workspaceRoot, res.taskId);
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 path30.join(path30.dirname(thisFile), "resolve-worker.js");
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 = path30.relative(workspaceRoot, fp);
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 = path30.basename(rel, path30.extname(rel));
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 = fs28.existsSync(workerScript);
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 = path30.relative(workspaceRoot, filePath);
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 && fs28.existsSync(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 = path30.join(regEntry.path, ".code-intel", "graph.db");
7132
- if (!fs28.existsSync(dbPath)) continue;
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
- fs28.mkdirSync(path30.dirname(dbPath), { recursive: true });
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 path30.join(os12.homedir(), ".code-intel", "jobs.db");
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 path30.join(os12.homedir(), ".code-intel", "backups");
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
- fs28.mkdirSync(this.backupDir, { recursive: true });
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 = path30.join(repoPath, ".code-intel");
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 = path30.join(codeIntelDir, f);
7451
- if (fs28.existsSync(fp)) {
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 = path30.join(os12.homedir(), ".code-intel", "registry.json");
7456
- if (fs28.existsSync(registryPath)) {
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 = path30.join(os12.homedir(), ".code-intel", "users.db");
7460
- if (fs28.existsSync(usersDbPath)) {
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 = fs28.readFileSync(f.localPath);
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 = fs28.readFileSync(f.localPath);
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 = path30.join(this.backupDir, backupFileName);
7497
- fs28.writeFileSync(backupPath, encrypted);
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 = path30.basename(entry.path);
7993
+ const fileName = path33.basename(entry.path);
7525
7994
  const s3Key = `${cfg.prefix}${fileName}`;
7526
- const body = fs28.readFileSync(entry.path);
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
- fs28.mkdirSync(path30.dirname(destPath), { recursive: true });
7544
- fs28.writeFileSync(destPath, Buffer.from(result.body, "binary"));
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 (!fs28.existsSync(entry.path)) {
8059
+ if (!fs33.existsSync(entry.path)) {
7591
8060
  throw new Error(`Backup file not found at: ${entry.path}`);
7592
8061
  }
7593
- const encrypted = fs28.readFileSync(entry.path);
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 = path30.join(restoreBase, ".code-intel");
7608
- fs28.mkdirSync(codeIntelDir, { recursive: true });
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 = path30.join(os12.homedir(), ".code-intel", name);
8094
+ destPath = path33.join(os12.homedir(), ".code-intel", name);
7626
8095
  } else {
7627
- destPath = path30.join(codeIntelDir, name);
8096
+ destPath = path33.join(codeIntelDir, name);
7628
8097
  }
7629
- fs28.writeFileSync(destPath, data);
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) => fs28.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
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
- fs28.unlinkSync(e.path);
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 path30.join(this.backupDir, "index.json");
8147
+ return path33.join(this.backupDir, "index.json");
7679
8148
  }
7680
8149
  _loadIndex() {
7681
8150
  try {
7682
- return JSON.parse(fs28.readFileSync(this._indexPath(), "utf-8"));
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
- fs28.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
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 = path30.dirname(fileURLToPath(import.meta.url));
8798
+ var __dirname$1 = path33.dirname(fileURLToPath(import.meta.url));
8306
8799
  var WEB_DIST = (() => {
8307
- const bundled = path30.resolve(__dirname$1, "..", "web");
8308
- if (fs28.existsSync(bundled)) return bundled;
8309
- return path30.resolve(__dirname$1, "..", "..", "..", "web", "dist");
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 = path30.join(entry.path, ".code-intel", "graph.db");
8841
- if (!fs28.existsSync(dbPath)) return null;
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 = fs28.readFileSync(file_path, "utf-8");
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 = path30.join(regEntry.path, ".code-intel", "graph.db");
9187
- if (!fs28.existsSync(dbPath)) continue;
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 = path30.normalize(file);
9225
- if (!path30.isAbsolute(rawResolved) && workspaceRoot) {
9226
- rawResolved = path30.join(workspaceRoot, rawResolved);
9756
+ let rawResolved = path33.normalize(file);
9757
+ if (!path33.isAbsolute(rawResolved) && workspaceRoot) {
9758
+ rawResolved = path33.join(workspaceRoot, rawResolved);
9227
9759
  }
9228
- const resolvedFile = path30.resolve(rawResolved);
9760
+ const resolvedFile = path33.resolve(rawResolved);
9229
9761
  function isInsideDir(fileAbs, dir) {
9230
- const rel = path30.relative(path30.resolve(dir), fileAbs);
9231
- return !rel.startsWith("..") && !path30.isAbsolute(rel);
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 = fs28.readFileSync(resolvedFile, "utf-8");
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 = path30.extname(resolvedFile).toLowerCase();
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 (fs28.existsSync(WEB_DIST)) {
9966
+ if (fs33.existsSync(WEB_DIST)) {
9435
9967
  app.use(express.static(WEB_DIST));
9436
9968
  app.get("/{*path}", (_req, res) => {
9437
- res.sendFile(path30.join(WEB_DIST, "index.html"));
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/query/pr-impact.ts
9674
- function parseDiffFiles(diff) {
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: path31, depth } = pathQueue.shift();
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, ...path31];
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 && path31.length > 1) {
10079
- callPaths.push(path31);
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 + path30.sep, "");
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 = path30.join(workspaceRoot, ".claude", "skills", "code-intel");
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
- fs28.rmSync(outputDir, { recursive: true, force: true });
11340
- fs28.mkdirSync(outputDir, { recursive: true });
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 = path30.join(outputDir, kebab);
11348
- fs28.mkdirSync(dir, { recursive: true });
11349
- fs28.writeFileSync(path30.join(dir, "SKILL.md"), content, "utf-8");
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(path30.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
11530
- upsertFile(path30.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
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 (!fs28.existsSync(filePath)) {
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
- fs28.writeFileSync(filePath, newContent, "utf-8");
12029
+ fs33.writeFileSync(filePath, newContent, "utf-8");
11596
12030
  return;
11597
12031
  }
11598
- const existing = fs28.readFileSync(filePath, "utf-8");
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
- fs28.writeFileSync(filePath, updated, "utf-8");
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
- fs28.writeFileSync(filePath, appended, "utf-8");
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 = path30.relative(workspaceRoot, absPath);
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 } = fs28.statSync(absPath);
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 } = fs28.statSync(absPath);
11680
- snap[path30.relative(workspaceRoot, absPath)] = mtimeMs;
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) => path30.relative(workspaceRoot, p)));
11692
- const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path30.join(workspaceRoot, 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 || !fs28.existsSync(dbFile)) return;
11807
- const backupDir = path30.join(os12.homedir(), ".code-intel", "backups", "pre-migration");
11808
- fs28.mkdirSync(backupDir, { recursive: true });
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 = path30.basename(dbFile, ".db");
11811
- const backupPath = path30.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
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
- fs28.copyFileSync(dbFile, backupPath);
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 = path30.resolve(targetPath);
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 (fs28.existsSync(f)) fs28.unlinkSync(f);
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 = path30.join(workspaceRoot, ".git");
12060
- if (!fs28.existsSync(gitDir)) {
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 (fs28.existsSync(dbPath)) {
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 = path30.relative(workspaceRoot, absPath);
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) => path30.relative(workspaceRoot, 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 = path30.basename(workspaceRoot);
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 (fs28.existsSync(f)) fs28.unlinkSync(f);
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 (fs28.existsSync(f)) fs28.unlinkSync(f);
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 (fs28.existsSync(f)) {
12829
+ if (fs33.existsSync(f)) {
12268
12830
  const dest = f.replace(dbPathNew, dbPath);
12269
12831
  try {
12270
- fs28.renameSync(f, dest);
12832
+ fs33.renameSync(f, dest);
12271
12833
  } catch {
12272
12834
  }
12273
12835
  }
12274
12836
  }
12275
- fs28.renameSync(dbPathNew, dbPath);
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 (fs28.existsSync(f)) fs28.unlinkSync(f);
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 (fs28.existsSync(configFile)) {
12395
- existing = JSON.parse(fs28.readFileSync(configFile, "utf-8"));
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
- fs28.mkdirSync(configDir, { recursive: true });
12405
- fs28.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
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 = path30.resolve(targetPath);
12476
- const repoName = path30.basename(workspaceRoot);
13063
+ const workspaceRoot = path33.resolve(targetPath);
13064
+ const repoName = path33.basename(workspaceRoot);
12477
13065
  const dbPath = getDbPath(workspaceRoot);
12478
- const existingIndex = fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
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 = path30.resolve(targetPath);
12510
- const repoName = path30.basename(workspaceRoot);
13097
+ const workspaceRoot = path33.resolve(targetPath);
13098
+ const repoName = path33.basename(workspaceRoot);
12511
13099
  const dbPath = getDbPath(workspaceRoot);
12512
- const existingIndex = !options.force && fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
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 = path30.resolve(targetPath);
12547
- const repoName = path30.basename(workspaceRoot);
13134
+ const workspaceRoot = path33.resolve(targetPath);
13135
+ const repoName = path33.basename(workspaceRoot);
12548
13136
  const dbPath = getDbPath(workspaceRoot);
12549
- const existingIndex = !options.force && fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
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) => path30.relative(workspaceRoot, 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 = path30.resolve(targetPath);
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 = path30.join(repoPath, ".code-intel");
12660
- if (!fs28.existsSync(codeIntelDir)) return;
13247
+ const codeIntelDir = path33.join(repoPath, ".code-intel");
13248
+ if (!fs33.existsSync(codeIntelDir)) return;
12661
13249
  const trashName = trashDirName();
12662
- const trashDir = path30.join(repoPath, trashName);
13250
+ const trashDir = path33.join(repoPath, trashName);
12663
13251
  let dest = trashDir;
12664
13252
  let counter = 1;
12665
- while (fs28.existsSync(dest)) {
13253
+ while (fs33.existsSync(dest)) {
12666
13254
  dest = `${trashDir}-${counter++}`;
12667
13255
  }
12668
- fs28.renameSync(codeIntelDir, dest);
12669
- fs28.writeFileSync(
12670
- path30.join(dest, "TRASH_META.json"),
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 fs28.readdirSync(repoPath)) {
13267
+ for (const entry of fs33.readdirSync(repoPath)) {
12680
13268
  if (!entry.startsWith(".code-intel-trash-")) continue;
12681
- const fullPath = path30.join(repoPath, entry);
12682
- const metaPath = path30.join(fullPath, "TRASH_META.json");
12683
- if (fs28.existsSync(metaPath)) {
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(fs28.readFileSync(metaPath, "utf-8"));
13273
+ const meta = JSON.parse(fs33.readFileSync(metaPath, "utf-8"));
12686
13274
  if (new Date(meta.deletedAt).getTime() < cutoff) {
12687
- fs28.rmSync(fullPath, { recursive: true, force: true });
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(path30.resolve("."));
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 fs28.readdirSync(root)) {
13306
+ for (const entry of fs33.readdirSync(root)) {
12719
13307
  if (!entry.startsWith(".code-intel-trash-")) continue;
12720
- const fullPath = path30.join(root, entry);
12721
- const metaPath = path30.join(fullPath, "TRASH_META.json");
13308
+ const fullPath = path33.join(root, entry);
13309
+ const metaPath = path33.join(fullPath, "TRASH_META.json");
12722
13310
  let deletedAt = "unknown";
12723
- if (fs28.existsSync(metaPath)) {
13311
+ if (fs33.existsSync(metaPath)) {
12724
13312
  try {
12725
- deletedAt = JSON.parse(fs28.readFileSync(metaPath, "utf-8")).deletedAt;
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 = path30.join(r.path, ".code-intel");
12754
- if (fs28.existsSync(codeIntelDir)) {
12755
- fs28.rmSync(codeIntelDir, { recursive: true, force: true });
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 = path30.resolve(targetPath);
13357
+ const workspaceRoot = path33.resolve(targetPath);
12770
13358
  if (opts.purge) {
12771
- const codeIntelDir = path30.join(workspaceRoot, ".code-intel");
12772
- if (fs28.existsSync(codeIntelDir)) {
12773
- fs28.rmSync(codeIntelDir, { recursive: true, force: true });
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 = path30.resolve(targetPath);
13373
+ const workspaceRoot = path33.resolve(targetPath);
12786
13374
  const dbPath = getDbPath(workspaceRoot);
12787
- const existingIndex = fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
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: path30.basename(workspaceRoot) };
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 = path30.join(regEntry.path, ".code-intel", "meta.json");
13810
+ const metaPath = path33.join(regEntry.path, ".code-intel", "meta.json");
13223
13811
  try {
13224
- const meta = JSON.parse(fs28.readFileSync(metaPath, "utf-8"));
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 = path30.resolve(targetPath);
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 = fs28.existsSync(e.path);
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 ? path30.resolve(opts.target) : void 0;
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 ?? path30.join(os12.homedir(), ".code-intel", "users.db");
13503
- if (!fs28.existsSync(dbPath)) {
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 = path30.join(os12.homedir(), ".code-intel", "oidc-token.json");
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
- fs28.mkdirSync(path30.dirname(tokenPath), { recursive: true });
13626
- fs28.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
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 = path30.join(os12.homedir(), ".code-intel", "oidc-token.json");
13640
- if (!fs28.existsSync(tokenPath)) {
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(fs28.readFileSync(tokenPath, "utf-8"));
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 = path30.join(os12.homedir(), ".code-intel", "oidc-token.json");
13658
- if (fs28.existsSync(tokenPath)) {
13659
- fs28.unlinkSync(tokenPath);
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 = path30.resolve(file);
13784
- if (!fs28.existsSync(filePath)) {
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(fs28.readFileSync(filePath, "utf-8"));
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 = path30.join(os12.homedir(), ".code-intel");
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 = path30.resolve(targetPath);
14506
+ const workspaceRoot = path33.resolve(targetPath);
13831
14507
  const dbPath = getDbPath(workspaceRoot);
13832
14508
  const meta = loadMetadata(workspaceRoot);
13833
- if (!meta || !fs28.existsSync(dbPath)) {
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 = path30.resolve(opts.path);
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 = path30.resolve(opts.file);
13978
- if (!fs28.existsSync(filePath)) {
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 = fs28.readFileSync(filePath, "utf-8");
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