@vohongtho.infotech/code-intel 0.6.0 → 0.8.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'module';
2
2
  import { fileURLToPath } from 'url';
3
- import path27 from 'path';
4
- import fs19, { existsSync } from 'fs';
3
+ import path31 from 'path';
4
+ import fs24, { existsSync } from 'fs';
5
5
  import { Parser, Language, Query } from 'web-tree-sitter';
6
6
  import { NodeSDK } from '@opentelemetry/sdk-node';
7
7
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
@@ -126,11 +126,11 @@ var init_shared = __esm({
126
126
  }
127
127
  });
128
128
  function findBundledWasmDir() {
129
- const fileDir = path27.dirname(fileURLToPath(import.meta.url));
129
+ const fileDir = path31.dirname(fileURLToPath(import.meta.url));
130
130
  const candidates = [
131
- path27.join(fileDir, "wasm"),
131
+ path31.join(fileDir, "wasm"),
132
132
  // dist/index.js → dist/wasm/
133
- path27.join(fileDir, "../wasm")
133
+ path31.join(fileDir, "../wasm")
134
134
  // dist/cli/main.js → dist/wasm/
135
135
  ];
136
136
  for (const candidate of candidates) {
@@ -171,7 +171,7 @@ function wasmPath(lang) {
171
171
  }
172
172
  const bundled = BUNDLED_WASM_MAP[lang];
173
173
  if (bundled) {
174
- const bundledPath = path27.join(_bundledWasmDir, bundled);
174
+ const bundledPath = path31.join(_bundledWasmDir, bundled);
175
175
  if (existsSync(bundledPath)) return bundledPath;
176
176
  }
177
177
  return null;
@@ -184,14 +184,14 @@ async function initParser() {
184
184
  }
185
185
  async function getLanguage(lang) {
186
186
  if (languageCache.has(lang)) return languageCache.get(lang);
187
- const path28 = wasmPath(lang);
188
- if (!path28) {
187
+ const path32 = wasmPath(lang);
188
+ if (!path32) {
189
189
  languageCache.set(lang, null);
190
190
  return null;
191
191
  }
192
192
  try {
193
193
  await initParser();
194
- const language = await Language.load(path28);
194
+ const language = await Language.load(path32);
195
195
  languageCache.set(lang, language);
196
196
  return language;
197
197
  } catch {
@@ -1162,7 +1162,7 @@ var init_logger = __esm({
1162
1162
  };
1163
1163
  }
1164
1164
  /** Global log directory: ~/.code-intel/logs */
1165
- static LOG_DIR = path27.join(os12.homedir(), ".code-intel", "logs");
1165
+ static LOG_DIR = path31.join(os12.homedir(), ".code-intel", "logs");
1166
1166
  static getLogger() {
1167
1167
  if (!_Logger.instance) {
1168
1168
  const isProduction = process.env.NODE_ENV === "production";
@@ -1171,12 +1171,12 @@ var init_logger = __esm({
1171
1171
  transports.push(new winston.transports.Console());
1172
1172
  if (!isProduction) {
1173
1173
  try {
1174
- if (!fs19.existsSync(_Logger.LOG_DIR)) {
1175
- fs19.mkdirSync(_Logger.LOG_DIR, { recursive: true });
1174
+ if (!fs24.existsSync(_Logger.LOG_DIR)) {
1175
+ fs24.mkdirSync(_Logger.LOG_DIR, { recursive: true });
1176
1176
  }
1177
1177
  transports.push(
1178
1178
  new DailyRotateFile({
1179
- filename: path27.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
1179
+ filename: path31.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
1180
1180
  datePattern: "YYYY-MM-DD",
1181
1181
  maxSize: "20m",
1182
1182
  maxFiles: "14d"
@@ -1961,7 +1961,7 @@ var init_parse_phase = __esm({
1961
1961
  const batch = filePaths.slice(i, i + CONCURRENCY);
1962
1962
  await Promise.all(batch.map(async (filePath) => {
1963
1963
  try {
1964
- const source = await fs19.promises.readFile(filePath, "utf-8");
1964
+ const source = await fs24.promises.readFile(filePath, "utf-8");
1965
1965
  context2.fileCache.set(filePath, source);
1966
1966
  } catch {
1967
1967
  }
@@ -1974,14 +1974,14 @@ var init_parse_phase = __esm({
1974
1974
  const lang = detectLanguage(filePath);
1975
1975
  if (!lang) {
1976
1976
  if (context2.verbose) {
1977
- const relativePath2 = path27.relative(context2.workspaceRoot, filePath);
1977
+ const relativePath2 = path31.relative(context2.workspaceRoot, filePath);
1978
1978
  logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
1979
1979
  }
1980
1980
  continue;
1981
1981
  }
1982
1982
  const source = context2.fileCache.get(filePath);
1983
1983
  if (!source) continue;
1984
- const relativePath = path27.relative(context2.workspaceRoot, filePath);
1984
+ const relativePath = path31.relative(context2.workspaceRoot, filePath);
1985
1985
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
1986
1986
  const fileNode = context2.graph.getNode(fileNodeId);
1987
1987
  if (fileNode) {
@@ -2221,11 +2221,11 @@ var init_resolve_phase = __esm({
2221
2221
  let heritageEdges = 0;
2222
2222
  const fileIndex = /* @__PURE__ */ new Map();
2223
2223
  for (const fp of filePaths) {
2224
- const rel = path27.relative(workspaceRoot, fp);
2224
+ const rel = path31.relative(workspaceRoot, fp);
2225
2225
  fileIndex.set(rel, fp);
2226
2226
  const noExt = rel.replace(/\.\w+$/, "");
2227
2227
  if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
2228
- const base = path27.basename(rel, path27.extname(rel));
2228
+ const base = path31.basename(rel, path31.extname(rel));
2229
2229
  if (!fileIndex.has(base)) fileIndex.set(base, fp);
2230
2230
  }
2231
2231
  const symbolIndex = /* @__PURE__ */ new Map();
@@ -2256,7 +2256,7 @@ var init_resolve_phase = __esm({
2256
2256
  for (const filePath of filePaths) {
2257
2257
  const lang = detectLanguage(filePath);
2258
2258
  if (!lang) continue;
2259
- const relativePath = path27.relative(workspaceRoot, filePath);
2259
+ const relativePath = path31.relative(workspaceRoot, filePath);
2260
2260
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2261
2261
  const source = fileCache.get(filePath);
2262
2262
  if (!source) continue;
@@ -2269,13 +2269,13 @@ var init_resolve_phase = __esm({
2269
2269
  let resolvedRelPath = null;
2270
2270
  if (cleaned.startsWith(".")) {
2271
2271
  const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
2272
- const fromDir = path27.dirname(relativePath);
2272
+ const fromDir = path31.dirname(relativePath);
2273
2273
  for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
2274
- const candidate = path27.join(fromDir, cleanedNoJs + ext);
2275
- const normalized = path27.normalize(candidate);
2274
+ const candidate = path31.join(fromDir, cleanedNoJs + ext);
2275
+ const normalized = path31.normalize(candidate);
2276
2276
  if (fileIndex.has(normalized)) {
2277
2277
  const absPath = fileIndex.get(normalized);
2278
- resolvedRelPath = path27.relative(workspaceRoot, absPath);
2278
+ resolvedRelPath = path31.relative(workspaceRoot, absPath);
2279
2279
  break;
2280
2280
  }
2281
2281
  }
@@ -2589,27 +2589,27 @@ __export(group_registry_exports, {
2589
2589
  saveSyncResult: () => saveSyncResult
2590
2590
  });
2591
2591
  function groupFile(name) {
2592
- return path27.join(GROUPS_DIR, `${name}.json`);
2592
+ return path31.join(GROUPS_DIR, `${name}.json`);
2593
2593
  }
2594
2594
  function loadGroup(name) {
2595
2595
  try {
2596
- return JSON.parse(fs19.readFileSync(groupFile(name), "utf-8"));
2596
+ return JSON.parse(fs24.readFileSync(groupFile(name), "utf-8"));
2597
2597
  } catch {
2598
2598
  return null;
2599
2599
  }
2600
2600
  }
2601
2601
  function saveGroup(group) {
2602
- fs19.mkdirSync(GROUPS_DIR, { recursive: true });
2603
- fs19.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
2602
+ fs24.mkdirSync(GROUPS_DIR, { recursive: true });
2603
+ fs24.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
2604
2604
  }
2605
2605
  function listGroups() {
2606
2606
  const groups = [];
2607
2607
  try {
2608
- for (const file of fs19.readdirSync(GROUPS_DIR)) {
2608
+ for (const file of fs24.readdirSync(GROUPS_DIR)) {
2609
2609
  if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
2610
2610
  try {
2611
2611
  const g = JSON.parse(
2612
- fs19.readFileSync(path27.join(GROUPS_DIR, file), "utf-8")
2612
+ fs24.readFileSync(path31.join(GROUPS_DIR, file), "utf-8")
2613
2613
  );
2614
2614
  groups.push(g);
2615
2615
  } catch {
@@ -2621,16 +2621,16 @@ function listGroups() {
2621
2621
  }
2622
2622
  function deleteGroup(name) {
2623
2623
  try {
2624
- fs19.unlinkSync(groupFile(name));
2624
+ fs24.unlinkSync(groupFile(name));
2625
2625
  } catch {
2626
2626
  }
2627
2627
  try {
2628
- fs19.unlinkSync(path27.join(GROUPS_DIR, `${name}.sync.json`));
2628
+ fs24.unlinkSync(path31.join(GROUPS_DIR, `${name}.sync.json`));
2629
2629
  } catch {
2630
2630
  }
2631
2631
  }
2632
2632
  function groupExists(name) {
2633
- return fs19.existsSync(groupFile(name));
2633
+ return fs24.existsSync(groupFile(name));
2634
2634
  }
2635
2635
  function addMember(groupName, member) {
2636
2636
  const group = loadGroup(groupName);
@@ -2656,16 +2656,16 @@ function removeMember(groupName, groupPath) {
2656
2656
  return group;
2657
2657
  }
2658
2658
  function saveSyncResult(result) {
2659
- fs19.mkdirSync(GROUPS_DIR, { recursive: true });
2660
- fs19.writeFileSync(
2661
- path27.join(GROUPS_DIR, `${result.groupName}.sync.json`),
2659
+ fs24.mkdirSync(GROUPS_DIR, { recursive: true });
2660
+ fs24.writeFileSync(
2661
+ path31.join(GROUPS_DIR, `${result.groupName}.sync.json`),
2662
2662
  JSON.stringify(result, null, 2) + "\n"
2663
2663
  );
2664
2664
  }
2665
2665
  function loadSyncResult(groupName) {
2666
2666
  try {
2667
2667
  return JSON.parse(
2668
- fs19.readFileSync(path27.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
2668
+ fs24.readFileSync(path31.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
2669
2669
  );
2670
2670
  } catch {
2671
2671
  return null;
@@ -2674,7 +2674,7 @@ function loadSyncResult(groupName) {
2674
2674
  var GROUPS_DIR;
2675
2675
  var init_group_registry = __esm({
2676
2676
  "src/multi-repo/group-registry.ts"() {
2677
- GROUPS_DIR = path27.join(os12.homedir(), ".code-intel", "groups");
2677
+ GROUPS_DIR = path31.join(os12.homedir(), ".code-intel", "groups");
2678
2678
  }
2679
2679
  });
2680
2680
 
@@ -2696,14 +2696,14 @@ function detectDeadCode(graph) {
2696
2696
  if (meta?.deprecated === true) continue;
2697
2697
  if (ENTRY_POINT_NAME_RE.test(node.name)) continue;
2698
2698
  if (entryPointIds.has(node.id)) continue;
2699
- let hasCallers = false;
2699
+ let hasCallers2 = false;
2700
2700
  for (const edge of graph.findEdgesTo(node.id)) {
2701
2701
  if (edge.kind === "calls") {
2702
- hasCallers = true;
2702
+ hasCallers2 = true;
2703
2703
  break;
2704
2704
  }
2705
2705
  }
2706
- if (hasCallers) continue;
2706
+ if (hasCallers2) continue;
2707
2707
  let hasImporters = false;
2708
2708
  for (const edge of graph.findEdgesTo(node.id)) {
2709
2709
  if (edge.kind === "imports") {
@@ -3702,6 +3702,550 @@ var init_gql_executor = __esm({
3702
3702
  }
3703
3703
  });
3704
3704
 
3705
+ // src/analysis/deprecated-detector.ts
3706
+ var deprecated_detector_exports = {};
3707
+ __export(deprecated_detector_exports, {
3708
+ DeprecatedDetector: () => DeprecatedDetector
3709
+ });
3710
+ var BUILTIN_DEPRECATED, DeprecatedDetector;
3711
+ var init_deprecated_detector = __esm({
3712
+ "src/analysis/deprecated-detector.ts"() {
3713
+ BUILTIN_DEPRECATED = [
3714
+ { pattern: "url.parse", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
3715
+ { pattern: "url.resolve", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
3716
+ { pattern: "url.format", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
3717
+ { pattern: "fs.exists", message: "deprecated \u2014 use fs.access instead" },
3718
+ { pattern: "crypto.createCipher", message: "deprecated \u2014 use crypto.createCipheriv instead" },
3719
+ { pattern: "crypto.createDecipher", message: "deprecated \u2014 use crypto.createDecipheriv instead" },
3720
+ { pattern: "new Buffer()", message: "deprecated \u2014 use Buffer.from() instead" },
3721
+ { pattern: "domain.create", message: "deprecated \u2014 the domain module is discouraged" },
3722
+ { pattern: "process.binding", message: "deprecated internal API" }
3723
+ ];
3724
+ DeprecatedDetector = class {
3725
+ tagDeprecated(graph) {
3726
+ for (const node of graph.allNodes()) {
3727
+ if (!node.metadata) node.metadata = {};
3728
+ if (node.metadata["deprecated"] === true) continue;
3729
+ let message;
3730
+ const jsdoc = node.metadata["jsdoc"];
3731
+ const comment = node.metadata["comment"];
3732
+ if (jsdoc?.includes("@deprecated") || comment?.includes("@deprecated")) {
3733
+ const src = jsdoc ?? comment ?? "";
3734
+ const match = src.match(/@deprecated\s+(.*)/);
3735
+ message = match?.[1]?.trim() || "deprecated";
3736
+ }
3737
+ if (!message && node.metadata["deprecated"] === true) {
3738
+ message = node.metadata["deprecationMessage"] ?? "deprecated";
3739
+ }
3740
+ if (!message) {
3741
+ const annotations = node.metadata["annotations"];
3742
+ if (Array.isArray(annotations) && annotations.includes("Deprecated")) {
3743
+ message = "marked @Deprecated";
3744
+ }
3745
+ }
3746
+ if (!message) {
3747
+ const attributes = node.metadata["attributes"];
3748
+ if (Array.isArray(attributes) && attributes.includes("deprecated")) {
3749
+ message = "marked #[deprecated]";
3750
+ }
3751
+ }
3752
+ if (!message) {
3753
+ for (const entry of BUILTIN_DEPRECATED) {
3754
+ if (node.name === entry.pattern || node.name.includes(entry.pattern)) {
3755
+ message = entry.message;
3756
+ break;
3757
+ }
3758
+ }
3759
+ }
3760
+ if (message) {
3761
+ node.metadata["deprecated"] = true;
3762
+ node.metadata["deprecationMessage"] = message;
3763
+ }
3764
+ }
3765
+ }
3766
+ detect(graph, scope) {
3767
+ const findings = [];
3768
+ for (const node of graph.allNodes()) {
3769
+ if (!node.metadata?.["deprecated"]) continue;
3770
+ const callers = [];
3771
+ for (const edge of graph.findEdgesTo(node.id)) {
3772
+ if (edge.kind !== "calls" && edge.kind !== "deprecated_use") continue;
3773
+ const caller = graph.getNode(edge.source);
3774
+ if (!caller) continue;
3775
+ if (scope && !caller.filePath.includes(scope)) continue;
3776
+ callers.push({ name: caller.name, filePath: caller.filePath });
3777
+ const edgeId = `dep_use_${edge.source}_${node.id}`;
3778
+ if (!graph.getEdge(edgeId)) {
3779
+ graph.addEdge({ id: edgeId, source: edge.source, target: node.id, kind: "deprecated_use" });
3780
+ }
3781
+ }
3782
+ findings.push({
3783
+ symbol: node.name,
3784
+ filePath: node.filePath,
3785
+ deprecationMessage: node.metadata?.["deprecationMessage"] ?? "deprecated",
3786
+ callers
3787
+ });
3788
+ }
3789
+ return findings;
3790
+ }
3791
+ };
3792
+ }
3793
+ });
3794
+
3795
+ // src/analysis/complexity.ts
3796
+ var complexity_exports = {};
3797
+ __export(complexity_exports, {
3798
+ computeComplexity: () => computeComplexity
3799
+ });
3800
+ function getSeverity(cyclomatic) {
3801
+ if (cyclomatic <= 5) return "LOW";
3802
+ if (cyclomatic <= 10) return "MEDIUM";
3803
+ if (cyclomatic <= 20) return "HIGH";
3804
+ return "CRITICAL";
3805
+ }
3806
+ function computeComplexity(graph, scope) {
3807
+ const results = [];
3808
+ for (const node of graph.allNodes()) {
3809
+ if (node.kind !== "function" && node.kind !== "method") continue;
3810
+ if (scope && !node.filePath.startsWith(scope)) continue;
3811
+ let outgoingCalls = 0;
3812
+ for (const edge of graph.findEdgesFrom(node.id)) {
3813
+ if (edge.kind === "calls") outgoingCalls++;
3814
+ }
3815
+ let cyclomatic;
3816
+ const meta = node.metadata;
3817
+ const metaComplexity = meta?.complexity;
3818
+ if (typeof metaComplexity?.cyclomatic === "number") {
3819
+ cyclomatic = metaComplexity.cyclomatic;
3820
+ } else {
3821
+ cyclomatic = 1 + Math.floor(outgoingCalls / 2);
3822
+ }
3823
+ cyclomatic = Math.min(cyclomatic, 50);
3824
+ let cognitive;
3825
+ if (typeof metaComplexity?.cognitive === "number") {
3826
+ cognitive = metaComplexity.cognitive;
3827
+ } else {
3828
+ cognitive = Math.ceil(cyclomatic * 1.3);
3829
+ }
3830
+ results.push({
3831
+ nodeId: node.id,
3832
+ name: node.name,
3833
+ filePath: node.filePath,
3834
+ cyclomatic,
3835
+ cognitive,
3836
+ severity: getSeverity(cyclomatic)
3837
+ });
3838
+ }
3839
+ return results.sort((a, b) => b.cyclomatic - a.cyclomatic);
3840
+ }
3841
+ var init_complexity = __esm({
3842
+ "src/analysis/complexity.ts"() {
3843
+ }
3844
+ });
3845
+
3846
+ // src/analysis/test-coverage.ts
3847
+ var test_coverage_exports = {};
3848
+ __export(test_coverage_exports, {
3849
+ computeCoverage: () => computeCoverage
3850
+ });
3851
+ function isTestFile(filePath) {
3852
+ if (filePath.includes(".test.") || filePath.includes(".spec.")) return true;
3853
+ if (filePath.includes("_test.") || filePath.endsWith("_test.go")) return true;
3854
+ if (filePath.includes("__tests__")) return true;
3855
+ const base = path31.basename(filePath);
3856
+ if (base.startsWith("Test") && filePath.endsWith(".java")) return true;
3857
+ return false;
3858
+ }
3859
+ function computeBlastRadius(graph, nodeId) {
3860
+ const visited = /* @__PURE__ */ new Set();
3861
+ const queue = [{ id: nodeId, depth: 0 }];
3862
+ while (queue.length > 0) {
3863
+ const { id, depth } = queue.shift();
3864
+ if (visited.has(id)) continue;
3865
+ visited.add(id);
3866
+ if (depth >= 3) continue;
3867
+ for (const edge of graph.findEdgesTo(id)) {
3868
+ if (edge.kind === "calls" || edge.kind === "imports") {
3869
+ if (!visited.has(edge.source)) {
3870
+ queue.push({ id: edge.source, depth: depth + 1 });
3871
+ }
3872
+ }
3873
+ }
3874
+ }
3875
+ return Math.max(0, visited.size - 1);
3876
+ }
3877
+ function getRisk(blastRadius) {
3878
+ if (blastRadius > 20) return "HIGH";
3879
+ if (blastRadius >= 5) return "MEDIUM";
3880
+ return "LOW";
3881
+ }
3882
+ function computeCoverage(graph, scope) {
3883
+ const testFilePaths = /* @__PURE__ */ new Set();
3884
+ for (const node of graph.allNodes()) {
3885
+ if (isTestFile(node.filePath)) testFilePaths.add(node.filePath);
3886
+ }
3887
+ const nodesImportedByTests = /* @__PURE__ */ new Set();
3888
+ for (const edge of graph.findEdgesByKind("imports")) {
3889
+ const sourceNode = graph.getNode(edge.source);
3890
+ if (sourceNode && isTestFile(sourceNode.filePath)) {
3891
+ nodesImportedByTests.add(edge.target);
3892
+ }
3893
+ }
3894
+ const nodesWithTestedBy = /* @__PURE__ */ new Set();
3895
+ for (const edge of graph.findEdgesByKind("tested_by")) {
3896
+ nodesWithTestedBy.add(edge.source);
3897
+ }
3898
+ const baseNameToTestFiles = /* @__PURE__ */ new Map();
3899
+ for (const testPath of testFilePaths) {
3900
+ const base = path31.basename(testPath);
3901
+ const stripped = base.replace(/\.test\.[^.]+$/, "").replace(/\.spec\.[^.]+$/, "").replace(/_test\.[^.]+$/, "").replace(/_test$/, "");
3902
+ const existing = baseNameToTestFiles.get(stripped) ?? [];
3903
+ existing.push(testPath);
3904
+ baseNameToTestFiles.set(stripped, existing);
3905
+ }
3906
+ const exportedKinds = /* @__PURE__ */ new Set(["function", "method", "class"]);
3907
+ const results = [];
3908
+ for (const node of graph.allNodes()) {
3909
+ if (!exportedKinds.has(node.kind)) continue;
3910
+ if (node.exported !== true) continue;
3911
+ if (scope && !node.filePath.startsWith(scope)) continue;
3912
+ const testFiles = [];
3913
+ if (nodesWithTestedBy.has(node.id)) {
3914
+ for (const edge of graph.findEdgesFrom(node.id)) {
3915
+ if (edge.kind === "tested_by") {
3916
+ const testNode = graph.getNode(edge.target);
3917
+ if (testNode && !testFiles.includes(testNode.filePath)) {
3918
+ testFiles.push(testNode.filePath);
3919
+ }
3920
+ }
3921
+ }
3922
+ }
3923
+ if (nodesImportedByTests.has(node.id)) {
3924
+ for (const edge of graph.findEdgesTo(node.id)) {
3925
+ if (edge.kind === "imports") {
3926
+ const sourceNode = graph.getNode(edge.source);
3927
+ if (sourceNode && isTestFile(sourceNode.filePath) && !testFiles.includes(sourceNode.filePath)) {
3928
+ testFiles.push(sourceNode.filePath);
3929
+ }
3930
+ }
3931
+ }
3932
+ }
3933
+ const nodeBase = path31.basename(node.filePath).replace(/\.[^.]+$/, "");
3934
+ const matchingTestFiles = baseNameToTestFiles.get(nodeBase) ?? [];
3935
+ for (const tf of matchingTestFiles) {
3936
+ if (!testFiles.includes(tf)) testFiles.push(tf);
3937
+ }
3938
+ const tested = testFiles.length > 0;
3939
+ const blastRadius = computeBlastRadius(graph, node.id);
3940
+ const risk = getRisk(blastRadius);
3941
+ results.push({
3942
+ nodeId: node.id,
3943
+ name: node.name,
3944
+ filePath: node.filePath,
3945
+ exported: true,
3946
+ tested,
3947
+ testFiles,
3948
+ blastRadius,
3949
+ risk
3950
+ });
3951
+ }
3952
+ const totalExported = results.length;
3953
+ const testedExported = results.filter((r) => r.tested).length;
3954
+ const coveragePct = totalExported === 0 ? 100 : Math.round(testedExported / totalExported * 100);
3955
+ const untestedByRisk = results.filter((r) => !r.tested).sort((a, b) => b.blastRadius - a.blastRadius);
3956
+ return { totalExported, testedExported, coveragePct, untestedByRisk };
3957
+ }
3958
+ var init_test_coverage = __esm({
3959
+ "src/analysis/test-coverage.ts"() {
3960
+ }
3961
+ });
3962
+
3963
+ // src/security/secret-scanner.ts
3964
+ var secret_scanner_exports = {};
3965
+ __export(secret_scanner_exports, {
3966
+ SecretScanner: () => SecretScanner
3967
+ });
3968
+ function shannonEntropy(s) {
3969
+ const freq = /* @__PURE__ */ new Map();
3970
+ for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
3971
+ let entropy = 0;
3972
+ for (const count of freq.values()) {
3973
+ const p = count / s.length;
3974
+ entropy -= p * Math.log2(p);
3975
+ }
3976
+ return entropy;
3977
+ }
3978
+ function isTestFile2(filePath) {
3979
+ return filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("fixtures/") || filePath.includes("mocks/");
3980
+ }
3981
+ var ENV_VAR_RE, SENSITIVE_NAME_RE, VALUE_PATTERNS, SecretScanner;
3982
+ var init_secret_scanner = __esm({
3983
+ "src/security/secret-scanner.ts"() {
3984
+ ENV_VAR_RE = /^process\.env\./;
3985
+ SENSITIVE_NAME_RE = /_SECRET$|_PASSWORD$|_TOKEN$|_KEY$|_API_KEY$/i;
3986
+ VALUE_PATTERNS = [
3987
+ [/sk-[A-Za-z0-9]{6,}/, "openai-api-key", "HIGH"],
3988
+ [/pk_live_[A-Za-z0-9]{20,}/, "stripe-key", "HIGH"],
3989
+ [/AKIA[0-9A-Z]{16}|aws.access.key/i, "aws-access-key", "HIGH"],
3990
+ [/xoxb-[0-9]{11}-[0-9]{11}-[A-Za-z0-9]{24}/, "slack-token", "HIGH"],
3991
+ [/postgres:\/\/[^@]+:[^@]+@/, "db-url-with-credentials", "HIGH"],
3992
+ [/mysql:\/\/[^@]+:[^@]+@/, "db-url-with-credentials", "HIGH"],
3993
+ [/-----BEGIN RSA PRIVATE KEY-----/, "rsa-private-key", "HIGH"]
3994
+ ];
3995
+ SecretScanner = class {
3996
+ scan(graph, options) {
3997
+ const findings = [];
3998
+ const includeTests = options?.includeTestFiles ?? false;
3999
+ const scope = options?.scope;
4000
+ const ignorePatterns = [...options?.ignorePatterns ?? []];
4001
+ if (options?.workspaceRoot) {
4002
+ try {
4003
+ const raw = fs24.readFileSync(path31.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
4004
+ for (const line of raw.split("\n")) {
4005
+ const trimmed = line.trim();
4006
+ if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
4007
+ }
4008
+ } catch {
4009
+ }
4010
+ }
4011
+ for (const node of graph.allNodes()) {
4012
+ const filePath = node.filePath;
4013
+ if (scope && !filePath.startsWith(scope)) continue;
4014
+ if (!includeTests && isTestFile2(filePath)) continue;
4015
+ if (ignorePatterns.length > 0 && ignorePatterns.some((p) => filePath.includes(p))) continue;
4016
+ const meta = node.metadata;
4017
+ const rawValue = meta?.value ?? meta?.literalValue;
4018
+ if (typeof rawValue !== "string" || rawValue.trim() === "") continue;
4019
+ const value = rawValue.trim();
4020
+ if (ENV_VAR_RE.test(value)) continue;
4021
+ let matched = false;
4022
+ for (const [re, label, severity] of VALUE_PATTERNS) {
4023
+ if (re.test(value)) {
4024
+ node.metadata = {
4025
+ ...node.metadata ?? {},
4026
+ security: { secretRisk: true, secretPattern: label }
4027
+ };
4028
+ findings.push({
4029
+ file: filePath,
4030
+ line: node.startLine,
4031
+ symbol: node.name,
4032
+ pattern: label,
4033
+ severity
4034
+ });
4035
+ matched = true;
4036
+ break;
4037
+ }
4038
+ }
4039
+ if (matched) continue;
4040
+ if (SENSITIVE_NAME_RE.test(node.name)) {
4041
+ node.metadata = {
4042
+ ...node.metadata ?? {},
4043
+ security: { secretRisk: true, secretPattern: "sensitive-name-with-value" }
4044
+ };
4045
+ findings.push({
4046
+ file: filePath,
4047
+ line: node.startLine,
4048
+ symbol: node.name,
4049
+ pattern: "sensitive-name-with-value",
4050
+ severity: "MEDIUM"
4051
+ });
4052
+ continue;
4053
+ }
4054
+ if (SENSITIVE_NAME_RE.test(node.name) && value.length > 20 && shannonEntropy(value) > 4.5) {
4055
+ node.metadata = {
4056
+ ...node.metadata ?? {},
4057
+ security: { secretRisk: true, secretPattern: "high-entropy-string" }
4058
+ };
4059
+ findings.push({
4060
+ file: filePath,
4061
+ line: node.startLine,
4062
+ symbol: node.name,
4063
+ pattern: "high-entropy-string",
4064
+ severity: "MEDIUM"
4065
+ });
4066
+ }
4067
+ }
4068
+ return findings;
4069
+ }
4070
+ };
4071
+ }
4072
+ });
4073
+
4074
+ // src/security/vulnerability-detector.ts
4075
+ var vulnerability_detector_exports = {};
4076
+ __export(vulnerability_detector_exports, {
4077
+ VulnerabilityDetector: () => VulnerabilityDetector
4078
+ });
4079
+ function hasCallers(graph, nodeId) {
4080
+ for (const edge of graph.findEdgesTo(nodeId)) {
4081
+ if (edge.kind === "calls") return true;
4082
+ }
4083
+ return false;
4084
+ }
4085
+ var CWE, SQL_PATTERN, XSS_PATTERN, SSRF_PATTERN, PATH_PATTERN, CMD_PATTERN, VulnerabilityDetector;
4086
+ var init_vulnerability_detector = __esm({
4087
+ "src/security/vulnerability-detector.ts"() {
4088
+ CWE = {
4089
+ SQL_INJECTION: "CWE-89",
4090
+ XSS: "CWE-79",
4091
+ SSRF: "CWE-918",
4092
+ PATH_TRAVERSAL: "CWE-22",
4093
+ COMMAND_INJECTION: "CWE-78"
4094
+ };
4095
+ SQL_PATTERN = /(db|database|connection|knex|sequelize|pool)\.(query|execute|raw)/i;
4096
+ XSS_PATTERN = /innerHTML|outerHTML|document\.write|insertAdjacentHTML/i;
4097
+ SSRF_PATTERN = /^(fetch|axios|http\.request|got)$/i;
4098
+ PATH_PATTERN = /^(fs\.readFile|fs\.writeFile|path\.join|createReadStream)$/i;
4099
+ CMD_PATTERN = /^(exec|execSync|spawn|eval)$/i;
4100
+ VulnerabilityDetector = class {
4101
+ detect(graph, options) {
4102
+ const findings = [];
4103
+ const scope = options?.scope;
4104
+ const types = options?.types ? new Set(options.types) : null;
4105
+ const want = (t) => !types || types.has(t);
4106
+ const nodes = [...graph.allNodes()];
4107
+ for (const node of nodes) {
4108
+ if (node.kind === "vulnerability") continue;
4109
+ const filePath = node.filePath;
4110
+ if (scope && !filePath.startsWith(scope)) continue;
4111
+ const nodeName = node.name;
4112
+ const meta = node.metadata;
4113
+ if (want("SQL_INJECTION") && SQL_PATTERN.test(nodeName)) {
4114
+ const hasDynamic = meta?.hasStringConcatenation === true || hasCallers(graph, node.id);
4115
+ if (hasDynamic) {
4116
+ findings.push({
4117
+ type: "SQL_INJECTION",
4118
+ severity: "MEDIUM",
4119
+ file: filePath,
4120
+ line: node.startLine,
4121
+ symbol: nodeName,
4122
+ description: `Potential SQL injection: ${nodeName} may execute unsanitized user input`,
4123
+ cweId: CWE["SQL_INJECTION"]
4124
+ });
4125
+ this._tagNode(graph, node.id, "SQL_INJECTION");
4126
+ }
4127
+ }
4128
+ if (want("XSS") && XSS_PATTERN.test(nodeName)) {
4129
+ findings.push({
4130
+ type: "XSS",
4131
+ severity: "HIGH",
4132
+ file: filePath,
4133
+ line: node.startLine,
4134
+ symbol: nodeName,
4135
+ description: `Potential XSS: ${nodeName} writes to DOM sink`,
4136
+ cweId: CWE["XSS"]
4137
+ });
4138
+ this._tagNode(graph, node.id, "XSS");
4139
+ }
4140
+ if (want("XSS")) {
4141
+ for (const edge of graph.findEdgesFrom(node.id)) {
4142
+ if (edge.kind === "calls") {
4143
+ const callee = graph.getNode(edge.target);
4144
+ if (callee && XSS_PATTERN.test(callee.name)) {
4145
+ findings.push({
4146
+ type: "XSS",
4147
+ severity: "HIGH",
4148
+ file: filePath,
4149
+ line: node.startLine,
4150
+ symbol: node.name,
4151
+ description: `Potential XSS: ${node.name} calls ${callee.name}`,
4152
+ cweId: CWE["XSS"]
4153
+ });
4154
+ this._tagNode(graph, node.id, "XSS");
4155
+ }
4156
+ }
4157
+ }
4158
+ }
4159
+ if (want("SSRF") && SSRF_PATTERN.test(nodeName)) {
4160
+ const isDynamic = meta?.dynamicUrl === true;
4161
+ if (isDynamic) {
4162
+ findings.push({
4163
+ type: "SSRF",
4164
+ severity: "HIGH",
4165
+ file: filePath,
4166
+ line: node.startLine,
4167
+ symbol: nodeName,
4168
+ description: `Potential SSRF: ${nodeName} uses a dynamic URL`,
4169
+ cweId: CWE["SSRF"]
4170
+ });
4171
+ this._tagNode(graph, node.id, "SSRF");
4172
+ }
4173
+ }
4174
+ if (want("PATH_TRAVERSAL") && PATH_PATTERN.test(nodeName)) {
4175
+ const isDynamic = meta?.dynamicPath === true;
4176
+ if (isDynamic) {
4177
+ findings.push({
4178
+ type: "PATH_TRAVERSAL",
4179
+ severity: "HIGH",
4180
+ file: filePath,
4181
+ line: node.startLine,
4182
+ symbol: nodeName,
4183
+ description: `Potential path traversal: ${nodeName} uses a dynamic path`,
4184
+ cweId: CWE["PATH_TRAVERSAL"]
4185
+ });
4186
+ this._tagNode(graph, node.id, "PATH_TRAVERSAL");
4187
+ }
4188
+ }
4189
+ if (want("COMMAND_INJECTION") && CMD_PATTERN.test(nodeName)) {
4190
+ const isDynamic = meta?.dynamicArgs === true;
4191
+ if (isDynamic) {
4192
+ findings.push({
4193
+ type: "COMMAND_INJECTION",
4194
+ severity: "HIGH",
4195
+ file: filePath,
4196
+ line: node.startLine,
4197
+ symbol: nodeName,
4198
+ description: `Potential command injection: ${nodeName} uses dynamic arguments`,
4199
+ cweId: CWE["COMMAND_INJECTION"]
4200
+ });
4201
+ this._tagNode(graph, node.id, "COMMAND_INJECTION");
4202
+ }
4203
+ }
4204
+ }
4205
+ const seen = /* @__PURE__ */ new Set();
4206
+ return findings.filter((f) => {
4207
+ const key = `${f.type}:${f.file}:${f.line}:${f.symbol}`;
4208
+ if (seen.has(key)) return false;
4209
+ seen.add(key);
4210
+ return true;
4211
+ });
4212
+ }
4213
+ _tagNode(graph, nodeId, vulnType) {
4214
+ const node = graph.getNode(nodeId);
4215
+ if (!node) return;
4216
+ const meta = node.metadata ?? {};
4217
+ node.metadata = {
4218
+ ...meta,
4219
+ security: {
4220
+ ...meta["security"] ?? {},
4221
+ vulnerability: vulnType
4222
+ }
4223
+ };
4224
+ const vulnNodeId = `vuln:${vulnType}:${nodeId}`;
4225
+ if (!graph.getNode(vulnNodeId)) {
4226
+ graph.addNode({
4227
+ id: vulnNodeId,
4228
+ kind: "vulnerability",
4229
+ name: `${vulnType}:${node.name}`,
4230
+ filePath: node.filePath,
4231
+ startLine: node.startLine,
4232
+ metadata: { cweId: CWE[vulnType], vulnType }
4233
+ });
4234
+ }
4235
+ const edgeId = `has_vulnerability:${nodeId}:${vulnNodeId}`;
4236
+ if (!graph.getEdge(edgeId)) {
4237
+ graph.addEdge({
4238
+ id: edgeId,
4239
+ source: nodeId,
4240
+ target: vulnNodeId,
4241
+ kind: "has_vulnerability"
4242
+ });
4243
+ }
4244
+ }
4245
+ };
4246
+ }
4247
+ });
4248
+
3705
4249
  // src/errors/codes.ts
3706
4250
  var ErrorCodes, AppError;
3707
4251
  var init_codes = __esm({
@@ -3735,10 +4279,10 @@ var init_codes = __esm({
3735
4279
  }
3736
4280
  });
3737
4281
  function secureMkdir(dir) {
3738
- fs19.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
4282
+ fs24.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
3739
4283
  if (process.platform !== "win32") {
3740
4284
  try {
3741
- fs19.chmodSync(dir, SECURE_DIR_MODE);
4285
+ fs24.chmodSync(dir, SECURE_DIR_MODE);
3742
4286
  } catch {
3743
4287
  }
3744
4288
  }
@@ -3746,17 +4290,17 @@ function secureMkdir(dir) {
3746
4290
  function secureChmodFile(file) {
3747
4291
  if (process.platform === "win32") return;
3748
4292
  try {
3749
- fs19.chmodSync(file, SECURE_FILE_MODE);
4293
+ fs24.chmodSync(file, SECURE_FILE_MODE);
3750
4294
  } catch {
3751
4295
  }
3752
4296
  }
3753
4297
  function tightenDbFiles(dir) {
3754
4298
  if (process.platform === "win32") return;
3755
- if (!fs19.existsSync(dir)) return;
3756
- for (const name of fs19.readdirSync(dir)) {
4299
+ if (!fs24.existsSync(dir)) return;
4300
+ for (const name of fs24.readdirSync(dir)) {
3757
4301
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
3758
4302
  try {
3759
- fs19.chmodSync(path27.join(dir, name), SECURE_FILE_MODE);
4303
+ fs24.chmodSync(path31.join(dir, name), SECURE_FILE_MODE);
3760
4304
  } catch {
3761
4305
  }
3762
4306
  }
@@ -3770,7 +4314,7 @@ var init_fs_secure = __esm({
3770
4314
  }
3771
4315
  });
3772
4316
  function getUsersDBPath() {
3773
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "users.db");
4317
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path31.join(os12.homedir(), ".code-intel", "users.db");
3774
4318
  }
3775
4319
  function getOrCreateUsersDB() {
3776
4320
  if (!_usersDB) {
@@ -3786,7 +4330,7 @@ var init_users_db = __esm({
3786
4330
  UsersDB = class {
3787
4331
  db;
3788
4332
  constructor(dbPath) {
3789
- const dir = path27.dirname(dbPath);
4333
+ const dir = path31.dirname(dbPath);
3790
4334
  secureMkdir(dir);
3791
4335
  this.db = new Database3(dbPath);
3792
4336
  this.db.pragma("journal_mode = WAL");
@@ -4063,7 +4607,7 @@ function getScryptN() {
4063
4607
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
4064
4608
  }
4065
4609
  function getSecretsPath() {
4066
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path27.join(os12.homedir(), ".code-intel", ".secrets");
4610
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path31.join(os12.homedir(), ".code-intel", ".secrets");
4067
4611
  }
4068
4612
  function getMasterPassword() {
4069
4613
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
@@ -4094,8 +4638,8 @@ function decryptSecrets(encrypted) {
4094
4638
  return JSON.parse(plaintext.toString("utf8"));
4095
4639
  }
4096
4640
  function loadSecrets(secretsPath = getSecretsPath()) {
4097
- if (!fs19.existsSync(secretsPath)) return {};
4098
- const blob = fs19.readFileSync(secretsPath);
4641
+ if (!fs24.existsSync(secretsPath)) return {};
4642
+ const blob = fs24.readFileSync(secretsPath);
4099
4643
  return decryptSecrets(blob);
4100
4644
  }
4101
4645
  function getSecret(key, secretsPath = getSecretsPath()) {
@@ -4214,6 +4758,9 @@ function requireAuth(req, res, next) {
4214
4758
  }
4215
4759
  next();
4216
4760
  }
4761
+ function meetsRole(userRole, required) {
4762
+ return (ROLE_RANK[userRole] ?? 0) >= (ROLE_RANK[required] ?? 0);
4763
+ }
4217
4764
  function requireRole(...roles) {
4218
4765
  return (req, res, next) => {
4219
4766
  if (!req.user) {
@@ -4228,7 +4775,8 @@ function requireRole(...roles) {
4228
4775
  });
4229
4776
  return;
4230
4777
  }
4231
- if (!roles.includes(req.user.role)) {
4778
+ const allowed = roles.some((r) => meetsRole(req.user.role, r));
4779
+ if (!allowed) {
4232
4780
  res.status(403).json({
4233
4781
  error: {
4234
4782
  code: ErrorCodes.FORBIDDEN,
@@ -4337,7 +4885,7 @@ function clearSessionCookie() {
4337
4885
  async function verifyPassword(plain, hash) {
4338
4886
  return bcrypt.compare(plain, hash);
4339
4887
  }
4340
- var sessionStore, SESSION_COOKIE_NAME;
4888
+ var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
4341
4889
  var init_middleware = __esm({
4342
4890
  "src/auth/middleware.ts"() {
4343
4891
  init_users_db();
@@ -4345,6 +4893,12 @@ var init_middleware = __esm({
4345
4893
  init_secret_store();
4346
4894
  sessionStore = /* @__PURE__ */ new Map();
4347
4895
  SESSION_COOKIE_NAME = "code_intel_session";
4896
+ ROLE_RANK = {
4897
+ viewer: 1,
4898
+ "repo-owner": 2,
4899
+ analyst: 3,
4900
+ admin: 4
4901
+ };
4348
4902
  }
4349
4903
  });
4350
4904
  function verifyWebSocketHandshake(req) {
@@ -4652,7 +5206,7 @@ init_shared();
4652
5206
  init_shared();
4653
5207
  init_typescript();
4654
5208
  function resolveRelative(rawPath, fromFile, workspace) {
4655
- const fromDir = path27.dirname(fromFile);
5209
+ const fromDir = path31.dirname(fromFile);
4656
5210
  const cleaned = rawPath.replace(/['"]/g, "");
4657
5211
  const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
4658
5212
  const resolved = workspace.resolve(fromDir, cleaned);
@@ -4704,7 +5258,7 @@ var pythonModule = {
4704
5258
  resolveImport(rawPath, fromFile, workspace) {
4705
5259
  const cleaned = rawPath.replace(/['"]/g, "");
4706
5260
  const parts = cleaned.split(".");
4707
- const fromDir = path27.dirname(fromFile);
5261
+ const fromDir = path31.dirname(fromFile);
4708
5262
  const relPath = parts.join("/");
4709
5263
  for (const suffix of ["/__init__.py", ".py"]) {
4710
5264
  const r = workspace.resolve(fromDir, relPath + suffix);
@@ -4783,7 +5337,7 @@ var cModule = {
4783
5337
  inheritanceStrategy: "none",
4784
5338
  resolveImport(rawPath, fromFile, workspace) {
4785
5339
  const cleaned = rawPath.replace(/[<>"']/g, "");
4786
- const fromDir = path27.dirname(fromFile);
5340
+ const fromDir = path31.dirname(fromFile);
4787
5341
  return workspace.resolve(fromDir, cleaned);
4788
5342
  },
4789
5343
  isExported(_node) {
@@ -4806,7 +5360,7 @@ var cppModule = {
4806
5360
  inheritanceStrategy: "depth-first",
4807
5361
  resolveImport(rawPath, fromFile, workspace) {
4808
5362
  const cleaned = rawPath.replace(/[<>"']/g, "");
4809
- const fromDir = path27.dirname(fromFile);
5363
+ const fromDir = path31.dirname(fromFile);
4810
5364
  return workspace.resolve(fromDir, cleaned);
4811
5365
  },
4812
5366
  isExported(_node) {
@@ -4968,7 +5522,7 @@ var dartModule = {
4968
5522
  const pkg = cleaned.replace("package:", "");
4969
5523
  return workspace.findByPackage(pkg);
4970
5524
  }
4971
- const fromDir = path27.dirname(fromFile);
5525
+ const fromDir = path31.dirname(fromFile);
4972
5526
  return workspace.resolve(fromDir, cleaned);
4973
5527
  },
4974
5528
  isExported(node) {
@@ -5323,25 +5877,25 @@ function validateDAG(phases) {
5323
5877
  const visiting = /* @__PURE__ */ new Set();
5324
5878
  const visited = /* @__PURE__ */ new Set();
5325
5879
  const phaseMap = new Map(phases.map((p) => [p.name, p]));
5326
- function dfs(name, path28) {
5880
+ function dfs(name, path32) {
5327
5881
  if (visiting.has(name)) {
5328
- const cycleStart = path28.indexOf(name);
5329
- const cycle = path28.slice(cycleStart).concat(name);
5882
+ const cycleStart = path32.indexOf(name);
5883
+ const cycle = path32.slice(cycleStart).concat(name);
5330
5884
  errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
5331
5885
  return true;
5332
5886
  }
5333
5887
  if (visited.has(name)) return false;
5334
5888
  visiting.add(name);
5335
- path28.push(name);
5889
+ path32.push(name);
5336
5890
  const phase = phaseMap.get(name);
5337
5891
  if (phase) {
5338
5892
  for (const dep of phase.dependencies) {
5339
- if (dfs(dep, path28)) return true;
5893
+ if (dfs(dep, path32)) return true;
5340
5894
  }
5341
5895
  }
5342
5896
  visiting.delete(name);
5343
5897
  visited.add(name);
5344
- path28.pop();
5898
+ path32.pop();
5345
5899
  return false;
5346
5900
  }
5347
5901
  for (const phase of phases) {
@@ -5554,7 +6108,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
5554
6108
  ]);
5555
6109
  function loadIgnorePatterns(workspaceRoot) {
5556
6110
  try {
5557
- const raw = fs19.readFileSync(path27.join(workspaceRoot, ".codeintelignore"), "utf-8");
6111
+ const raw = fs24.readFileSync(path31.join(workspaceRoot, ".codeintelignore"), "utf-8");
5558
6112
  const extras = /* @__PURE__ */ new Set();
5559
6113
  for (const line of raw.split("\n")) {
5560
6114
  const trimmed = line.trim();
@@ -5578,7 +6132,7 @@ var scanPhase = {
5578
6132
  function walk(dir) {
5579
6133
  let entries;
5580
6134
  try {
5581
- entries = fs19.readdirSync(dir, { withFileTypes: true });
6135
+ entries = fs24.readdirSync(dir, { withFileTypes: true });
5582
6136
  } catch {
5583
6137
  return;
5584
6138
  }
@@ -5587,15 +6141,15 @@ var scanPhase = {
5587
6141
  if (entry.name.startsWith(".")) continue;
5588
6142
  if (IGNORED_DIRS.has(entry.name)) continue;
5589
6143
  if (extraIgnore.has(entry.name)) continue;
5590
- walk(path27.join(dir, entry.name));
6144
+ walk(path31.join(dir, entry.name));
5591
6145
  } else if (entry.isFile()) {
5592
6146
  const name = entry.name;
5593
6147
  if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
5594
- const ext = path27.extname(name);
6148
+ const ext = path31.extname(name);
5595
6149
  if (!extensions.has(ext)) continue;
5596
- const fullPath = path27.join(dir, name);
6150
+ const fullPath = path31.join(dir, name);
5597
6151
  try {
5598
- const stat = fs19.statSync(fullPath);
6152
+ const stat = fs24.statSync(fullPath);
5599
6153
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
5600
6154
  } catch {
5601
6155
  continue;
@@ -5622,20 +6176,20 @@ var structurePhase = {
5622
6176
  const dirs = /* @__PURE__ */ new Set();
5623
6177
  let structDone = 0;
5624
6178
  for (const filePath of context2.filePaths) {
5625
- const relativePath = path27.relative(context2.workspaceRoot, filePath);
6179
+ const relativePath = path31.relative(context2.workspaceRoot, filePath);
5626
6180
  const lang = detectLanguage(filePath);
5627
6181
  context2.graph.addNode({
5628
6182
  id: generateNodeId("file", relativePath, relativePath),
5629
6183
  kind: "file",
5630
- name: path27.basename(filePath),
6184
+ name: path31.basename(filePath),
5631
6185
  filePath: relativePath,
5632
6186
  metadata: lang ? { language: lang } : void 0
5633
6187
  });
5634
- let dir = path27.dirname(relativePath);
6188
+ let dir = path31.dirname(relativePath);
5635
6189
  while (dir && dir !== "." && dir !== "") {
5636
6190
  if (dirs.has(dir)) break;
5637
6191
  dirs.add(dir);
5638
- dir = path27.dirname(dir);
6192
+ dir = path31.dirname(dir);
5639
6193
  }
5640
6194
  structDone++;
5641
6195
  context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
@@ -5644,7 +6198,7 @@ var structurePhase = {
5644
6198
  context2.graph.addNode({
5645
6199
  id: generateNodeId("directory", dir, dir),
5646
6200
  kind: "directory",
5647
- name: path27.basename(dir),
6201
+ name: path31.basename(dir),
5648
6202
  filePath: dir
5649
6203
  });
5650
6204
  }
@@ -5731,9 +6285,9 @@ var flowPhase = {
5731
6285
  for (const node of graph.allNodes()) {
5732
6286
  if (!["function", "method"].includes(node.kind)) continue;
5733
6287
  let score = 0;
5734
- const hasCallers = calledNodes.has(node.id);
6288
+ const hasCallers2 = calledNodes.has(node.id);
5735
6289
  const outCalls = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "calls");
5736
- if (!hasCallers && outCalls.length > 0) score += 10;
6290
+ if (!hasCallers2 && outCalls.length > 0) score += 10;
5737
6291
  if (node.exported) score += 5;
5738
6292
  if (/^(main|handle|init|start|run|execute|process|serve|listen|bootstrap)/.test(node.name)) score += 3;
5739
6293
  if (node.filePath.includes("test") || node.filePath.includes("spec") || node.filePath.includes("__test")) score -= 20;
@@ -5755,22 +6309,22 @@ var flowPhase = {
5755
6309
  const queue = [{ nodeId: ep.id, path: [ep.id] }];
5756
6310
  const visited = /* @__PURE__ */ new Set();
5757
6311
  while (queue.length > 0 && flowCount < maxFlows) {
5758
- const { nodeId, path: path28 } = queue.shift();
5759
- if (path28.length > maxDepth) continue;
6312
+ const { nodeId, path: path32 } = queue.shift();
6313
+ if (path32.length > maxDepth) continue;
5760
6314
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
5761
- if (callEdges.length === 0 && path28.length >= 3) {
6315
+ if (callEdges.length === 0 && path32.length >= 3) {
5762
6316
  const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
5763
6317
  graph.addNode({
5764
6318
  id: flowId,
5765
6319
  kind: "flow",
5766
6320
  name: `${ep.name} flow ${flowCount}`,
5767
6321
  filePath: ep.filePath,
5768
- metadata: { steps: path28, entryPoint: ep.name }
6322
+ metadata: { steps: path32, entryPoint: ep.name }
5769
6323
  });
5770
- for (let i = 0; i < path28.length; i++) {
6324
+ for (let i = 0; i < path32.length; i++) {
5771
6325
  graph.addEdge({
5772
- id: generateEdgeId(path28[i], flowId, `step_of_${i}`),
5773
- source: path28[i],
6326
+ id: generateEdgeId(path32[i], flowId, `step_of_${i}`),
6327
+ source: path32[i],
5774
6328
  target: flowId,
5775
6329
  kind: "step_of",
5776
6330
  weight: 1,
@@ -5783,7 +6337,7 @@ var flowPhase = {
5783
6337
  for (const edge of callEdges) {
5784
6338
  if (visited.has(edge.target)) continue;
5785
6339
  visited.add(edge.target);
5786
- queue.push({ nodeId: edge.target, path: [...path28, edge.target] });
6340
+ queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
5787
6341
  }
5788
6342
  }
5789
6343
  }
@@ -5801,7 +6355,7 @@ var LLMGovernanceLogger = class {
5801
6355
  }
5802
6356
  /** Path to the JSONL log file. */
5803
6357
  getLogPath() {
5804
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
6358
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path31.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
5805
6359
  }
5806
6360
  /**
5807
6361
  * Append an entry to the governance log.
@@ -5817,8 +6371,8 @@ var LLMGovernanceLogger = class {
5817
6371
  ...entry
5818
6372
  };
5819
6373
  const logPath = this.getLogPath();
5820
- fs19.mkdirSync(path27.dirname(logPath), { recursive: true });
5821
- fs19.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
6374
+ fs24.mkdirSync(path31.dirname(logPath), { recursive: true });
6375
+ fs24.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
5822
6376
  } catch {
5823
6377
  }
5824
6378
  }
@@ -5828,7 +6382,7 @@ var LLMGovernanceLogger = class {
5828
6382
  */
5829
6383
  readLog(limit = 100) {
5830
6384
  try {
5831
- const raw = fs19.readFileSync(this.getLogPath(), "utf-8");
6385
+ const raw = fs24.readFileSync(this.getLogPath(), "utf-8");
5832
6386
  const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
5833
6387
  return lines.map((l) => JSON.parse(l));
5834
6388
  } catch {
@@ -5972,17 +6526,17 @@ function traceFlow(entryId, graph, maxDepth = 10, maxBranching = 4) {
5972
6526
  const queue = [{ nodeId: entryId, path: [entryId] }];
5973
6527
  const visited = /* @__PURE__ */ new Set();
5974
6528
  while (queue.length > 0 && flows.length < maxFlows) {
5975
- const { nodeId, path: path28 } = queue.shift();
5976
- if (path28.length > maxDepth) continue;
6529
+ const { nodeId, path: path32 } = queue.shift();
6530
+ if (path32.length > maxDepth) continue;
5977
6531
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
5978
- if (callEdges.length === 0 && path28.length >= 3) {
5979
- flows.push({ entryPointId: entryId, steps: [...path28] });
6532
+ if (callEdges.length === 0 && path32.length >= 3) {
6533
+ flows.push({ entryPointId: entryId, steps: [...path32] });
5980
6534
  continue;
5981
6535
  }
5982
6536
  for (const edge of callEdges) {
5983
6537
  if (visited.has(edge.target)) continue;
5984
6538
  visited.add(edge.target);
5985
- queue.push({ nodeId: edge.target, path: [...path28, edge.target] });
6539
+ queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
5986
6540
  }
5987
6541
  }
5988
6542
  }
@@ -6216,7 +6770,7 @@ init_embedder();
6216
6770
  async function hybridSearch(graph, query, limit, options = {}) {
6217
6771
  const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
6218
6772
  const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
6219
- const hasVectorDb = Boolean(vectorDbPath && fs19.existsSync(vectorDbPath));
6773
+ const hasVectorDb = Boolean(vectorDbPath && fs24.existsSync(vectorDbPath));
6220
6774
  if (!hasVectorDb) {
6221
6775
  const bm25Results2 = await bm25Promise;
6222
6776
  return {
@@ -6273,7 +6827,7 @@ var DbManager = class {
6273
6827
  this.dbPath = dbPath;
6274
6828
  }
6275
6829
  async init() {
6276
- fs19.mkdirSync(path27.dirname(this.dbPath), { recursive: true });
6830
+ fs24.mkdirSync(path31.dirname(this.dbPath), { recursive: true });
6277
6831
  this.db = new Database(this.dbPath);
6278
6832
  await this.db.init();
6279
6833
  this.conn = new Connection(this.db);
@@ -6363,7 +6917,7 @@ function getCreateEdgeTableDDL() {
6363
6917
  )`];
6364
6918
  }
6365
6919
  function writeNodeCSVs(graph, outputDir) {
6366
- fs19.mkdirSync(outputDir, { recursive: true });
6920
+ fs24.mkdirSync(outputDir, { recursive: true });
6367
6921
  const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
6368
6922
  const tableBuffers = /* @__PURE__ */ new Map();
6369
6923
  const tableFilePaths = /* @__PURE__ */ new Map();
@@ -6371,7 +6925,7 @@ function writeNodeCSVs(graph, outputDir) {
6371
6925
  const table = NODE_TABLE_MAP[node.kind];
6372
6926
  if (!tableBuffers.has(table)) {
6373
6927
  tableBuffers.set(table, [header]);
6374
- tableFilePaths.set(table, path27.join(outputDir, `${table}.csv`));
6928
+ tableFilePaths.set(table, path31.join(outputDir, `${table}.csv`));
6375
6929
  }
6376
6930
  tableBuffers.get(table).push(
6377
6931
  csvRow([
@@ -6391,12 +6945,12 @@ function writeNodeCSVs(graph, outputDir) {
6391
6945
  );
6392
6946
  }
6393
6947
  for (const [table, lines] of tableBuffers) {
6394
- fs19.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
6948
+ fs24.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
6395
6949
  }
6396
6950
  return tableFilePaths;
6397
6951
  }
6398
6952
  function writeEdgeCSV(graph, outputDir) {
6399
- fs19.mkdirSync(outputDir, { recursive: true });
6953
+ fs24.mkdirSync(outputDir, { recursive: true });
6400
6954
  const header = "from_id,to_id,kind,weight,label\n";
6401
6955
  const groups = /* @__PURE__ */ new Map();
6402
6956
  for (const edge of graph.allEdges()) {
@@ -6407,7 +6961,7 @@ function writeEdgeCSV(graph, outputDir) {
6407
6961
  const toTable = NODE_TABLE_MAP[targetNode.kind];
6408
6962
  const key = `${fromTable}->${toTable}`;
6409
6963
  if (!groups.has(key)) {
6410
- const filePath = path27.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
6964
+ const filePath = path31.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
6411
6965
  groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
6412
6966
  }
6413
6967
  groups.get(key).lines.push(
@@ -6422,7 +6976,7 @@ function writeEdgeCSV(graph, outputDir) {
6422
6976
  }
6423
6977
  const result = [];
6424
6978
  for (const group of groups.values()) {
6425
- fs19.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
6979
+ fs24.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
6426
6980
  result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
6427
6981
  }
6428
6982
  return result;
@@ -6450,7 +7004,7 @@ async function loadGraphToDB(graph, dbManager) {
6450
7004
  } catch {
6451
7005
  }
6452
7006
  }
6453
- const tmpDir = fs19.mkdtempSync(path27.join(os12.tmpdir(), "code-intel-csv-"));
7007
+ const tmpDir = fs24.mkdtempSync(path31.join(os12.tmpdir(), "code-intel-csv-"));
6454
7008
  try {
6455
7009
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
6456
7010
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -6469,8 +7023,8 @@ async function loadGraphToDB(graph, dbManager) {
6469
7023
  }
6470
7024
  let nodeCount = 0;
6471
7025
  for (const [table, csvPath] of nodeTableFiles) {
6472
- if (!fs19.existsSync(csvPath)) continue;
6473
- const stat = fs19.statSync(csvPath);
7026
+ if (!fs24.existsSync(csvPath)) continue;
7027
+ const stat = fs24.statSync(csvPath);
6474
7028
  if (stat.size < 50) continue;
6475
7029
  try {
6476
7030
  await dbManager.execute(
@@ -6483,8 +7037,8 @@ async function loadGraphToDB(graph, dbManager) {
6483
7037
  }
6484
7038
  let edgeCount = 0;
6485
7039
  for (const group of edgeGroups) {
6486
- if (!fs19.existsSync(group.filePath)) continue;
6487
- const stat = fs19.statSync(group.filePath);
7040
+ if (!fs24.existsSync(group.filePath)) continue;
7041
+ const stat = fs24.statSync(group.filePath);
6488
7042
  if (stat.size < 50) continue;
6489
7043
  try {
6490
7044
  await dbManager.execute(
@@ -6498,7 +7052,7 @@ async function loadGraphToDB(graph, dbManager) {
6498
7052
  return { nodeCount, edgeCount };
6499
7053
  } finally {
6500
7054
  try {
6501
- fs19.rmSync(tmpDir, { recursive: true, force: true });
7055
+ fs24.rmSync(tmpDir, { recursive: true, force: true });
6502
7056
  } catch {
6503
7057
  }
6504
7058
  }
@@ -6550,19 +7104,19 @@ function buildNodeProps(node) {
6550
7104
  function escCypher(s) {
6551
7105
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
6552
7106
  }
6553
- var GLOBAL_DIR = path27.join(os12.homedir(), ".code-intel");
6554
- var REPOS_FILE = path27.join(GLOBAL_DIR, "repos.json");
7107
+ var GLOBAL_DIR = path31.join(os12.homedir(), ".code-intel");
7108
+ var REPOS_FILE = path31.join(GLOBAL_DIR, "repos.json");
6555
7109
  function loadRegistry() {
6556
7110
  try {
6557
- const data = fs19.readFileSync(REPOS_FILE, "utf-8");
7111
+ const data = fs24.readFileSync(REPOS_FILE, "utf-8");
6558
7112
  return JSON.parse(data);
6559
7113
  } catch {
6560
7114
  return [];
6561
7115
  }
6562
7116
  }
6563
7117
  function saveRegistry(entries) {
6564
- fs19.mkdirSync(GLOBAL_DIR, { recursive: true });
6565
- fs19.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
7118
+ fs24.mkdirSync(GLOBAL_DIR, { recursive: true });
7119
+ fs24.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
6566
7120
  }
6567
7121
  function upsertRepo(entry) {
6568
7122
  const entries = loadRegistry();
@@ -6579,23 +7133,23 @@ function removeRepo(repoPath) {
6579
7133
  saveRegistry(entries);
6580
7134
  }
6581
7135
  function saveMetadata(repoDir, metadata) {
6582
- const metaDir = path27.join(repoDir, ".code-intel");
6583
- fs19.mkdirSync(metaDir, { recursive: true });
6584
- fs19.writeFileSync(path27.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
7136
+ const metaDir = path31.join(repoDir, ".code-intel");
7137
+ fs24.mkdirSync(metaDir, { recursive: true });
7138
+ fs24.writeFileSync(path31.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
6585
7139
  }
6586
7140
  function loadMetadata(repoDir) {
6587
7141
  try {
6588
- const data = fs19.readFileSync(path27.join(repoDir, ".code-intel", "meta.json"), "utf-8");
7142
+ const data = fs24.readFileSync(path31.join(repoDir, ".code-intel", "meta.json"), "utf-8");
6589
7143
  return JSON.parse(data);
6590
7144
  } catch {
6591
7145
  return null;
6592
7146
  }
6593
7147
  }
6594
7148
  function getDbPath(repoDir) {
6595
- return path27.join(repoDir, ".code-intel", "graph.db");
7149
+ return path31.join(repoDir, ".code-intel", "graph.db");
6596
7150
  }
6597
7151
  function getVectorDbPath(repoDir) {
6598
- return path27.join(repoDir, ".code-intel", "vector.db");
7152
+ return path31.join(repoDir, ".code-intel", "vector.db");
6599
7153
  }
6600
7154
 
6601
7155
  // src/mcp-server/server.ts
@@ -6673,6 +7227,205 @@ async function loadGraphFromDB(graph, db) {
6673
7227
 
6674
7228
  // src/multi-repo/group-sync.ts
6675
7229
  init_logger();
7230
+ function scanForFiles(root, matcher, maxDepth = 2) {
7231
+ const results = [];
7232
+ function walk(dir, depth) {
7233
+ if (depth > maxDepth) return;
7234
+ let entries;
7235
+ try {
7236
+ entries = fs24.readdirSync(dir, { withFileTypes: true });
7237
+ } catch {
7238
+ return;
7239
+ }
7240
+ for (const entry of entries) {
7241
+ const full = path31.join(dir, entry.name);
7242
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
7243
+ walk(full, depth + 1);
7244
+ } else if (entry.isFile() && matcher(entry.name)) {
7245
+ results.push(full);
7246
+ }
7247
+ }
7248
+ }
7249
+ walk(root, 0);
7250
+ return results;
7251
+ }
7252
+
7253
+ // src/multi-repo/schema-parsers/openapi-parser.ts
7254
+ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
7255
+ "openapi.yaml",
7256
+ "openapi.json",
7257
+ "openapi.yml",
7258
+ "swagger.yaml",
7259
+ "swagger.json",
7260
+ "swagger.yml"
7261
+ ]);
7262
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
7263
+ function tryParseFile(filePath) {
7264
+ const ext = path31.extname(filePath).toLowerCase();
7265
+ const content = fs24.readFileSync(filePath, "utf-8");
7266
+ if (ext === ".json") {
7267
+ try {
7268
+ return JSON.parse(content);
7269
+ } catch {
7270
+ return null;
7271
+ }
7272
+ }
7273
+ return null;
7274
+ }
7275
+ async function parseOpenAPIContracts(repoRoot) {
7276
+ const files = scanForFiles(repoRoot, (name) => OPENAPI_FILENAMES.has(name));
7277
+ const contracts = [];
7278
+ for (const filePath of files) {
7279
+ const spec = tryParseFile(filePath);
7280
+ if (!spec) continue;
7281
+ const paths = spec["paths"];
7282
+ if (!paths || typeof paths !== "object") continue;
7283
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
7284
+ if (!pathItem || typeof pathItem !== "object") continue;
7285
+ const ops = pathItem;
7286
+ for (const method of HTTP_METHODS) {
7287
+ if (!(method in ops)) continue;
7288
+ const operation = ops[method];
7289
+ if (!operation) continue;
7290
+ const requestBody = operation["requestBody"];
7291
+ const requestSchema = requestBody?.["content"] ? requestBody["content"]["application/json"]?.["schema"] : void 0;
7292
+ const responses = operation["responses"];
7293
+ const ok200 = responses?.["200"];
7294
+ const responseSchema = ok200?.["content"] ? ok200["content"]["application/json"]?.["schema"] : void 0;
7295
+ contracts.push({
7296
+ name: `${method.toUpperCase()} ${pathStr}`,
7297
+ kind: "route",
7298
+ method: method.toUpperCase(),
7299
+ path: pathStr,
7300
+ ...requestSchema ? { requestSchema } : {},
7301
+ ...responseSchema ? { responseSchema } : {},
7302
+ filePath
7303
+ });
7304
+ }
7305
+ }
7306
+ }
7307
+ return contracts;
7308
+ }
7309
+ function extractFieldNames(block) {
7310
+ return block.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
7311
+ const m = line.match(/^(\w+)\s*[(:]/);
7312
+ return m ? m[1] : null;
7313
+ }).filter((f) => f !== null);
7314
+ }
7315
+ async function parseGraphQLContracts(repoRoot) {
7316
+ const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
7317
+ const contracts = [];
7318
+ for (const filePath of files) {
7319
+ const content = fs24.readFileSync(filePath, "utf-8");
7320
+ const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
7321
+ let match;
7322
+ while ((match = typeRegex.exec(content)) !== null) {
7323
+ const typeName = match[1];
7324
+ const body = match[2];
7325
+ const fields = extractFieldNames(body);
7326
+ const lcName = typeName.toLowerCase();
7327
+ if (lcName === "query") {
7328
+ for (const field of fields) {
7329
+ contracts.push({ name: `query.${field}`, kind: "graphql", operation: "query", fields, filePath });
7330
+ }
7331
+ } else if (lcName === "mutation") {
7332
+ for (const field of fields) {
7333
+ contracts.push({ name: `mutation.${field}`, kind: "graphql", operation: "mutation", fields, filePath });
7334
+ }
7335
+ } else if (lcName === "subscription") {
7336
+ for (const field of fields) {
7337
+ contracts.push({ name: `subscription.${field}`, kind: "graphql", operation: "subscription", fields, filePath });
7338
+ }
7339
+ } else {
7340
+ contracts.push({ name: `type.${typeName}`, kind: "graphql", operation: "type", fields, filePath });
7341
+ }
7342
+ }
7343
+ }
7344
+ return contracts;
7345
+ }
7346
+ async function parseProtoContracts(repoRoot) {
7347
+ const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
7348
+ const contracts = [];
7349
+ for (const filePath of files) {
7350
+ const content = fs24.readFileSync(filePath, "utf-8");
7351
+ const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
7352
+ let serviceMatch;
7353
+ while ((serviceMatch = serviceRegex.exec(content)) !== null) {
7354
+ const serviceName = serviceMatch[1];
7355
+ const body = serviceMatch[2];
7356
+ const rpcRegex = /rpc\s+(\w+)\s*\((\w+)\)\s*returns\s*\((\w+)\)/g;
7357
+ let rpcMatch;
7358
+ while ((rpcMatch = rpcRegex.exec(body)) !== null) {
7359
+ const rpcName = rpcMatch[1];
7360
+ const inputType = rpcMatch[2];
7361
+ const outputType = rpcMatch[3];
7362
+ contracts.push({
7363
+ name: `${serviceName}.${rpcName}`,
7364
+ kind: "grpc",
7365
+ serviceName,
7366
+ rpcName,
7367
+ inputType,
7368
+ outputType,
7369
+ filePath
7370
+ });
7371
+ }
7372
+ }
7373
+ }
7374
+ return contracts;
7375
+ }
7376
+
7377
+ // src/multi-repo/type-similarity.ts
7378
+ function normalizeType(t) {
7379
+ return t.toLowerCase().replace(/[\[\]?<>\s]/g, "").trim();
7380
+ }
7381
+ function paramTypeSimilarity(paramsA, paramsB) {
7382
+ if (paramsA.length === 0 && paramsB.length === 0) return 1;
7383
+ if (paramsA.length === 0 || paramsB.length === 0) return 0;
7384
+ const setA = new Set(paramsA.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
7385
+ const setB = new Set(paramsB.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
7386
+ if (setA.size === 0 && setB.size === 0) return 1;
7387
+ if (setA.size === 0 || setB.size === 0) return 0;
7388
+ const intersection = [...setA].filter((x) => setB.has(x)).length;
7389
+ const union = (/* @__PURE__ */ new Set([...setA, ...setB])).size;
7390
+ return intersection / union;
7391
+ }
7392
+ function returnTypeSimilarity(typeA, typeB) {
7393
+ if (!typeA || !typeB) return 0.5;
7394
+ const a = normalizeType(typeA);
7395
+ const b = normalizeType(typeB);
7396
+ if (a === b) return 1;
7397
+ const compatible = [
7398
+ ["string", "str"],
7399
+ ["number", "int"],
7400
+ ["number", "float"],
7401
+ ["number", "double"],
7402
+ ["boolean", "bool"],
7403
+ ["void", "unit"],
7404
+ ["void", "none"]
7405
+ ];
7406
+ for (const [x, y] of compatible) {
7407
+ if (a === x && b === y || a === y && b === x) return 0.8;
7408
+ }
7409
+ return 0;
7410
+ }
7411
+ function paramCountSimilarity(countA, countB) {
7412
+ const maxCount = Math.max(countA, countB, 1);
7413
+ return 1 - Math.abs(countA - countB) / maxCount;
7414
+ }
7415
+ function computeContractSimilarity(a, b, nameSim) {
7416
+ const paramsA = a.parameters ?? [];
7417
+ const paramsB = b.parameters ?? [];
7418
+ const ptSim = paramTypeSimilarity(paramsA, paramsB);
7419
+ const rtSim = returnTypeSimilarity(a.returnType, b.returnType);
7420
+ const pcSim = paramCountSimilarity(paramsA.length, paramsB.length);
7421
+ let score = 0.4 * nameSim + 0.3 * ptSim + 0.2 * rtSim + 0.1 * pcSim;
7422
+ if (ptSim > 0.8) {
7423
+ score = Math.min(1, score * 1.2);
7424
+ }
7425
+ return score;
7426
+ }
7427
+
7428
+ // src/multi-repo/group-sync.ts
6676
7429
  function extractContracts(graph, repoName, repoPath) {
6677
7430
  const contracts = [];
6678
7431
  for (const node of graph.allNodes()) {
@@ -6685,7 +7438,10 @@ function extractContracts(graph, repoName, repoPath) {
6685
7438
  nodeId: node.id,
6686
7439
  nodeKind: node.kind,
6687
7440
  filePath: node.filePath,
6688
- signature: node.content?.split("\n")[0]?.trim()
7441
+ signature: node.content?.split("\n")[0]?.trim(),
7442
+ parameters: node.metadata?.parameters ?? node.metadata?.params,
7443
+ returnType: node.metadata?.returnType,
7444
+ exported: node.exported
6689
7445
  });
6690
7446
  }
6691
7447
  if (node.kind === "route") {
@@ -6747,13 +7503,15 @@ function matchContracts(allContracts) {
6747
7503
  const consumer = consumerByName.get(provider.name);
6748
7504
  if (consumer) {
6749
7505
  const sameKind = provider.kind === consumer.kind;
7506
+ const typedScore = computeContractSimilarity(provider, consumer, 1);
7507
+ const confidence = sameKind ? Math.max(typedScore, 0.9) : Math.max(typedScore, 0.6);
6750
7508
  links.push({
6751
7509
  providerRepo: provider.repoName,
6752
7510
  providerContract: provider.name,
6753
7511
  consumerRepo: consumer.repoName,
6754
7512
  consumerContract: consumer.name,
6755
7513
  matchKind: provider.kind === "route" ? "route-match" : "name-match",
6756
- confidence: sameKind ? 0.9 : 0.6
7514
+ confidence: Math.min(1, confidence)
6757
7515
  });
6758
7516
  } else {
6759
7517
  const providerLC = provider.name.toLowerCase();
@@ -6794,8 +7552,8 @@ async function syncGroup(group) {
6794
7552
  logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
6795
7553
  continue;
6796
7554
  }
6797
- const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
6798
- if (!fs19.existsSync(dbPath)) {
7555
+ const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
7556
+ if (!fs24.existsSync(dbPath)) {
6799
7557
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
6800
7558
  continue;
6801
7559
  }
@@ -6811,6 +7569,44 @@ async function syncGroup(group) {
6811
7569
  continue;
6812
7570
  }
6813
7571
  const contracts = extractContracts(graph, member.registryName, regEntry.path);
7572
+ const [openapiContracts, graphqlContracts, protoContracts] = await Promise.all([
7573
+ parseOpenAPIContracts(regEntry.path).catch(() => []),
7574
+ parseGraphQLContracts(regEntry.path).catch(() => []),
7575
+ parseProtoContracts(regEntry.path).catch(() => [])
7576
+ ]);
7577
+ for (const c of openapiContracts) {
7578
+ contracts.push({
7579
+ repoName: member.registryName,
7580
+ repoPath: regEntry.path,
7581
+ kind: "route",
7582
+ name: c.name,
7583
+ nodeId: `openapi:${c.method}:${c.path}`,
7584
+ nodeKind: "route",
7585
+ filePath: c.filePath
7586
+ });
7587
+ }
7588
+ for (const c of graphqlContracts) {
7589
+ contracts.push({
7590
+ repoName: member.registryName,
7591
+ repoPath: regEntry.path,
7592
+ kind: "graphql",
7593
+ name: c.name,
7594
+ nodeId: `graphql:${c.name}`,
7595
+ nodeKind: "graphql",
7596
+ filePath: c.filePath
7597
+ });
7598
+ }
7599
+ for (const c of protoContracts) {
7600
+ contracts.push({
7601
+ repoName: member.registryName,
7602
+ repoPath: regEntry.path,
7603
+ kind: "grpc",
7604
+ name: c.name,
7605
+ nodeId: `grpc:${c.serviceName}:${c.rpcName}`,
7606
+ nodeKind: "grpc",
7607
+ filePath: c.filePath
7608
+ });
7609
+ }
6814
7610
  logger_default.info(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
6815
7611
  allContracts.push(...contracts);
6816
7612
  }
@@ -6830,8 +7626,8 @@ async function queryGroup(group, query, limit = 20) {
6830
7626
  for (const member of group.members) {
6831
7627
  const regEntry = registry.find((r) => r.name === member.registryName);
6832
7628
  if (!regEntry) continue;
6833
- const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
6834
- if (!fs19.existsSync(dbPath)) continue;
7629
+ const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
7630
+ if (!fs24.existsSync(dbPath)) continue;
6835
7631
  const graph = createKnowledgeGraph();
6836
7632
  const db = new DbManager(dbPath);
6837
7633
  try {
@@ -7331,22 +8127,22 @@ function suggestTests(graph, symbolName) {
7331
8127
  const callPaths = [];
7332
8128
  const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
7333
8129
  while (pathQueue.length > 0 && callPaths.length < 5) {
7334
- const { id, path: path28, depth } = pathQueue.shift();
7335
- let hasCallers = false;
8130
+ const { id, path: path32, depth } = pathQueue.shift();
8131
+ let hasCallers2 = false;
7336
8132
  for (const edge of graph.findEdgesTo(id)) {
7337
8133
  if (edge.kind !== "calls") continue;
7338
8134
  const callerNode = graph.getNode(edge.source);
7339
8135
  if (!callerNode) continue;
7340
- hasCallers = true;
7341
- const newPath = [callerNode.name, ...path28];
8136
+ hasCallers2 = true;
8137
+ const newPath = [callerNode.name, ...path32];
7342
8138
  if (depth + 1 >= 3 || callPaths.length >= 5) {
7343
8139
  if (callPaths.length < 5) callPaths.push(newPath);
7344
8140
  continue;
7345
8141
  }
7346
8142
  pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
7347
8143
  }
7348
- if (!hasCallers && path28.length > 1) {
7349
- callPaths.push(path28);
8144
+ if (!hasCallers2 && path32.length > 1) {
8145
+ callPaths.push(path32);
7350
8146
  }
7351
8147
  }
7352
8148
  if (callPaths.length === 0) {
@@ -7821,6 +8617,70 @@ function createMcpServer(graph, repoName, workspaceRoot) {
7821
8617
  },
7822
8618
  required: ["cluster"]
7823
8619
  }
8620
+ },
8621
+ {
8622
+ name: "deprecated_usage",
8623
+ description: "Find usages of deprecated APIs in the codebase",
8624
+ inputSchema: {
8625
+ type: "object",
8626
+ properties: {
8627
+ scope: { type: "string", description: "Directory scope filter" },
8628
+ ..._tokenProp
8629
+ }
8630
+ }
8631
+ },
8632
+ {
8633
+ name: "complexity_hotspots",
8634
+ description: "Ranked list of functions/methods by cyclomatic complexity. Useful for identifying refactoring candidates.",
8635
+ inputSchema: {
8636
+ type: "object",
8637
+ properties: {
8638
+ scope: { type: "string", description: "Limit to a file path prefix (optional)" },
8639
+ limit: { type: "number", description: "Maximum number of results (default: 20)" },
8640
+ ..._tokenProp
8641
+ }
8642
+ }
8643
+ },
8644
+ {
8645
+ name: "coverage_gaps",
8646
+ description: "Find exported symbols with no test coverage, ranked by blast radius. Useful for prioritizing test writing.",
8647
+ inputSchema: {
8648
+ type: "object",
8649
+ properties: {
8650
+ scope: { type: "string", description: "Limit to a file path prefix (optional)" },
8651
+ limit: { type: "number", description: "Maximum number of untested results to return (default: 20)" },
8652
+ ..._tokenProp
8653
+ }
8654
+ }
8655
+ },
8656
+ {
8657
+ name: "secrets",
8658
+ description: "Scan the knowledge graph for hardcoded secrets: API keys, passwords, tokens, private keys, high-entropy strings",
8659
+ inputSchema: {
8660
+ type: "object",
8661
+ properties: {
8662
+ scope: { type: "string", description: "Limit scan to files under this path prefix" },
8663
+ includeTestFiles: { type: "boolean", description: "Include test/spec/fixture files (default: false)" },
8664
+ ..._tokenProp
8665
+ }
8666
+ }
8667
+ },
8668
+ {
8669
+ name: "vulnerability_scan",
8670
+ description: "Scan the knowledge graph for OWASP vulnerabilities: SQL injection, XSS, SSRF, path traversal, command injection",
8671
+ inputSchema: {
8672
+ type: "object",
8673
+ properties: {
8674
+ scope: { type: "string", description: "Limit scan to files under this path prefix" },
8675
+ types: {
8676
+ type: "array",
8677
+ items: { type: "string", enum: ["SQL_INJECTION", "XSS", "SSRF", "PATH_TRAVERSAL", "COMMAND_INJECTION"] },
8678
+ description: "Vulnerability types to detect (default: all)"
8679
+ },
8680
+ severity: { type: "string", description: "Minimum severity to report: HIGH|MEDIUM|LOW (default: LOW)" },
8681
+ ..._tokenProp
8682
+ }
8683
+ }
7824
8684
  }
7825
8685
  ]
7826
8686
  }));
@@ -8244,7 +9104,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
8244
9104
  for (const { filePath: changedFile, changedLines } of changedFiles) {
8245
9105
  for (const node of graph.allNodes()) {
8246
9106
  if (!node.filePath) continue;
8247
- const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path27.sep, "");
9107
+ const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path31.sep, "");
8248
9108
  const normChanged = changedFile.replace(/^a\/|^b\//, "");
8249
9109
  if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
8250
9110
  if (node.startLine !== void 0 && node.endLine !== void 0) {
@@ -8517,6 +9377,63 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
8517
9377
  const result = summarizeCluster(graph, cluster);
8518
9378
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
8519
9379
  }
9380
+ case "deprecated_usage": {
9381
+ const scope = a.scope;
9382
+ const { DeprecatedDetector: DeprecatedDetector2 } = await Promise.resolve().then(() => (init_deprecated_detector(), deprecated_detector_exports));
9383
+ const detector = new DeprecatedDetector2();
9384
+ detector.tagDeprecated(graph);
9385
+ const findings = detector.detect(graph, scope);
9386
+ return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
9387
+ }
9388
+ // ── complexity_hotspots ────────────────────────────────────────────────
9389
+ case "complexity_hotspots": {
9390
+ const { computeComplexity: computeComplexity2 } = await Promise.resolve().then(() => (init_complexity(), complexity_exports));
9391
+ const scope = a.scope;
9392
+ const limit = typeof a.limit === "number" ? a.limit : 20;
9393
+ const hotspots = computeComplexity2(graph, scope).slice(0, limit);
9394
+ return { content: [{ type: "text", text: JSON.stringify({ hotspots, total: hotspots.length }, null, 2) }] };
9395
+ }
9396
+ // ── coverage_gaps ──────────────────────────────────────────────────────
9397
+ case "coverage_gaps": {
9398
+ const { computeCoverage: computeCoverage2 } = await Promise.resolve().then(() => (init_test_coverage(), test_coverage_exports));
9399
+ const scope = a.scope;
9400
+ const limit = typeof a.limit === "number" ? a.limit : 20;
9401
+ const summary = computeCoverage2(graph, scope);
9402
+ const untestedByRisk = summary.untestedByRisk.slice(0, limit);
9403
+ return {
9404
+ content: [{
9405
+ type: "text",
9406
+ text: JSON.stringify({
9407
+ untestedByRisk,
9408
+ coveragePct: summary.coveragePct,
9409
+ totalExported: summary.totalExported,
9410
+ testedExported: summary.testedExported
9411
+ }, null, 2)
9412
+ }]
9413
+ };
9414
+ }
9415
+ // ── secrets ────────────────────────────────────────────────────────────
9416
+ case "secrets": {
9417
+ const { SecretScanner: SecretScanner2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
9418
+ const scanner = new SecretScanner2();
9419
+ const scope = a.scope;
9420
+ const includeTestFiles = a.includeTestFiles ?? false;
9421
+ const findings = scanner.scan(graph, { scope, includeTestFiles });
9422
+ return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
9423
+ }
9424
+ // ── vulnerability_scan ─────────────────────────────────────────────────
9425
+ case "vulnerability_scan": {
9426
+ const { VulnerabilityDetector: VulnerabilityDetector2 } = await Promise.resolve().then(() => (init_vulnerability_detector(), vulnerability_detector_exports));
9427
+ const detector = new VulnerabilityDetector2();
9428
+ const scope = a.scope;
9429
+ const types = a.types;
9430
+ const minSev = (a.severity ?? "LOW").toUpperCase();
9431
+ const sevRank = { HIGH: 3, MEDIUM: 2, LOW: 1 };
9432
+ const minRank = sevRank[minSev] ?? 1;
9433
+ let findings = detector.detect(graph, { scope, types });
9434
+ findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
9435
+ return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
9436
+ }
8520
9437
  default:
8521
9438
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
8522
9439
  }
@@ -8610,7 +9527,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
8610
9527
  var JobsDB = class {
8611
9528
  db;
8612
9529
  constructor(dbPath) {
8613
- fs19.mkdirSync(path27.dirname(dbPath), { recursive: true });
9530
+ fs24.mkdirSync(path31.dirname(dbPath), { recursive: true });
8614
9531
  this.db = new Database3(dbPath);
8615
9532
  this.db.pragma("journal_mode = WAL");
8616
9533
  this.db.pragma("foreign_keys = ON");
@@ -8752,7 +9669,7 @@ var JobsDB = class {
8752
9669
  }
8753
9670
  };
8754
9671
  function getJobsDBPath() {
8755
- return path27.join(os12.homedir(), ".code-intel", "jobs.db");
9672
+ return path31.join(os12.homedir(), ".code-intel", "jobs.db");
8756
9673
  }
8757
9674
  var _jobsDB = null;
8758
9675
  function getOrCreateJobsDB() {
@@ -8844,7 +9761,7 @@ var BACKUP_VERSION = "1.0";
8844
9761
  var ALGORITHM = "aes-256-gcm";
8845
9762
  var IV_LENGTH = 16;
8846
9763
  function getBackupDir() {
8847
- return path27.join(os12.homedir(), ".code-intel", "backups");
9764
+ return path31.join(os12.homedir(), ".code-intel", "backups");
8848
9765
  }
8849
9766
  function getBackupKey() {
8850
9767
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
@@ -8875,30 +9792,30 @@ var BackupService = class {
8875
9792
  constructor(backupDir) {
8876
9793
  this.backupDir = backupDir ?? getBackupDir();
8877
9794
  this.key = getBackupKey();
8878
- fs19.mkdirSync(this.backupDir, { recursive: true });
9795
+ fs24.mkdirSync(this.backupDir, { recursive: true });
8879
9796
  }
8880
9797
  /**
8881
9798
  * Create a backup for a repository.
8882
9799
  * Returns the backup entry.
8883
9800
  */
8884
9801
  createBackup(repoPath) {
8885
- const codeIntelDir = path27.join(repoPath, ".code-intel");
9802
+ const codeIntelDir = path31.join(repoPath, ".code-intel");
8886
9803
  const id = v4();
8887
9804
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
8888
9805
  const filesToBackup = [];
8889
9806
  const candidates = ["graph.db", "vector.db", "meta.json"];
8890
9807
  for (const f of candidates) {
8891
- const fp = path27.join(codeIntelDir, f);
8892
- if (fs19.existsSync(fp)) {
9808
+ const fp = path31.join(codeIntelDir, f);
9809
+ if (fs24.existsSync(fp)) {
8893
9810
  filesToBackup.push({ name: f, localPath: fp });
8894
9811
  }
8895
9812
  }
8896
- const registryPath = path27.join(os12.homedir(), ".code-intel", "registry.json");
8897
- if (fs19.existsSync(registryPath)) {
9813
+ const registryPath = path31.join(os12.homedir(), ".code-intel", "registry.json");
9814
+ if (fs24.existsSync(registryPath)) {
8898
9815
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
8899
9816
  }
8900
- const usersDbPath = path27.join(os12.homedir(), ".code-intel", "users.db");
8901
- if (fs19.existsSync(usersDbPath)) {
9817
+ const usersDbPath = path31.join(os12.homedir(), ".code-intel", "users.db");
9818
+ if (fs24.existsSync(usersDbPath)) {
8902
9819
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
8903
9820
  }
8904
9821
  if (filesToBackup.length === 0) {
@@ -8909,7 +9826,7 @@ var BackupService = class {
8909
9826
  createdAt,
8910
9827
  version: BACKUP_VERSION,
8911
9828
  files: filesToBackup.map((f) => {
8912
- const data = fs19.readFileSync(f.localPath);
9829
+ const data = fs24.readFileSync(f.localPath);
8913
9830
  return {
8914
9831
  name: f.name,
8915
9832
  sha256: crypto5.createHash("sha256").update(data).digest("hex"),
@@ -8923,7 +9840,7 @@ var BackupService = class {
8923
9840
  manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
8924
9841
  parts.push(manifestLenBuf, manifestBuf);
8925
9842
  for (const f of filesToBackup) {
8926
- const data = fs19.readFileSync(f.localPath);
9843
+ const data = fs24.readFileSync(f.localPath);
8927
9844
  const nameBuf = Buffer.from(f.name, "utf-8");
8928
9845
  const nameLenBuf = Buffer.alloc(2);
8929
9846
  nameLenBuf.writeUInt16BE(nameBuf.length, 0);
@@ -8934,8 +9851,8 @@ var BackupService = class {
8934
9851
  const plaintext = Buffer.concat(parts);
8935
9852
  const encrypted = encryptBuffer(plaintext, this.key);
8936
9853
  const backupFileName = `backup-${id}.cib`;
8937
- const backupPath = path27.join(this.backupDir, backupFileName);
8938
- fs19.writeFileSync(backupPath, encrypted);
9854
+ const backupPath = path31.join(this.backupDir, backupFileName);
9855
+ fs24.writeFileSync(backupPath, encrypted);
8939
9856
  const entry = {
8940
9857
  id,
8941
9858
  createdAt,
@@ -8962,9 +9879,9 @@ var BackupService = class {
8962
9879
  async uploadToS3(entry) {
8963
9880
  const cfg = getS3Config();
8964
9881
  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.");
8965
- const fileName = path27.basename(entry.path);
9882
+ const fileName = path31.basename(entry.path);
8966
9883
  const s3Key = `${cfg.prefix}${fileName}`;
8967
- const body = fs19.readFileSync(entry.path);
9884
+ const body = fs24.readFileSync(entry.path);
8968
9885
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
8969
9886
  if (result.statusCode < 200 || result.statusCode >= 300) {
8970
9887
  throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
@@ -8981,8 +9898,8 @@ var BackupService = class {
8981
9898
  if (result.statusCode < 200 || result.statusCode >= 300) {
8982
9899
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
8983
9900
  }
8984
- fs19.mkdirSync(path27.dirname(destPath), { recursive: true });
8985
- fs19.writeFileSync(destPath, Buffer.from(result.body, "binary"));
9901
+ fs24.mkdirSync(path31.dirname(destPath), { recursive: true });
9902
+ fs24.writeFileSync(destPath, Buffer.from(result.body, "binary"));
8986
9903
  }
8987
9904
  /**
8988
9905
  * List backup objects in S3 with the configured prefix.
@@ -9028,10 +9945,10 @@ var BackupService = class {
9028
9945
  if (!entry) {
9029
9946
  throw new Error(`Backup "${backupId}" not found.`);
9030
9947
  }
9031
- if (!fs19.existsSync(entry.path)) {
9948
+ if (!fs24.existsSync(entry.path)) {
9032
9949
  throw new Error(`Backup file not found at: ${entry.path}`);
9033
9950
  }
9034
- const encrypted = fs19.readFileSync(entry.path);
9951
+ const encrypted = fs24.readFileSync(entry.path);
9035
9952
  let plaintext;
9036
9953
  try {
9037
9954
  plaintext = decryptBuffer(encrypted, this.key);
@@ -9045,8 +9962,8 @@ var BackupService = class {
9045
9962
  offset += manifestLen;
9046
9963
  const manifest = JSON.parse(manifestStr);
9047
9964
  const restoreBase = targetRepoPath ?? entry.repoPath;
9048
- const codeIntelDir = path27.join(restoreBase, ".code-intel");
9049
- fs19.mkdirSync(codeIntelDir, { recursive: true });
9965
+ const codeIntelDir = path31.join(restoreBase, ".code-intel");
9966
+ fs24.mkdirSync(codeIntelDir, { recursive: true });
9050
9967
  for (const fileEntry of manifest.files) {
9051
9968
  const nameLen = plaintext.readUInt16BE(offset);
9052
9969
  offset += 2;
@@ -9063,18 +9980,18 @@ var BackupService = class {
9063
9980
  }
9064
9981
  let destPath;
9065
9982
  if (name === "registry.json" || name === "users.db") {
9066
- destPath = path27.join(os12.homedir(), ".code-intel", name);
9983
+ destPath = path31.join(os12.homedir(), ".code-intel", name);
9067
9984
  } else {
9068
- destPath = path27.join(codeIntelDir, name);
9985
+ destPath = path31.join(codeIntelDir, name);
9069
9986
  }
9070
- fs19.writeFileSync(destPath, data);
9987
+ fs24.writeFileSync(destPath, data);
9071
9988
  }
9072
9989
  }
9073
9990
  /**
9074
9991
  * Apply retention policy: keep N daily, M weekly, L monthly backups.
9075
9992
  */
9076
9993
  applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
9077
- const entries = this._loadIndex().filter((e) => fs19.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
9994
+ const entries = this._loadIndex().filter((e) => fs24.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
9078
9995
  const keep = /* @__PURE__ */ new Set();
9079
9996
  const now = /* @__PURE__ */ new Date();
9080
9997
  const dailyCutoff = new Date(now);
@@ -9104,7 +10021,7 @@ var BackupService = class {
9104
10021
  for (const e of entries) {
9105
10022
  if (!keep.has(e.id)) {
9106
10023
  try {
9107
- fs19.unlinkSync(e.path);
10024
+ fs24.unlinkSync(e.path);
9108
10025
  deleted++;
9109
10026
  } catch {
9110
10027
  }
@@ -9116,17 +10033,17 @@ var BackupService = class {
9116
10033
  }
9117
10034
  // ── Index helpers ──────────────────────────────────────────────────────────
9118
10035
  _indexPath() {
9119
- return path27.join(this.backupDir, "index.json");
10036
+ return path31.join(this.backupDir, "index.json");
9120
10037
  }
9121
10038
  _loadIndex() {
9122
10039
  try {
9123
- return JSON.parse(fs19.readFileSync(this._indexPath(), "utf-8"));
10040
+ return JSON.parse(fs24.readFileSync(this._indexPath(), "utf-8"));
9124
10041
  } catch {
9125
10042
  return [];
9126
10043
  }
9127
10044
  }
9128
10045
  _saveIndex(entries) {
9129
- fs19.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
10046
+ fs24.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
9130
10047
  }
9131
10048
  _appendIndex(entry) {
9132
10049
  const entries = this._loadIndex();
@@ -9785,6 +10702,30 @@ var openApiSpec = {
9785
10702
  }
9786
10703
  }
9787
10704
  },
10705
+ "/groups/{name}/topology": {
10706
+ get: {
10707
+ tags: ["Groups"],
10708
+ summary: "Get the topology of repos and cross-repo contract edges for a group",
10709
+ parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" } }],
10710
+ responses: {
10711
+ "200": {
10712
+ description: "Repos and cross-repo edges",
10713
+ content: {
10714
+ "application/json": {
10715
+ schema: {
10716
+ type: "object",
10717
+ properties: {
10718
+ repos: { type: "array", items: { type: "object", properties: { name: { type: "string" }, groupPath: { type: "string" }, nodeCount: { type: "integer" }, edgeCount: { type: "integer" } } } },
10719
+ edges: { type: "array", items: { type: "object", properties: { source: { type: "string" }, target: { type: "string" }, contractName: { type: "string" }, confidence: { type: "number" }, kind: { type: "string" } } } }
10720
+ }
10721
+ }
10722
+ }
10723
+ }
10724
+ },
10725
+ "404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
10726
+ }
10727
+ }
10728
+ },
9788
10729
  "/query": {
9789
10730
  post: {
9790
10731
  tags: ["GQL"],
@@ -9895,11 +10836,11 @@ var openApiSpec = {
9895
10836
  };
9896
10837
 
9897
10838
  // src/http/app.ts
9898
- var __dirname$1 = path27.dirname(fileURLToPath(import.meta.url));
10839
+ var __dirname$1 = path31.dirname(fileURLToPath(import.meta.url));
9899
10840
  var WEB_DIST = (() => {
9900
- const bundled = path27.resolve(__dirname$1, "..", "web");
9901
- if (fs19.existsSync(bundled)) return bundled;
9902
- return path27.resolve(__dirname$1, "..", "..", "..", "web", "dist");
10841
+ const bundled = path31.resolve(__dirname$1, "..", "web");
10842
+ if (fs24.existsSync(bundled)) return bundled;
10843
+ return path31.resolve(__dirname$1, "..", "..", "..", "web", "dist");
9903
10844
  })();
9904
10845
  function getAllowedOrigins() {
9905
10846
  const env = process.env["CODE_INTEL_CORS_ORIGINS"];
@@ -10430,8 +11371,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10430
11371
  const registry = loadRegistry();
10431
11372
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
10432
11373
  if (!entry) return null;
10433
- const dbPath = path27.join(entry.path, ".code-intel", "graph.db");
10434
- if (!fs19.existsSync(dbPath)) return null;
11374
+ const dbPath = path31.join(entry.path, ".code-intel", "graph.db");
11375
+ if (!fs24.existsSync(dbPath)) return null;
10435
11376
  const repoGraph = createKnowledgeGraph();
10436
11377
  const db = new DbManager(dbPath);
10437
11378
  try {
@@ -10518,7 +11459,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10518
11459
  return;
10519
11460
  }
10520
11461
  try {
10521
- const content = fs19.readFileSync(file_path, "utf-8");
11462
+ const content = fs24.readFileSync(file_path, "utf-8");
10522
11463
  res.json({ content });
10523
11464
  } catch {
10524
11465
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
@@ -10776,8 +11717,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10776
11717
  for (const member of group.members) {
10777
11718
  const regEntry = registry.find((r) => r.name === member.registryName);
10778
11719
  if (!regEntry) continue;
10779
- const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
10780
- if (!fs19.existsSync(dbPath)) continue;
11720
+ const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
11721
+ if (!fs24.existsSync(dbPath)) continue;
10781
11722
  const db = new DbManager(dbPath);
10782
11723
  try {
10783
11724
  await db.init();
@@ -10789,6 +11730,45 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10789
11730
  }
10790
11731
  res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
10791
11732
  });
11733
+ app.get("/api/v1/groups/:name/topology", requireAuth, requireRole("viewer"), async (req, res) => {
11734
+ const groupName = req.params["name"];
11735
+ const group = loadGroup(groupName);
11736
+ if (!group) {
11737
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
11738
+ return;
11739
+ }
11740
+ const syncResult = loadSyncResult(groupName);
11741
+ const registry = loadRegistry();
11742
+ const repos = await Promise.all(group.members.map(async (member) => {
11743
+ const regEntry = registry.find((r) => r.name === member.registryName);
11744
+ let nodeCount = 0;
11745
+ let edgeCount = 0;
11746
+ if (regEntry) {
11747
+ const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
11748
+ if (fs24.existsSync(dbPath)) {
11749
+ try {
11750
+ const db = new DbManager(dbPath);
11751
+ await db.init();
11752
+ const g = createKnowledgeGraph();
11753
+ await loadGraphFromDB(g, db);
11754
+ db.close();
11755
+ nodeCount = g.size.nodes;
11756
+ edgeCount = g.size.edges;
11757
+ } catch {
11758
+ }
11759
+ }
11760
+ }
11761
+ return { name: member.registryName, groupPath: member.groupPath, nodeCount, edgeCount };
11762
+ }));
11763
+ const edges = syncResult ? syncResult.links.map((link) => ({
11764
+ source: link.providerRepo,
11765
+ target: link.consumerRepo,
11766
+ contractName: link.providerContract,
11767
+ confidence: link.confidence,
11768
+ kind: "contract"
11769
+ })) : [];
11770
+ res.json({ repos, edges });
11771
+ });
10792
11772
  app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
10793
11773
  const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
10794
11774
  if (!file) {
@@ -10814,14 +11794,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10814
11794
  });
10815
11795
  return;
10816
11796
  }
10817
- let rawResolved = path27.normalize(file);
10818
- if (!path27.isAbsolute(rawResolved) && workspaceRoot) {
10819
- rawResolved = path27.join(workspaceRoot, rawResolved);
11797
+ let rawResolved = path31.normalize(file);
11798
+ if (!path31.isAbsolute(rawResolved) && workspaceRoot) {
11799
+ rawResolved = path31.join(workspaceRoot, rawResolved);
10820
11800
  }
10821
- const resolvedFile = path27.resolve(rawResolved);
11801
+ const resolvedFile = path31.resolve(rawResolved);
10822
11802
  function isInsideDir(fileAbs, dir) {
10823
- const rel = path27.relative(path27.resolve(dir), fileAbs);
10824
- return !rel.startsWith("..") && !path27.isAbsolute(rel);
11803
+ const rel = path31.relative(path31.resolve(dir), fileAbs);
11804
+ return !rel.startsWith("..") && !path31.isAbsolute(rel);
10825
11805
  }
10826
11806
  if (workspaceRoot) {
10827
11807
  if (!isInsideDir(resolvedFile, workspaceRoot)) {
@@ -10858,7 +11838,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10858
11838
  }
10859
11839
  let fileContent;
10860
11840
  try {
10861
- fileContent = fs19.readFileSync(resolvedFile, "utf-8");
11841
+ fileContent = fs24.readFileSync(resolvedFile, "utf-8");
10862
11842
  } catch {
10863
11843
  res.status(404).json({
10864
11844
  error: {
@@ -10889,7 +11869,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10889
11869
  const contextStart = Math.max(1, startLine - 20);
10890
11870
  const contextEnd = Math.min(lines.length, endLine + 20);
10891
11871
  const content = lines.slice(contextStart - 1, contextEnd).join("\n");
10892
- const ext = path27.extname(resolvedFile).toLowerCase();
11872
+ const ext = path31.extname(resolvedFile).toLowerCase();
10893
11873
  const languageMap = {
10894
11874
  ".ts": "typescript",
10895
11875
  ".tsx": "typescript",
@@ -11024,10 +12004,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11024
12004
  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() } });
11025
12005
  }
11026
12006
  });
11027
- if (fs19.existsSync(WEB_DIST)) {
12007
+ if (fs24.existsSync(WEB_DIST)) {
11028
12008
  app.use(express.static(WEB_DIST));
11029
12009
  app.get("/{*path}", (_req, res) => {
11030
- res.sendFile(path27.join(WEB_DIST, "index.html"));
12010
+ res.sendFile(path31.join(WEB_DIST, "index.html"));
11031
12011
  });
11032
12012
  }
11033
12013
  app.use("/admin", requireRole("admin"));