@vohongtho.infotech/code-intel 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 path29 from 'path';
4
+ import fs23, { 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 = path29.dirname(fileURLToPath(import.meta.url));
130
130
  const candidates = [
131
- path27.join(fileDir, "wasm"),
131
+ path29.join(fileDir, "wasm"),
132
132
  // dist/index.js → dist/wasm/
133
- path27.join(fileDir, "../wasm")
133
+ path29.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 = path29.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 path30 = wasmPath(lang);
188
+ if (!path30) {
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(path30);
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 = path29.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 (!fs23.existsSync(_Logger.LOG_DIR)) {
1175
+ fs23.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: path29.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 fs23.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 = path29.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 = path29.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 = path29.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 = path29.basename(rel, path29.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 = path29.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 = path29.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 = path29.join(fromDir, cleanedNoJs + ext);
2275
+ const normalized = path29.normalize(candidate);
2276
2276
  if (fileIndex.has(normalized)) {
2277
2277
  const absPath = fileIndex.get(normalized);
2278
- resolvedRelPath = path27.relative(workspaceRoot, absPath);
2278
+ resolvedRelPath = path29.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 path29.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(fs23.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
+ fs23.mkdirSync(GROUPS_DIR, { recursive: true });
2603
+ fs23.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 fs23.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
+ fs23.readFileSync(path29.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
+ fs23.unlinkSync(groupFile(name));
2625
2625
  } catch {
2626
2626
  }
2627
2627
  try {
2628
- fs19.unlinkSync(path27.join(GROUPS_DIR, `${name}.sync.json`));
2628
+ fs23.unlinkSync(path29.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 fs23.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
+ fs23.mkdirSync(GROUPS_DIR, { recursive: true });
2660
+ fs23.writeFileSync(
2661
+ path29.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
+ fs23.readFileSync(path29.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 = path29.join(os12.homedir(), ".code-intel", "groups");
2678
2678
  }
2679
2679
  });
2680
2680
 
@@ -3735,10 +3735,10 @@ var init_codes = __esm({
3735
3735
  }
3736
3736
  });
3737
3737
  function secureMkdir(dir) {
3738
- fs19.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
3738
+ fs23.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
3739
3739
  if (process.platform !== "win32") {
3740
3740
  try {
3741
- fs19.chmodSync(dir, SECURE_DIR_MODE);
3741
+ fs23.chmodSync(dir, SECURE_DIR_MODE);
3742
3742
  } catch {
3743
3743
  }
3744
3744
  }
@@ -3746,17 +3746,17 @@ function secureMkdir(dir) {
3746
3746
  function secureChmodFile(file) {
3747
3747
  if (process.platform === "win32") return;
3748
3748
  try {
3749
- fs19.chmodSync(file, SECURE_FILE_MODE);
3749
+ fs23.chmodSync(file, SECURE_FILE_MODE);
3750
3750
  } catch {
3751
3751
  }
3752
3752
  }
3753
3753
  function tightenDbFiles(dir) {
3754
3754
  if (process.platform === "win32") return;
3755
- if (!fs19.existsSync(dir)) return;
3756
- for (const name of fs19.readdirSync(dir)) {
3755
+ if (!fs23.existsSync(dir)) return;
3756
+ for (const name of fs23.readdirSync(dir)) {
3757
3757
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
3758
3758
  try {
3759
- fs19.chmodSync(path27.join(dir, name), SECURE_FILE_MODE);
3759
+ fs23.chmodSync(path29.join(dir, name), SECURE_FILE_MODE);
3760
3760
  } catch {
3761
3761
  }
3762
3762
  }
@@ -3770,7 +3770,7 @@ var init_fs_secure = __esm({
3770
3770
  }
3771
3771
  });
3772
3772
  function getUsersDBPath() {
3773
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "users.db");
3773
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path29.join(os12.homedir(), ".code-intel", "users.db");
3774
3774
  }
3775
3775
  function getOrCreateUsersDB() {
3776
3776
  if (!_usersDB) {
@@ -3786,7 +3786,7 @@ var init_users_db = __esm({
3786
3786
  UsersDB = class {
3787
3787
  db;
3788
3788
  constructor(dbPath) {
3789
- const dir = path27.dirname(dbPath);
3789
+ const dir = path29.dirname(dbPath);
3790
3790
  secureMkdir(dir);
3791
3791
  this.db = new Database3(dbPath);
3792
3792
  this.db.pragma("journal_mode = WAL");
@@ -4063,7 +4063,7 @@ function getScryptN() {
4063
4063
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
4064
4064
  }
4065
4065
  function getSecretsPath() {
4066
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path27.join(os12.homedir(), ".code-intel", ".secrets");
4066
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path29.join(os12.homedir(), ".code-intel", ".secrets");
4067
4067
  }
4068
4068
  function getMasterPassword() {
4069
4069
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
@@ -4094,8 +4094,8 @@ function decryptSecrets(encrypted) {
4094
4094
  return JSON.parse(plaintext.toString("utf8"));
4095
4095
  }
4096
4096
  function loadSecrets(secretsPath = getSecretsPath()) {
4097
- if (!fs19.existsSync(secretsPath)) return {};
4098
- const blob = fs19.readFileSync(secretsPath);
4097
+ if (!fs23.existsSync(secretsPath)) return {};
4098
+ const blob = fs23.readFileSync(secretsPath);
4099
4099
  return decryptSecrets(blob);
4100
4100
  }
4101
4101
  function getSecret(key, secretsPath = getSecretsPath()) {
@@ -4214,6 +4214,9 @@ function requireAuth(req, res, next) {
4214
4214
  }
4215
4215
  next();
4216
4216
  }
4217
+ function meetsRole(userRole, required) {
4218
+ return (ROLE_RANK[userRole] ?? 0) >= (ROLE_RANK[required] ?? 0);
4219
+ }
4217
4220
  function requireRole(...roles) {
4218
4221
  return (req, res, next) => {
4219
4222
  if (!req.user) {
@@ -4228,7 +4231,8 @@ function requireRole(...roles) {
4228
4231
  });
4229
4232
  return;
4230
4233
  }
4231
- if (!roles.includes(req.user.role)) {
4234
+ const allowed = roles.some((r) => meetsRole(req.user.role, r));
4235
+ if (!allowed) {
4232
4236
  res.status(403).json({
4233
4237
  error: {
4234
4238
  code: ErrorCodes.FORBIDDEN,
@@ -4337,7 +4341,7 @@ function clearSessionCookie() {
4337
4341
  async function verifyPassword(plain, hash) {
4338
4342
  return bcrypt.compare(plain, hash);
4339
4343
  }
4340
- var sessionStore, SESSION_COOKIE_NAME;
4344
+ var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
4341
4345
  var init_middleware = __esm({
4342
4346
  "src/auth/middleware.ts"() {
4343
4347
  init_users_db();
@@ -4345,6 +4349,12 @@ var init_middleware = __esm({
4345
4349
  init_secret_store();
4346
4350
  sessionStore = /* @__PURE__ */ new Map();
4347
4351
  SESSION_COOKIE_NAME = "code_intel_session";
4352
+ ROLE_RANK = {
4353
+ viewer: 1,
4354
+ "repo-owner": 2,
4355
+ analyst: 3,
4356
+ admin: 4
4357
+ };
4348
4358
  }
4349
4359
  });
4350
4360
  function verifyWebSocketHandshake(req) {
@@ -4652,7 +4662,7 @@ init_shared();
4652
4662
  init_shared();
4653
4663
  init_typescript();
4654
4664
  function resolveRelative(rawPath, fromFile, workspace) {
4655
- const fromDir = path27.dirname(fromFile);
4665
+ const fromDir = path29.dirname(fromFile);
4656
4666
  const cleaned = rawPath.replace(/['"]/g, "");
4657
4667
  const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
4658
4668
  const resolved = workspace.resolve(fromDir, cleaned);
@@ -4704,7 +4714,7 @@ var pythonModule = {
4704
4714
  resolveImport(rawPath, fromFile, workspace) {
4705
4715
  const cleaned = rawPath.replace(/['"]/g, "");
4706
4716
  const parts = cleaned.split(".");
4707
- const fromDir = path27.dirname(fromFile);
4717
+ const fromDir = path29.dirname(fromFile);
4708
4718
  const relPath = parts.join("/");
4709
4719
  for (const suffix of ["/__init__.py", ".py"]) {
4710
4720
  const r = workspace.resolve(fromDir, relPath + suffix);
@@ -4783,7 +4793,7 @@ var cModule = {
4783
4793
  inheritanceStrategy: "none",
4784
4794
  resolveImport(rawPath, fromFile, workspace) {
4785
4795
  const cleaned = rawPath.replace(/[<>"']/g, "");
4786
- const fromDir = path27.dirname(fromFile);
4796
+ const fromDir = path29.dirname(fromFile);
4787
4797
  return workspace.resolve(fromDir, cleaned);
4788
4798
  },
4789
4799
  isExported(_node) {
@@ -4806,7 +4816,7 @@ var cppModule = {
4806
4816
  inheritanceStrategy: "depth-first",
4807
4817
  resolveImport(rawPath, fromFile, workspace) {
4808
4818
  const cleaned = rawPath.replace(/[<>"']/g, "");
4809
- const fromDir = path27.dirname(fromFile);
4819
+ const fromDir = path29.dirname(fromFile);
4810
4820
  return workspace.resolve(fromDir, cleaned);
4811
4821
  },
4812
4822
  isExported(_node) {
@@ -4968,7 +4978,7 @@ var dartModule = {
4968
4978
  const pkg = cleaned.replace("package:", "");
4969
4979
  return workspace.findByPackage(pkg);
4970
4980
  }
4971
- const fromDir = path27.dirname(fromFile);
4981
+ const fromDir = path29.dirname(fromFile);
4972
4982
  return workspace.resolve(fromDir, cleaned);
4973
4983
  },
4974
4984
  isExported(node) {
@@ -5323,25 +5333,25 @@ function validateDAG(phases) {
5323
5333
  const visiting = /* @__PURE__ */ new Set();
5324
5334
  const visited = /* @__PURE__ */ new Set();
5325
5335
  const phaseMap = new Map(phases.map((p) => [p.name, p]));
5326
- function dfs(name, path28) {
5336
+ function dfs(name, path30) {
5327
5337
  if (visiting.has(name)) {
5328
- const cycleStart = path28.indexOf(name);
5329
- const cycle = path28.slice(cycleStart).concat(name);
5338
+ const cycleStart = path30.indexOf(name);
5339
+ const cycle = path30.slice(cycleStart).concat(name);
5330
5340
  errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
5331
5341
  return true;
5332
5342
  }
5333
5343
  if (visited.has(name)) return false;
5334
5344
  visiting.add(name);
5335
- path28.push(name);
5345
+ path30.push(name);
5336
5346
  const phase = phaseMap.get(name);
5337
5347
  if (phase) {
5338
5348
  for (const dep of phase.dependencies) {
5339
- if (dfs(dep, path28)) return true;
5349
+ if (dfs(dep, path30)) return true;
5340
5350
  }
5341
5351
  }
5342
5352
  visiting.delete(name);
5343
5353
  visited.add(name);
5344
- path28.pop();
5354
+ path30.pop();
5345
5355
  return false;
5346
5356
  }
5347
5357
  for (const phase of phases) {
@@ -5554,7 +5564,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
5554
5564
  ]);
5555
5565
  function loadIgnorePatterns(workspaceRoot) {
5556
5566
  try {
5557
- const raw = fs19.readFileSync(path27.join(workspaceRoot, ".codeintelignore"), "utf-8");
5567
+ const raw = fs23.readFileSync(path29.join(workspaceRoot, ".codeintelignore"), "utf-8");
5558
5568
  const extras = /* @__PURE__ */ new Set();
5559
5569
  for (const line of raw.split("\n")) {
5560
5570
  const trimmed = line.trim();
@@ -5578,7 +5588,7 @@ var scanPhase = {
5578
5588
  function walk(dir) {
5579
5589
  let entries;
5580
5590
  try {
5581
- entries = fs19.readdirSync(dir, { withFileTypes: true });
5591
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
5582
5592
  } catch {
5583
5593
  return;
5584
5594
  }
@@ -5587,15 +5597,15 @@ var scanPhase = {
5587
5597
  if (entry.name.startsWith(".")) continue;
5588
5598
  if (IGNORED_DIRS.has(entry.name)) continue;
5589
5599
  if (extraIgnore.has(entry.name)) continue;
5590
- walk(path27.join(dir, entry.name));
5600
+ walk(path29.join(dir, entry.name));
5591
5601
  } else if (entry.isFile()) {
5592
5602
  const name = entry.name;
5593
5603
  if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
5594
- const ext = path27.extname(name);
5604
+ const ext = path29.extname(name);
5595
5605
  if (!extensions.has(ext)) continue;
5596
- const fullPath = path27.join(dir, name);
5606
+ const fullPath = path29.join(dir, name);
5597
5607
  try {
5598
- const stat = fs19.statSync(fullPath);
5608
+ const stat = fs23.statSync(fullPath);
5599
5609
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
5600
5610
  } catch {
5601
5611
  continue;
@@ -5622,20 +5632,20 @@ var structurePhase = {
5622
5632
  const dirs = /* @__PURE__ */ new Set();
5623
5633
  let structDone = 0;
5624
5634
  for (const filePath of context2.filePaths) {
5625
- const relativePath = path27.relative(context2.workspaceRoot, filePath);
5635
+ const relativePath = path29.relative(context2.workspaceRoot, filePath);
5626
5636
  const lang = detectLanguage(filePath);
5627
5637
  context2.graph.addNode({
5628
5638
  id: generateNodeId("file", relativePath, relativePath),
5629
5639
  kind: "file",
5630
- name: path27.basename(filePath),
5640
+ name: path29.basename(filePath),
5631
5641
  filePath: relativePath,
5632
5642
  metadata: lang ? { language: lang } : void 0
5633
5643
  });
5634
- let dir = path27.dirname(relativePath);
5644
+ let dir = path29.dirname(relativePath);
5635
5645
  while (dir && dir !== "." && dir !== "") {
5636
5646
  if (dirs.has(dir)) break;
5637
5647
  dirs.add(dir);
5638
- dir = path27.dirname(dir);
5648
+ dir = path29.dirname(dir);
5639
5649
  }
5640
5650
  structDone++;
5641
5651
  context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
@@ -5644,7 +5654,7 @@ var structurePhase = {
5644
5654
  context2.graph.addNode({
5645
5655
  id: generateNodeId("directory", dir, dir),
5646
5656
  kind: "directory",
5647
- name: path27.basename(dir),
5657
+ name: path29.basename(dir),
5648
5658
  filePath: dir
5649
5659
  });
5650
5660
  }
@@ -5755,22 +5765,22 @@ var flowPhase = {
5755
5765
  const queue = [{ nodeId: ep.id, path: [ep.id] }];
5756
5766
  const visited = /* @__PURE__ */ new Set();
5757
5767
  while (queue.length > 0 && flowCount < maxFlows) {
5758
- const { nodeId, path: path28 } = queue.shift();
5759
- if (path28.length > maxDepth) continue;
5768
+ const { nodeId, path: path30 } = queue.shift();
5769
+ if (path30.length > maxDepth) continue;
5760
5770
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
5761
- if (callEdges.length === 0 && path28.length >= 3) {
5771
+ if (callEdges.length === 0 && path30.length >= 3) {
5762
5772
  const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
5763
5773
  graph.addNode({
5764
5774
  id: flowId,
5765
5775
  kind: "flow",
5766
5776
  name: `${ep.name} flow ${flowCount}`,
5767
5777
  filePath: ep.filePath,
5768
- metadata: { steps: path28, entryPoint: ep.name }
5778
+ metadata: { steps: path30, entryPoint: ep.name }
5769
5779
  });
5770
- for (let i = 0; i < path28.length; i++) {
5780
+ for (let i = 0; i < path30.length; i++) {
5771
5781
  graph.addEdge({
5772
- id: generateEdgeId(path28[i], flowId, `step_of_${i}`),
5773
- source: path28[i],
5782
+ id: generateEdgeId(path30[i], flowId, `step_of_${i}`),
5783
+ source: path30[i],
5774
5784
  target: flowId,
5775
5785
  kind: "step_of",
5776
5786
  weight: 1,
@@ -5783,7 +5793,7 @@ var flowPhase = {
5783
5793
  for (const edge of callEdges) {
5784
5794
  if (visited.has(edge.target)) continue;
5785
5795
  visited.add(edge.target);
5786
- queue.push({ nodeId: edge.target, path: [...path28, edge.target] });
5796
+ queue.push({ nodeId: edge.target, path: [...path30, edge.target] });
5787
5797
  }
5788
5798
  }
5789
5799
  }
@@ -5801,7 +5811,7 @@ var LLMGovernanceLogger = class {
5801
5811
  }
5802
5812
  /** Path to the JSONL log file. */
5803
5813
  getLogPath() {
5804
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
5814
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path29.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
5805
5815
  }
5806
5816
  /**
5807
5817
  * Append an entry to the governance log.
@@ -5817,8 +5827,8 @@ var LLMGovernanceLogger = class {
5817
5827
  ...entry
5818
5828
  };
5819
5829
  const logPath = this.getLogPath();
5820
- fs19.mkdirSync(path27.dirname(logPath), { recursive: true });
5821
- fs19.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
5830
+ fs23.mkdirSync(path29.dirname(logPath), { recursive: true });
5831
+ fs23.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
5822
5832
  } catch {
5823
5833
  }
5824
5834
  }
@@ -5828,7 +5838,7 @@ var LLMGovernanceLogger = class {
5828
5838
  */
5829
5839
  readLog(limit = 100) {
5830
5840
  try {
5831
- const raw = fs19.readFileSync(this.getLogPath(), "utf-8");
5841
+ const raw = fs23.readFileSync(this.getLogPath(), "utf-8");
5832
5842
  const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
5833
5843
  return lines.map((l) => JSON.parse(l));
5834
5844
  } catch {
@@ -5972,17 +5982,17 @@ function traceFlow(entryId, graph, maxDepth = 10, maxBranching = 4) {
5972
5982
  const queue = [{ nodeId: entryId, path: [entryId] }];
5973
5983
  const visited = /* @__PURE__ */ new Set();
5974
5984
  while (queue.length > 0 && flows.length < maxFlows) {
5975
- const { nodeId, path: path28 } = queue.shift();
5976
- if (path28.length > maxDepth) continue;
5985
+ const { nodeId, path: path30 } = queue.shift();
5986
+ if (path30.length > maxDepth) continue;
5977
5987
  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] });
5988
+ if (callEdges.length === 0 && path30.length >= 3) {
5989
+ flows.push({ entryPointId: entryId, steps: [...path30] });
5980
5990
  continue;
5981
5991
  }
5982
5992
  for (const edge of callEdges) {
5983
5993
  if (visited.has(edge.target)) continue;
5984
5994
  visited.add(edge.target);
5985
- queue.push({ nodeId: edge.target, path: [...path28, edge.target] });
5995
+ queue.push({ nodeId: edge.target, path: [...path30, edge.target] });
5986
5996
  }
5987
5997
  }
5988
5998
  }
@@ -6216,7 +6226,7 @@ init_embedder();
6216
6226
  async function hybridSearch(graph, query, limit, options = {}) {
6217
6227
  const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
6218
6228
  const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
6219
- const hasVectorDb = Boolean(vectorDbPath && fs19.existsSync(vectorDbPath));
6229
+ const hasVectorDb = Boolean(vectorDbPath && fs23.existsSync(vectorDbPath));
6220
6230
  if (!hasVectorDb) {
6221
6231
  const bm25Results2 = await bm25Promise;
6222
6232
  return {
@@ -6273,7 +6283,7 @@ var DbManager = class {
6273
6283
  this.dbPath = dbPath;
6274
6284
  }
6275
6285
  async init() {
6276
- fs19.mkdirSync(path27.dirname(this.dbPath), { recursive: true });
6286
+ fs23.mkdirSync(path29.dirname(this.dbPath), { recursive: true });
6277
6287
  this.db = new Database(this.dbPath);
6278
6288
  await this.db.init();
6279
6289
  this.conn = new Connection(this.db);
@@ -6363,7 +6373,7 @@ function getCreateEdgeTableDDL() {
6363
6373
  )`];
6364
6374
  }
6365
6375
  function writeNodeCSVs(graph, outputDir) {
6366
- fs19.mkdirSync(outputDir, { recursive: true });
6376
+ fs23.mkdirSync(outputDir, { recursive: true });
6367
6377
  const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
6368
6378
  const tableBuffers = /* @__PURE__ */ new Map();
6369
6379
  const tableFilePaths = /* @__PURE__ */ new Map();
@@ -6371,7 +6381,7 @@ function writeNodeCSVs(graph, outputDir) {
6371
6381
  const table = NODE_TABLE_MAP[node.kind];
6372
6382
  if (!tableBuffers.has(table)) {
6373
6383
  tableBuffers.set(table, [header]);
6374
- tableFilePaths.set(table, path27.join(outputDir, `${table}.csv`));
6384
+ tableFilePaths.set(table, path29.join(outputDir, `${table}.csv`));
6375
6385
  }
6376
6386
  tableBuffers.get(table).push(
6377
6387
  csvRow([
@@ -6391,12 +6401,12 @@ function writeNodeCSVs(graph, outputDir) {
6391
6401
  );
6392
6402
  }
6393
6403
  for (const [table, lines] of tableBuffers) {
6394
- fs19.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
6404
+ fs23.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
6395
6405
  }
6396
6406
  return tableFilePaths;
6397
6407
  }
6398
6408
  function writeEdgeCSV(graph, outputDir) {
6399
- fs19.mkdirSync(outputDir, { recursive: true });
6409
+ fs23.mkdirSync(outputDir, { recursive: true });
6400
6410
  const header = "from_id,to_id,kind,weight,label\n";
6401
6411
  const groups = /* @__PURE__ */ new Map();
6402
6412
  for (const edge of graph.allEdges()) {
@@ -6407,7 +6417,7 @@ function writeEdgeCSV(graph, outputDir) {
6407
6417
  const toTable = NODE_TABLE_MAP[targetNode.kind];
6408
6418
  const key = `${fromTable}->${toTable}`;
6409
6419
  if (!groups.has(key)) {
6410
- const filePath = path27.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
6420
+ const filePath = path29.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
6411
6421
  groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
6412
6422
  }
6413
6423
  groups.get(key).lines.push(
@@ -6422,7 +6432,7 @@ function writeEdgeCSV(graph, outputDir) {
6422
6432
  }
6423
6433
  const result = [];
6424
6434
  for (const group of groups.values()) {
6425
- fs19.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
6435
+ fs23.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
6426
6436
  result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
6427
6437
  }
6428
6438
  return result;
@@ -6450,7 +6460,7 @@ async function loadGraphToDB(graph, dbManager) {
6450
6460
  } catch {
6451
6461
  }
6452
6462
  }
6453
- const tmpDir = fs19.mkdtempSync(path27.join(os12.tmpdir(), "code-intel-csv-"));
6463
+ const tmpDir = fs23.mkdtempSync(path29.join(os12.tmpdir(), "code-intel-csv-"));
6454
6464
  try {
6455
6465
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
6456
6466
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -6469,8 +6479,8 @@ async function loadGraphToDB(graph, dbManager) {
6469
6479
  }
6470
6480
  let nodeCount = 0;
6471
6481
  for (const [table, csvPath] of nodeTableFiles) {
6472
- if (!fs19.existsSync(csvPath)) continue;
6473
- const stat = fs19.statSync(csvPath);
6482
+ if (!fs23.existsSync(csvPath)) continue;
6483
+ const stat = fs23.statSync(csvPath);
6474
6484
  if (stat.size < 50) continue;
6475
6485
  try {
6476
6486
  await dbManager.execute(
@@ -6483,8 +6493,8 @@ async function loadGraphToDB(graph, dbManager) {
6483
6493
  }
6484
6494
  let edgeCount = 0;
6485
6495
  for (const group of edgeGroups) {
6486
- if (!fs19.existsSync(group.filePath)) continue;
6487
- const stat = fs19.statSync(group.filePath);
6496
+ if (!fs23.existsSync(group.filePath)) continue;
6497
+ const stat = fs23.statSync(group.filePath);
6488
6498
  if (stat.size < 50) continue;
6489
6499
  try {
6490
6500
  await dbManager.execute(
@@ -6498,7 +6508,7 @@ async function loadGraphToDB(graph, dbManager) {
6498
6508
  return { nodeCount, edgeCount };
6499
6509
  } finally {
6500
6510
  try {
6501
- fs19.rmSync(tmpDir, { recursive: true, force: true });
6511
+ fs23.rmSync(tmpDir, { recursive: true, force: true });
6502
6512
  } catch {
6503
6513
  }
6504
6514
  }
@@ -6550,19 +6560,19 @@ function buildNodeProps(node) {
6550
6560
  function escCypher(s) {
6551
6561
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
6552
6562
  }
6553
- var GLOBAL_DIR = path27.join(os12.homedir(), ".code-intel");
6554
- var REPOS_FILE = path27.join(GLOBAL_DIR, "repos.json");
6563
+ var GLOBAL_DIR = path29.join(os12.homedir(), ".code-intel");
6564
+ var REPOS_FILE = path29.join(GLOBAL_DIR, "repos.json");
6555
6565
  function loadRegistry() {
6556
6566
  try {
6557
- const data = fs19.readFileSync(REPOS_FILE, "utf-8");
6567
+ const data = fs23.readFileSync(REPOS_FILE, "utf-8");
6558
6568
  return JSON.parse(data);
6559
6569
  } catch {
6560
6570
  return [];
6561
6571
  }
6562
6572
  }
6563
6573
  function saveRegistry(entries) {
6564
- fs19.mkdirSync(GLOBAL_DIR, { recursive: true });
6565
- fs19.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
6574
+ fs23.mkdirSync(GLOBAL_DIR, { recursive: true });
6575
+ fs23.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
6566
6576
  }
6567
6577
  function upsertRepo(entry) {
6568
6578
  const entries = loadRegistry();
@@ -6579,23 +6589,23 @@ function removeRepo(repoPath) {
6579
6589
  saveRegistry(entries);
6580
6590
  }
6581
6591
  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));
6592
+ const metaDir = path29.join(repoDir, ".code-intel");
6593
+ fs23.mkdirSync(metaDir, { recursive: true });
6594
+ fs23.writeFileSync(path29.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
6585
6595
  }
6586
6596
  function loadMetadata(repoDir) {
6587
6597
  try {
6588
- const data = fs19.readFileSync(path27.join(repoDir, ".code-intel", "meta.json"), "utf-8");
6598
+ const data = fs23.readFileSync(path29.join(repoDir, ".code-intel", "meta.json"), "utf-8");
6589
6599
  return JSON.parse(data);
6590
6600
  } catch {
6591
6601
  return null;
6592
6602
  }
6593
6603
  }
6594
6604
  function getDbPath(repoDir) {
6595
- return path27.join(repoDir, ".code-intel", "graph.db");
6605
+ return path29.join(repoDir, ".code-intel", "graph.db");
6596
6606
  }
6597
6607
  function getVectorDbPath(repoDir) {
6598
- return path27.join(repoDir, ".code-intel", "vector.db");
6608
+ return path29.join(repoDir, ".code-intel", "vector.db");
6599
6609
  }
6600
6610
 
6601
6611
  // src/mcp-server/server.ts
@@ -6673,6 +6683,205 @@ async function loadGraphFromDB(graph, db) {
6673
6683
 
6674
6684
  // src/multi-repo/group-sync.ts
6675
6685
  init_logger();
6686
+ function scanForFiles(root, matcher, maxDepth = 2) {
6687
+ const results = [];
6688
+ function walk(dir, depth) {
6689
+ if (depth > maxDepth) return;
6690
+ let entries;
6691
+ try {
6692
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
6693
+ } catch {
6694
+ return;
6695
+ }
6696
+ for (const entry of entries) {
6697
+ const full = path29.join(dir, entry.name);
6698
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
6699
+ walk(full, depth + 1);
6700
+ } else if (entry.isFile() && matcher(entry.name)) {
6701
+ results.push(full);
6702
+ }
6703
+ }
6704
+ }
6705
+ walk(root, 0);
6706
+ return results;
6707
+ }
6708
+
6709
+ // src/multi-repo/schema-parsers/openapi-parser.ts
6710
+ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
6711
+ "openapi.yaml",
6712
+ "openapi.json",
6713
+ "openapi.yml",
6714
+ "swagger.yaml",
6715
+ "swagger.json",
6716
+ "swagger.yml"
6717
+ ]);
6718
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
6719
+ function tryParseFile(filePath) {
6720
+ const ext = path29.extname(filePath).toLowerCase();
6721
+ const content = fs23.readFileSync(filePath, "utf-8");
6722
+ if (ext === ".json") {
6723
+ try {
6724
+ return JSON.parse(content);
6725
+ } catch {
6726
+ return null;
6727
+ }
6728
+ }
6729
+ return null;
6730
+ }
6731
+ async function parseOpenAPIContracts(repoRoot) {
6732
+ const files = scanForFiles(repoRoot, (name) => OPENAPI_FILENAMES.has(name));
6733
+ const contracts = [];
6734
+ for (const filePath of files) {
6735
+ const spec = tryParseFile(filePath);
6736
+ if (!spec) continue;
6737
+ const paths = spec["paths"];
6738
+ if (!paths || typeof paths !== "object") continue;
6739
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
6740
+ if (!pathItem || typeof pathItem !== "object") continue;
6741
+ const ops = pathItem;
6742
+ for (const method of HTTP_METHODS) {
6743
+ if (!(method in ops)) continue;
6744
+ const operation = ops[method];
6745
+ if (!operation) continue;
6746
+ const requestBody = operation["requestBody"];
6747
+ const requestSchema = requestBody?.["content"] ? requestBody["content"]["application/json"]?.["schema"] : void 0;
6748
+ const responses = operation["responses"];
6749
+ const ok200 = responses?.["200"];
6750
+ const responseSchema = ok200?.["content"] ? ok200["content"]["application/json"]?.["schema"] : void 0;
6751
+ contracts.push({
6752
+ name: `${method.toUpperCase()} ${pathStr}`,
6753
+ kind: "route",
6754
+ method: method.toUpperCase(),
6755
+ path: pathStr,
6756
+ ...requestSchema ? { requestSchema } : {},
6757
+ ...responseSchema ? { responseSchema } : {},
6758
+ filePath
6759
+ });
6760
+ }
6761
+ }
6762
+ }
6763
+ return contracts;
6764
+ }
6765
+ function extractFieldNames(block) {
6766
+ return block.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
6767
+ const m = line.match(/^(\w+)\s*[(:]/);
6768
+ return m ? m[1] : null;
6769
+ }).filter((f) => f !== null);
6770
+ }
6771
+ async function parseGraphQLContracts(repoRoot) {
6772
+ const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
6773
+ const contracts = [];
6774
+ for (const filePath of files) {
6775
+ const content = fs23.readFileSync(filePath, "utf-8");
6776
+ const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
6777
+ let match;
6778
+ while ((match = typeRegex.exec(content)) !== null) {
6779
+ const typeName = match[1];
6780
+ const body = match[2];
6781
+ const fields = extractFieldNames(body);
6782
+ const lcName = typeName.toLowerCase();
6783
+ if (lcName === "query") {
6784
+ for (const field of fields) {
6785
+ contracts.push({ name: `query.${field}`, kind: "graphql", operation: "query", fields, filePath });
6786
+ }
6787
+ } else if (lcName === "mutation") {
6788
+ for (const field of fields) {
6789
+ contracts.push({ name: `mutation.${field}`, kind: "graphql", operation: "mutation", fields, filePath });
6790
+ }
6791
+ } else if (lcName === "subscription") {
6792
+ for (const field of fields) {
6793
+ contracts.push({ name: `subscription.${field}`, kind: "graphql", operation: "subscription", fields, filePath });
6794
+ }
6795
+ } else {
6796
+ contracts.push({ name: `type.${typeName}`, kind: "graphql", operation: "type", fields, filePath });
6797
+ }
6798
+ }
6799
+ }
6800
+ return contracts;
6801
+ }
6802
+ async function parseProtoContracts(repoRoot) {
6803
+ const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
6804
+ const contracts = [];
6805
+ for (const filePath of files) {
6806
+ const content = fs23.readFileSync(filePath, "utf-8");
6807
+ const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
6808
+ let serviceMatch;
6809
+ while ((serviceMatch = serviceRegex.exec(content)) !== null) {
6810
+ const serviceName = serviceMatch[1];
6811
+ const body = serviceMatch[2];
6812
+ const rpcRegex = /rpc\s+(\w+)\s*\((\w+)\)\s*returns\s*\((\w+)\)/g;
6813
+ let rpcMatch;
6814
+ while ((rpcMatch = rpcRegex.exec(body)) !== null) {
6815
+ const rpcName = rpcMatch[1];
6816
+ const inputType = rpcMatch[2];
6817
+ const outputType = rpcMatch[3];
6818
+ contracts.push({
6819
+ name: `${serviceName}.${rpcName}`,
6820
+ kind: "grpc",
6821
+ serviceName,
6822
+ rpcName,
6823
+ inputType,
6824
+ outputType,
6825
+ filePath
6826
+ });
6827
+ }
6828
+ }
6829
+ }
6830
+ return contracts;
6831
+ }
6832
+
6833
+ // src/multi-repo/type-similarity.ts
6834
+ function normalizeType(t) {
6835
+ return t.toLowerCase().replace(/[\[\]?<>\s]/g, "").trim();
6836
+ }
6837
+ function paramTypeSimilarity(paramsA, paramsB) {
6838
+ if (paramsA.length === 0 && paramsB.length === 0) return 1;
6839
+ if (paramsA.length === 0 || paramsB.length === 0) return 0;
6840
+ const setA = new Set(paramsA.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
6841
+ const setB = new Set(paramsB.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
6842
+ if (setA.size === 0 && setB.size === 0) return 1;
6843
+ if (setA.size === 0 || setB.size === 0) return 0;
6844
+ const intersection = [...setA].filter((x) => setB.has(x)).length;
6845
+ const union = (/* @__PURE__ */ new Set([...setA, ...setB])).size;
6846
+ return intersection / union;
6847
+ }
6848
+ function returnTypeSimilarity(typeA, typeB) {
6849
+ if (!typeA || !typeB) return 0.5;
6850
+ const a = normalizeType(typeA);
6851
+ const b = normalizeType(typeB);
6852
+ if (a === b) return 1;
6853
+ const compatible = [
6854
+ ["string", "str"],
6855
+ ["number", "int"],
6856
+ ["number", "float"],
6857
+ ["number", "double"],
6858
+ ["boolean", "bool"],
6859
+ ["void", "unit"],
6860
+ ["void", "none"]
6861
+ ];
6862
+ for (const [x, y] of compatible) {
6863
+ if (a === x && b === y || a === y && b === x) return 0.8;
6864
+ }
6865
+ return 0;
6866
+ }
6867
+ function paramCountSimilarity(countA, countB) {
6868
+ const maxCount = Math.max(countA, countB, 1);
6869
+ return 1 - Math.abs(countA - countB) / maxCount;
6870
+ }
6871
+ function computeContractSimilarity(a, b, nameSim) {
6872
+ const paramsA = a.parameters ?? [];
6873
+ const paramsB = b.parameters ?? [];
6874
+ const ptSim = paramTypeSimilarity(paramsA, paramsB);
6875
+ const rtSim = returnTypeSimilarity(a.returnType, b.returnType);
6876
+ const pcSim = paramCountSimilarity(paramsA.length, paramsB.length);
6877
+ let score = 0.4 * nameSim + 0.3 * ptSim + 0.2 * rtSim + 0.1 * pcSim;
6878
+ if (ptSim > 0.8) {
6879
+ score = Math.min(1, score * 1.2);
6880
+ }
6881
+ return score;
6882
+ }
6883
+
6884
+ // src/multi-repo/group-sync.ts
6676
6885
  function extractContracts(graph, repoName, repoPath) {
6677
6886
  const contracts = [];
6678
6887
  for (const node of graph.allNodes()) {
@@ -6685,7 +6894,10 @@ function extractContracts(graph, repoName, repoPath) {
6685
6894
  nodeId: node.id,
6686
6895
  nodeKind: node.kind,
6687
6896
  filePath: node.filePath,
6688
- signature: node.content?.split("\n")[0]?.trim()
6897
+ signature: node.content?.split("\n")[0]?.trim(),
6898
+ parameters: node.metadata?.parameters ?? node.metadata?.params,
6899
+ returnType: node.metadata?.returnType,
6900
+ exported: node.exported
6689
6901
  });
6690
6902
  }
6691
6903
  if (node.kind === "route") {
@@ -6747,13 +6959,15 @@ function matchContracts(allContracts) {
6747
6959
  const consumer = consumerByName.get(provider.name);
6748
6960
  if (consumer) {
6749
6961
  const sameKind = provider.kind === consumer.kind;
6962
+ const typedScore = computeContractSimilarity(provider, consumer, 1);
6963
+ const confidence = sameKind ? Math.max(typedScore, 0.9) : Math.max(typedScore, 0.6);
6750
6964
  links.push({
6751
6965
  providerRepo: provider.repoName,
6752
6966
  providerContract: provider.name,
6753
6967
  consumerRepo: consumer.repoName,
6754
6968
  consumerContract: consumer.name,
6755
6969
  matchKind: provider.kind === "route" ? "route-match" : "name-match",
6756
- confidence: sameKind ? 0.9 : 0.6
6970
+ confidence: Math.min(1, confidence)
6757
6971
  });
6758
6972
  } else {
6759
6973
  const providerLC = provider.name.toLowerCase();
@@ -6794,8 +7008,8 @@ async function syncGroup(group) {
6794
7008
  logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
6795
7009
  continue;
6796
7010
  }
6797
- const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
6798
- if (!fs19.existsSync(dbPath)) {
7011
+ const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
7012
+ if (!fs23.existsSync(dbPath)) {
6799
7013
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
6800
7014
  continue;
6801
7015
  }
@@ -6811,6 +7025,44 @@ async function syncGroup(group) {
6811
7025
  continue;
6812
7026
  }
6813
7027
  const contracts = extractContracts(graph, member.registryName, regEntry.path);
7028
+ const [openapiContracts, graphqlContracts, protoContracts] = await Promise.all([
7029
+ parseOpenAPIContracts(regEntry.path).catch(() => []),
7030
+ parseGraphQLContracts(regEntry.path).catch(() => []),
7031
+ parseProtoContracts(regEntry.path).catch(() => [])
7032
+ ]);
7033
+ for (const c of openapiContracts) {
7034
+ contracts.push({
7035
+ repoName: member.registryName,
7036
+ repoPath: regEntry.path,
7037
+ kind: "route",
7038
+ name: c.name,
7039
+ nodeId: `openapi:${c.method}:${c.path}`,
7040
+ nodeKind: "route",
7041
+ filePath: c.filePath
7042
+ });
7043
+ }
7044
+ for (const c of graphqlContracts) {
7045
+ contracts.push({
7046
+ repoName: member.registryName,
7047
+ repoPath: regEntry.path,
7048
+ kind: "graphql",
7049
+ name: c.name,
7050
+ nodeId: `graphql:${c.name}`,
7051
+ nodeKind: "graphql",
7052
+ filePath: c.filePath
7053
+ });
7054
+ }
7055
+ for (const c of protoContracts) {
7056
+ contracts.push({
7057
+ repoName: member.registryName,
7058
+ repoPath: regEntry.path,
7059
+ kind: "grpc",
7060
+ name: c.name,
7061
+ nodeId: `grpc:${c.serviceName}:${c.rpcName}`,
7062
+ nodeKind: "grpc",
7063
+ filePath: c.filePath
7064
+ });
7065
+ }
6814
7066
  logger_default.info(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
6815
7067
  allContracts.push(...contracts);
6816
7068
  }
@@ -6830,8 +7082,8 @@ async function queryGroup(group, query, limit = 20) {
6830
7082
  for (const member of group.members) {
6831
7083
  const regEntry = registry.find((r) => r.name === member.registryName);
6832
7084
  if (!regEntry) continue;
6833
- const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
6834
- if (!fs19.existsSync(dbPath)) continue;
7085
+ const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
7086
+ if (!fs23.existsSync(dbPath)) continue;
6835
7087
  const graph = createKnowledgeGraph();
6836
7088
  const db = new DbManager(dbPath);
6837
7089
  try {
@@ -7331,22 +7583,22 @@ function suggestTests(graph, symbolName) {
7331
7583
  const callPaths = [];
7332
7584
  const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
7333
7585
  while (pathQueue.length > 0 && callPaths.length < 5) {
7334
- const { id, path: path28, depth } = pathQueue.shift();
7586
+ const { id, path: path30, depth } = pathQueue.shift();
7335
7587
  let hasCallers = false;
7336
7588
  for (const edge of graph.findEdgesTo(id)) {
7337
7589
  if (edge.kind !== "calls") continue;
7338
7590
  const callerNode = graph.getNode(edge.source);
7339
7591
  if (!callerNode) continue;
7340
7592
  hasCallers = true;
7341
- const newPath = [callerNode.name, ...path28];
7593
+ const newPath = [callerNode.name, ...path30];
7342
7594
  if (depth + 1 >= 3 || callPaths.length >= 5) {
7343
7595
  if (callPaths.length < 5) callPaths.push(newPath);
7344
7596
  continue;
7345
7597
  }
7346
7598
  pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
7347
7599
  }
7348
- if (!hasCallers && path28.length > 1) {
7349
- callPaths.push(path28);
7600
+ if (!hasCallers && path30.length > 1) {
7601
+ callPaths.push(path30);
7350
7602
  }
7351
7603
  }
7352
7604
  if (callPaths.length === 0) {
@@ -8244,7 +8496,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
8244
8496
  for (const { filePath: changedFile, changedLines } of changedFiles) {
8245
8497
  for (const node of graph.allNodes()) {
8246
8498
  if (!node.filePath) continue;
8247
- const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path27.sep, "");
8499
+ const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path29.sep, "");
8248
8500
  const normChanged = changedFile.replace(/^a\/|^b\//, "");
8249
8501
  if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
8250
8502
  if (node.startLine !== void 0 && node.endLine !== void 0) {
@@ -8610,7 +8862,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
8610
8862
  var JobsDB = class {
8611
8863
  db;
8612
8864
  constructor(dbPath) {
8613
- fs19.mkdirSync(path27.dirname(dbPath), { recursive: true });
8865
+ fs23.mkdirSync(path29.dirname(dbPath), { recursive: true });
8614
8866
  this.db = new Database3(dbPath);
8615
8867
  this.db.pragma("journal_mode = WAL");
8616
8868
  this.db.pragma("foreign_keys = ON");
@@ -8752,7 +9004,7 @@ var JobsDB = class {
8752
9004
  }
8753
9005
  };
8754
9006
  function getJobsDBPath() {
8755
- return path27.join(os12.homedir(), ".code-intel", "jobs.db");
9007
+ return path29.join(os12.homedir(), ".code-intel", "jobs.db");
8756
9008
  }
8757
9009
  var _jobsDB = null;
8758
9010
  function getOrCreateJobsDB() {
@@ -8844,7 +9096,7 @@ var BACKUP_VERSION = "1.0";
8844
9096
  var ALGORITHM = "aes-256-gcm";
8845
9097
  var IV_LENGTH = 16;
8846
9098
  function getBackupDir() {
8847
- return path27.join(os12.homedir(), ".code-intel", "backups");
9099
+ return path29.join(os12.homedir(), ".code-intel", "backups");
8848
9100
  }
8849
9101
  function getBackupKey() {
8850
9102
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
@@ -8875,30 +9127,30 @@ var BackupService = class {
8875
9127
  constructor(backupDir) {
8876
9128
  this.backupDir = backupDir ?? getBackupDir();
8877
9129
  this.key = getBackupKey();
8878
- fs19.mkdirSync(this.backupDir, { recursive: true });
9130
+ fs23.mkdirSync(this.backupDir, { recursive: true });
8879
9131
  }
8880
9132
  /**
8881
9133
  * Create a backup for a repository.
8882
9134
  * Returns the backup entry.
8883
9135
  */
8884
9136
  createBackup(repoPath) {
8885
- const codeIntelDir = path27.join(repoPath, ".code-intel");
9137
+ const codeIntelDir = path29.join(repoPath, ".code-intel");
8886
9138
  const id = v4();
8887
9139
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
8888
9140
  const filesToBackup = [];
8889
9141
  const candidates = ["graph.db", "vector.db", "meta.json"];
8890
9142
  for (const f of candidates) {
8891
- const fp = path27.join(codeIntelDir, f);
8892
- if (fs19.existsSync(fp)) {
9143
+ const fp = path29.join(codeIntelDir, f);
9144
+ if (fs23.existsSync(fp)) {
8893
9145
  filesToBackup.push({ name: f, localPath: fp });
8894
9146
  }
8895
9147
  }
8896
- const registryPath = path27.join(os12.homedir(), ".code-intel", "registry.json");
8897
- if (fs19.existsSync(registryPath)) {
9148
+ const registryPath = path29.join(os12.homedir(), ".code-intel", "registry.json");
9149
+ if (fs23.existsSync(registryPath)) {
8898
9150
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
8899
9151
  }
8900
- const usersDbPath = path27.join(os12.homedir(), ".code-intel", "users.db");
8901
- if (fs19.existsSync(usersDbPath)) {
9152
+ const usersDbPath = path29.join(os12.homedir(), ".code-intel", "users.db");
9153
+ if (fs23.existsSync(usersDbPath)) {
8902
9154
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
8903
9155
  }
8904
9156
  if (filesToBackup.length === 0) {
@@ -8909,7 +9161,7 @@ var BackupService = class {
8909
9161
  createdAt,
8910
9162
  version: BACKUP_VERSION,
8911
9163
  files: filesToBackup.map((f) => {
8912
- const data = fs19.readFileSync(f.localPath);
9164
+ const data = fs23.readFileSync(f.localPath);
8913
9165
  return {
8914
9166
  name: f.name,
8915
9167
  sha256: crypto5.createHash("sha256").update(data).digest("hex"),
@@ -8923,7 +9175,7 @@ var BackupService = class {
8923
9175
  manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
8924
9176
  parts.push(manifestLenBuf, manifestBuf);
8925
9177
  for (const f of filesToBackup) {
8926
- const data = fs19.readFileSync(f.localPath);
9178
+ const data = fs23.readFileSync(f.localPath);
8927
9179
  const nameBuf = Buffer.from(f.name, "utf-8");
8928
9180
  const nameLenBuf = Buffer.alloc(2);
8929
9181
  nameLenBuf.writeUInt16BE(nameBuf.length, 0);
@@ -8934,8 +9186,8 @@ var BackupService = class {
8934
9186
  const plaintext = Buffer.concat(parts);
8935
9187
  const encrypted = encryptBuffer(plaintext, this.key);
8936
9188
  const backupFileName = `backup-${id}.cib`;
8937
- const backupPath = path27.join(this.backupDir, backupFileName);
8938
- fs19.writeFileSync(backupPath, encrypted);
9189
+ const backupPath = path29.join(this.backupDir, backupFileName);
9190
+ fs23.writeFileSync(backupPath, encrypted);
8939
9191
  const entry = {
8940
9192
  id,
8941
9193
  createdAt,
@@ -8962,9 +9214,9 @@ var BackupService = class {
8962
9214
  async uploadToS3(entry) {
8963
9215
  const cfg = getS3Config();
8964
9216
  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);
9217
+ const fileName = path29.basename(entry.path);
8966
9218
  const s3Key = `${cfg.prefix}${fileName}`;
8967
- const body = fs19.readFileSync(entry.path);
9219
+ const body = fs23.readFileSync(entry.path);
8968
9220
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
8969
9221
  if (result.statusCode < 200 || result.statusCode >= 300) {
8970
9222
  throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
@@ -8981,8 +9233,8 @@ var BackupService = class {
8981
9233
  if (result.statusCode < 200 || result.statusCode >= 300) {
8982
9234
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
8983
9235
  }
8984
- fs19.mkdirSync(path27.dirname(destPath), { recursive: true });
8985
- fs19.writeFileSync(destPath, Buffer.from(result.body, "binary"));
9236
+ fs23.mkdirSync(path29.dirname(destPath), { recursive: true });
9237
+ fs23.writeFileSync(destPath, Buffer.from(result.body, "binary"));
8986
9238
  }
8987
9239
  /**
8988
9240
  * List backup objects in S3 with the configured prefix.
@@ -9028,10 +9280,10 @@ var BackupService = class {
9028
9280
  if (!entry) {
9029
9281
  throw new Error(`Backup "${backupId}" not found.`);
9030
9282
  }
9031
- if (!fs19.existsSync(entry.path)) {
9283
+ if (!fs23.existsSync(entry.path)) {
9032
9284
  throw new Error(`Backup file not found at: ${entry.path}`);
9033
9285
  }
9034
- const encrypted = fs19.readFileSync(entry.path);
9286
+ const encrypted = fs23.readFileSync(entry.path);
9035
9287
  let plaintext;
9036
9288
  try {
9037
9289
  plaintext = decryptBuffer(encrypted, this.key);
@@ -9045,8 +9297,8 @@ var BackupService = class {
9045
9297
  offset += manifestLen;
9046
9298
  const manifest = JSON.parse(manifestStr);
9047
9299
  const restoreBase = targetRepoPath ?? entry.repoPath;
9048
- const codeIntelDir = path27.join(restoreBase, ".code-intel");
9049
- fs19.mkdirSync(codeIntelDir, { recursive: true });
9300
+ const codeIntelDir = path29.join(restoreBase, ".code-intel");
9301
+ fs23.mkdirSync(codeIntelDir, { recursive: true });
9050
9302
  for (const fileEntry of manifest.files) {
9051
9303
  const nameLen = plaintext.readUInt16BE(offset);
9052
9304
  offset += 2;
@@ -9063,18 +9315,18 @@ var BackupService = class {
9063
9315
  }
9064
9316
  let destPath;
9065
9317
  if (name === "registry.json" || name === "users.db") {
9066
- destPath = path27.join(os12.homedir(), ".code-intel", name);
9318
+ destPath = path29.join(os12.homedir(), ".code-intel", name);
9067
9319
  } else {
9068
- destPath = path27.join(codeIntelDir, name);
9320
+ destPath = path29.join(codeIntelDir, name);
9069
9321
  }
9070
- fs19.writeFileSync(destPath, data);
9322
+ fs23.writeFileSync(destPath, data);
9071
9323
  }
9072
9324
  }
9073
9325
  /**
9074
9326
  * Apply retention policy: keep N daily, M weekly, L monthly backups.
9075
9327
  */
9076
9328
  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());
9329
+ const entries = this._loadIndex().filter((e) => fs23.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
9078
9330
  const keep = /* @__PURE__ */ new Set();
9079
9331
  const now = /* @__PURE__ */ new Date();
9080
9332
  const dailyCutoff = new Date(now);
@@ -9104,7 +9356,7 @@ var BackupService = class {
9104
9356
  for (const e of entries) {
9105
9357
  if (!keep.has(e.id)) {
9106
9358
  try {
9107
- fs19.unlinkSync(e.path);
9359
+ fs23.unlinkSync(e.path);
9108
9360
  deleted++;
9109
9361
  } catch {
9110
9362
  }
@@ -9116,17 +9368,17 @@ var BackupService = class {
9116
9368
  }
9117
9369
  // ── Index helpers ──────────────────────────────────────────────────────────
9118
9370
  _indexPath() {
9119
- return path27.join(this.backupDir, "index.json");
9371
+ return path29.join(this.backupDir, "index.json");
9120
9372
  }
9121
9373
  _loadIndex() {
9122
9374
  try {
9123
- return JSON.parse(fs19.readFileSync(this._indexPath(), "utf-8"));
9375
+ return JSON.parse(fs23.readFileSync(this._indexPath(), "utf-8"));
9124
9376
  } catch {
9125
9377
  return [];
9126
9378
  }
9127
9379
  }
9128
9380
  _saveIndex(entries) {
9129
- fs19.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
9381
+ fs23.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
9130
9382
  }
9131
9383
  _appendIndex(entry) {
9132
9384
  const entries = this._loadIndex();
@@ -9785,6 +10037,30 @@ var openApiSpec = {
9785
10037
  }
9786
10038
  }
9787
10039
  },
10040
+ "/groups/{name}/topology": {
10041
+ get: {
10042
+ tags: ["Groups"],
10043
+ summary: "Get the topology of repos and cross-repo contract edges for a group",
10044
+ parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" } }],
10045
+ responses: {
10046
+ "200": {
10047
+ description: "Repos and cross-repo edges",
10048
+ content: {
10049
+ "application/json": {
10050
+ schema: {
10051
+ type: "object",
10052
+ properties: {
10053
+ repos: { type: "array", items: { type: "object", properties: { name: { type: "string" }, groupPath: { type: "string" }, nodeCount: { type: "integer" }, edgeCount: { type: "integer" } } } },
10054
+ edges: { type: "array", items: { type: "object", properties: { source: { type: "string" }, target: { type: "string" }, contractName: { type: "string" }, confidence: { type: "number" }, kind: { type: "string" } } } }
10055
+ }
10056
+ }
10057
+ }
10058
+ }
10059
+ },
10060
+ "404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
10061
+ }
10062
+ }
10063
+ },
9788
10064
  "/query": {
9789
10065
  post: {
9790
10066
  tags: ["GQL"],
@@ -9895,11 +10171,11 @@ var openApiSpec = {
9895
10171
  };
9896
10172
 
9897
10173
  // src/http/app.ts
9898
- var __dirname$1 = path27.dirname(fileURLToPath(import.meta.url));
10174
+ var __dirname$1 = path29.dirname(fileURLToPath(import.meta.url));
9899
10175
  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");
10176
+ const bundled = path29.resolve(__dirname$1, "..", "web");
10177
+ if (fs23.existsSync(bundled)) return bundled;
10178
+ return path29.resolve(__dirname$1, "..", "..", "..", "web", "dist");
9903
10179
  })();
9904
10180
  function getAllowedOrigins() {
9905
10181
  const env = process.env["CODE_INTEL_CORS_ORIGINS"];
@@ -10430,8 +10706,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10430
10706
  const registry = loadRegistry();
10431
10707
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
10432
10708
  if (!entry) return null;
10433
- const dbPath = path27.join(entry.path, ".code-intel", "graph.db");
10434
- if (!fs19.existsSync(dbPath)) return null;
10709
+ const dbPath = path29.join(entry.path, ".code-intel", "graph.db");
10710
+ if (!fs23.existsSync(dbPath)) return null;
10435
10711
  const repoGraph = createKnowledgeGraph();
10436
10712
  const db = new DbManager(dbPath);
10437
10713
  try {
@@ -10518,7 +10794,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10518
10794
  return;
10519
10795
  }
10520
10796
  try {
10521
- const content = fs19.readFileSync(file_path, "utf-8");
10797
+ const content = fs23.readFileSync(file_path, "utf-8");
10522
10798
  res.json({ content });
10523
10799
  } catch {
10524
10800
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
@@ -10776,8 +11052,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10776
11052
  for (const member of group.members) {
10777
11053
  const regEntry = registry.find((r) => r.name === member.registryName);
10778
11054
  if (!regEntry) continue;
10779
- const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
10780
- if (!fs19.existsSync(dbPath)) continue;
11055
+ const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
11056
+ if (!fs23.existsSync(dbPath)) continue;
10781
11057
  const db = new DbManager(dbPath);
10782
11058
  try {
10783
11059
  await db.init();
@@ -10789,6 +11065,45 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10789
11065
  }
10790
11066
  res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
10791
11067
  });
11068
+ app.get("/api/v1/groups/:name/topology", requireAuth, requireRole("viewer"), async (req, res) => {
11069
+ const groupName = req.params["name"];
11070
+ const group = loadGroup(groupName);
11071
+ if (!group) {
11072
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
11073
+ return;
11074
+ }
11075
+ const syncResult = loadSyncResult(groupName);
11076
+ const registry = loadRegistry();
11077
+ const repos = await Promise.all(group.members.map(async (member) => {
11078
+ const regEntry = registry.find((r) => r.name === member.registryName);
11079
+ let nodeCount = 0;
11080
+ let edgeCount = 0;
11081
+ if (regEntry) {
11082
+ const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
11083
+ if (fs23.existsSync(dbPath)) {
11084
+ try {
11085
+ const db = new DbManager(dbPath);
11086
+ await db.init();
11087
+ const g = createKnowledgeGraph();
11088
+ await loadGraphFromDB(g, db);
11089
+ db.close();
11090
+ nodeCount = g.size.nodes;
11091
+ edgeCount = g.size.edges;
11092
+ } catch {
11093
+ }
11094
+ }
11095
+ }
11096
+ return { name: member.registryName, groupPath: member.groupPath, nodeCount, edgeCount };
11097
+ }));
11098
+ const edges = syncResult ? syncResult.links.map((link) => ({
11099
+ source: link.providerRepo,
11100
+ target: link.consumerRepo,
11101
+ contractName: link.providerContract,
11102
+ confidence: link.confidence,
11103
+ kind: "contract"
11104
+ })) : [];
11105
+ res.json({ repos, edges });
11106
+ });
10792
11107
  app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
10793
11108
  const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
10794
11109
  if (!file) {
@@ -10814,14 +11129,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10814
11129
  });
10815
11130
  return;
10816
11131
  }
10817
- let rawResolved = path27.normalize(file);
10818
- if (!path27.isAbsolute(rawResolved) && workspaceRoot) {
10819
- rawResolved = path27.join(workspaceRoot, rawResolved);
11132
+ let rawResolved = path29.normalize(file);
11133
+ if (!path29.isAbsolute(rawResolved) && workspaceRoot) {
11134
+ rawResolved = path29.join(workspaceRoot, rawResolved);
10820
11135
  }
10821
- const resolvedFile = path27.resolve(rawResolved);
11136
+ const resolvedFile = path29.resolve(rawResolved);
10822
11137
  function isInsideDir(fileAbs, dir) {
10823
- const rel = path27.relative(path27.resolve(dir), fileAbs);
10824
- return !rel.startsWith("..") && !path27.isAbsolute(rel);
11138
+ const rel = path29.relative(path29.resolve(dir), fileAbs);
11139
+ return !rel.startsWith("..") && !path29.isAbsolute(rel);
10825
11140
  }
10826
11141
  if (workspaceRoot) {
10827
11142
  if (!isInsideDir(resolvedFile, workspaceRoot)) {
@@ -10858,7 +11173,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10858
11173
  }
10859
11174
  let fileContent;
10860
11175
  try {
10861
- fileContent = fs19.readFileSync(resolvedFile, "utf-8");
11176
+ fileContent = fs23.readFileSync(resolvedFile, "utf-8");
10862
11177
  } catch {
10863
11178
  res.status(404).json({
10864
11179
  error: {
@@ -10889,7 +11204,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10889
11204
  const contextStart = Math.max(1, startLine - 20);
10890
11205
  const contextEnd = Math.min(lines.length, endLine + 20);
10891
11206
  const content = lines.slice(contextStart - 1, contextEnd).join("\n");
10892
- const ext = path27.extname(resolvedFile).toLowerCase();
11207
+ const ext = path29.extname(resolvedFile).toLowerCase();
10893
11208
  const languageMap = {
10894
11209
  ".ts": "typescript",
10895
11210
  ".tsx": "typescript",
@@ -11024,10 +11339,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11024
11339
  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
11340
  }
11026
11341
  });
11027
- if (fs19.existsSync(WEB_DIST)) {
11342
+ if (fs23.existsSync(WEB_DIST)) {
11028
11343
  app.use(express.static(WEB_DIST));
11029
11344
  app.get("/{*path}", (_req, res) => {
11030
- res.sendFile(path27.join(WEB_DIST, "index.html"));
11345
+ res.sendFile(path29.join(WEB_DIST, "index.html"));
11031
11346
  });
11032
11347
  }
11033
11348
  app.use("/admin", requireRole("admin"));