@vohongtho.infotech/code-intel 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/main.js CHANGED
@@ -7,8 +7,8 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
7
7
  import { SpanStatusCode, trace, context } from '@opentelemetry/api';
8
8
  import winston from 'winston';
9
9
  import DailyRotateFile from 'winston-daily-rotate-file';
10
- import fs27, { readFileSync, existsSync } from 'fs';
11
- import path29, { dirname, join } from 'path';
10
+ import fs28, { readFileSync, existsSync } from 'fs';
11
+ import path30, { dirname, join } from 'path';
12
12
  import os12 from 'os';
13
13
  import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client';
14
14
  import { createRequire } from 'module';
@@ -26,6 +26,7 @@ import { Command } from 'commander';
26
26
  import { Worker } from 'worker_threads';
27
27
  import { EventEmitter } from 'events';
28
28
  import express from 'express';
29
+ import compression from 'compression';
29
30
  import cors from 'cors';
30
31
  import helmet from 'helmet';
31
32
  import cookieParser from 'cookie-parser';
@@ -325,7 +326,7 @@ var init_logger = __esm({
325
326
  };
326
327
  }
327
328
  /** Global log directory: ~/.code-intel/logs */
328
- static LOG_DIR = path29.join(os12.homedir(), ".code-intel", "logs");
329
+ static LOG_DIR = path30.join(os12.homedir(), ".code-intel", "logs");
329
330
  static getLogger() {
330
331
  if (!_Logger.instance) {
331
332
  const isProduction = process.env.NODE_ENV === "production";
@@ -334,12 +335,12 @@ var init_logger = __esm({
334
335
  transports.push(new winston.transports.Console());
335
336
  if (!isProduction) {
336
337
  try {
337
- if (!fs27.existsSync(_Logger.LOG_DIR)) {
338
- fs27.mkdirSync(_Logger.LOG_DIR, { recursive: true });
338
+ if (!fs28.existsSync(_Logger.LOG_DIR)) {
339
+ fs28.mkdirSync(_Logger.LOG_DIR, { recursive: true });
339
340
  }
340
341
  transports.push(
341
342
  new DailyRotateFile({
342
- filename: path29.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
343
+ filename: path30.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
343
344
  datePattern: "YYYY-MM-DD",
344
345
  maxSize: "20m",
345
346
  maxFiles: "14d"
@@ -414,25 +415,25 @@ function validateDAG(phases) {
414
415
  const visiting = /* @__PURE__ */ new Set();
415
416
  const visited = /* @__PURE__ */ new Set();
416
417
  const phaseMap = new Map(phases.map((p) => [p.name, p]));
417
- function dfs(name, path30) {
418
+ function dfs(name, path31) {
418
419
  if (visiting.has(name)) {
419
- const cycleStart = path30.indexOf(name);
420
- const cycle = path30.slice(cycleStart).concat(name);
420
+ const cycleStart = path31.indexOf(name);
421
+ const cycle = path31.slice(cycleStart).concat(name);
421
422
  errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
422
423
  return true;
423
424
  }
424
425
  if (visited.has(name)) return false;
425
426
  visiting.add(name);
426
- path30.push(name);
427
+ path31.push(name);
427
428
  const phase = phaseMap.get(name);
428
429
  if (phase) {
429
430
  for (const dep of phase.dependencies) {
430
- if (dfs(dep, path30)) return true;
431
+ if (dfs(dep, path31)) return true;
431
432
  }
432
433
  }
433
434
  visiting.delete(name);
434
435
  visited.add(name);
435
- path30.pop();
436
+ path31.pop();
436
437
  return false;
437
438
  }
438
439
  for (const phase of phases) {
@@ -692,11 +693,11 @@ var init_id_generator = __esm({
692
693
  }
693
694
  });
694
695
  function findBundledWasmDir() {
695
- const fileDir = path29.dirname(fileURLToPath(import.meta.url));
696
+ const fileDir = path30.dirname(fileURLToPath(import.meta.url));
696
697
  const candidates = [
697
- path29.join(fileDir, "wasm"),
698
+ path30.join(fileDir, "wasm"),
698
699
  // dist/index.js → dist/wasm/
699
- path29.join(fileDir, "../wasm")
700
+ path30.join(fileDir, "../wasm")
700
701
  // dist/cli/main.js → dist/wasm/
701
702
  ];
702
703
  for (const candidate of candidates) {
@@ -737,7 +738,7 @@ function wasmPath(lang) {
737
738
  }
738
739
  const bundled = BUNDLED_WASM_MAP[lang];
739
740
  if (bundled) {
740
- const bundledPath = path29.join(_bundledWasmDir, bundled);
741
+ const bundledPath = path30.join(_bundledWasmDir, bundled);
741
742
  if (existsSync(bundledPath)) return bundledPath;
742
743
  }
743
744
  return null;
@@ -750,14 +751,14 @@ async function initParser() {
750
751
  }
751
752
  async function getLanguage(lang) {
752
753
  if (languageCache.has(lang)) return languageCache.get(lang);
753
- const path30 = wasmPath(lang);
754
- if (!path30) {
754
+ const path31 = wasmPath(lang);
755
+ if (!path31) {
755
756
  languageCache.set(lang, null);
756
757
  return null;
757
758
  }
758
759
  try {
759
760
  await initParser();
760
- const language = await Language.load(path30);
761
+ const language = await Language.load(path31);
761
762
  languageCache.set(lang, language);
762
763
  return language;
763
764
  } catch {
@@ -2182,7 +2183,7 @@ var init_parse_phase = __esm({
2182
2183
  const batch = filePaths.slice(i, i + CONCURRENCY);
2183
2184
  await Promise.all(batch.map(async (filePath) => {
2184
2185
  try {
2185
- const source = await fs27.promises.readFile(filePath, "utf-8");
2186
+ const source = await fs28.promises.readFile(filePath, "utf-8");
2186
2187
  context2.fileCache.set(filePath, source);
2187
2188
  } catch {
2188
2189
  }
@@ -2195,14 +2196,14 @@ var init_parse_phase = __esm({
2195
2196
  const lang = detectLanguage(filePath);
2196
2197
  if (!lang) {
2197
2198
  if (context2.verbose) {
2198
- const relativePath2 = path29.relative(context2.workspaceRoot, filePath);
2199
+ const relativePath2 = path30.relative(context2.workspaceRoot, filePath);
2199
2200
  logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
2200
2201
  }
2201
2202
  continue;
2202
2203
  }
2203
2204
  const source = context2.fileCache.get(filePath);
2204
2205
  if (!source) continue;
2205
- const relativePath = path29.relative(context2.workspaceRoot, filePath);
2206
+ const relativePath = path30.relative(context2.workspaceRoot, filePath);
2206
2207
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2207
2208
  const fileNode = context2.graph.getNode(fileNodeId);
2208
2209
  if (fileNode) {
@@ -2448,11 +2449,11 @@ var init_resolve_phase = __esm({
2448
2449
  let heritageEdges = 0;
2449
2450
  const fileIndex = /* @__PURE__ */ new Map();
2450
2451
  for (const fp of filePaths) {
2451
- const rel = path29.relative(workspaceRoot, fp);
2452
+ const rel = path30.relative(workspaceRoot, fp);
2452
2453
  fileIndex.set(rel, fp);
2453
2454
  const noExt = rel.replace(/\.\w+$/, "");
2454
2455
  if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
2455
- const base = path29.basename(rel, path29.extname(rel));
2456
+ const base = path30.basename(rel, path30.extname(rel));
2456
2457
  if (!fileIndex.has(base)) fileIndex.set(base, fp);
2457
2458
  }
2458
2459
  const symbolIndex = /* @__PURE__ */ new Map();
@@ -2483,7 +2484,7 @@ var init_resolve_phase = __esm({
2483
2484
  for (const filePath of filePaths) {
2484
2485
  const lang = detectLanguage(filePath);
2485
2486
  if (!lang) continue;
2486
- const relativePath = path29.relative(workspaceRoot, filePath);
2487
+ const relativePath = path30.relative(workspaceRoot, filePath);
2487
2488
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2488
2489
  const source = fileCache.get(filePath);
2489
2490
  if (!source) continue;
@@ -2496,13 +2497,13 @@ var init_resolve_phase = __esm({
2496
2497
  let resolvedRelPath = null;
2497
2498
  if (cleaned.startsWith(".")) {
2498
2499
  const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
2499
- const fromDir = path29.dirname(relativePath);
2500
+ const fromDir = path30.dirname(relativePath);
2500
2501
  for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
2501
- const candidate = path29.join(fromDir, cleanedNoJs + ext);
2502
- const normalized = path29.normalize(candidate);
2502
+ const candidate = path30.join(fromDir, cleanedNoJs + ext);
2503
+ const normalized = path30.normalize(candidate);
2503
2504
  if (fileIndex.has(normalized)) {
2504
2505
  const absPath = fileIndex.get(normalized);
2505
- resolvedRelPath = path29.relative(workspaceRoot, absPath);
2506
+ resolvedRelPath = path30.relative(workspaceRoot, absPath);
2506
2507
  break;
2507
2508
  }
2508
2509
  }
@@ -2967,7 +2968,7 @@ var init_db_manager = __esm({
2967
2968
  this.dbPath = dbPath;
2968
2969
  }
2969
2970
  async init() {
2970
- fs27.mkdirSync(path29.dirname(this.dbPath), { recursive: true });
2971
+ fs28.mkdirSync(path30.dirname(this.dbPath), { recursive: true });
2971
2972
  this.db = new Database$1(this.dbPath);
2972
2973
  await this.db.init();
2973
2974
  this.conn = new Connection(this.db);
@@ -3063,7 +3064,7 @@ var init_schema = __esm({
3063
3064
  }
3064
3065
  });
3065
3066
  function writeNodeCSVs(graph, outputDir) {
3066
- fs27.mkdirSync(outputDir, { recursive: true });
3067
+ fs28.mkdirSync(outputDir, { recursive: true });
3067
3068
  const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
3068
3069
  const tableBuffers = /* @__PURE__ */ new Map();
3069
3070
  const tableFilePaths = /* @__PURE__ */ new Map();
@@ -3071,7 +3072,7 @@ function writeNodeCSVs(graph, outputDir) {
3071
3072
  const table = NODE_TABLE_MAP[node.kind];
3072
3073
  if (!tableBuffers.has(table)) {
3073
3074
  tableBuffers.set(table, [header]);
3074
- tableFilePaths.set(table, path29.join(outputDir, `${table}.csv`));
3075
+ tableFilePaths.set(table, path30.join(outputDir, `${table}.csv`));
3075
3076
  }
3076
3077
  tableBuffers.get(table).push(
3077
3078
  csvRow([
@@ -3091,12 +3092,12 @@ function writeNodeCSVs(graph, outputDir) {
3091
3092
  );
3092
3093
  }
3093
3094
  for (const [table, lines] of tableBuffers) {
3094
- fs27.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
3095
+ fs28.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
3095
3096
  }
3096
3097
  return tableFilePaths;
3097
3098
  }
3098
3099
  function writeEdgeCSV(graph, outputDir) {
3099
- fs27.mkdirSync(outputDir, { recursive: true });
3100
+ fs28.mkdirSync(outputDir, { recursive: true });
3100
3101
  const header = "from_id,to_id,kind,weight,label\n";
3101
3102
  const groups = /* @__PURE__ */ new Map();
3102
3103
  for (const edge of graph.allEdges()) {
@@ -3107,7 +3108,7 @@ function writeEdgeCSV(graph, outputDir) {
3107
3108
  const toTable = NODE_TABLE_MAP[targetNode.kind];
3108
3109
  const key = `${fromTable}->${toTable}`;
3109
3110
  if (!groups.has(key)) {
3110
- const filePath = path29.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
3111
+ const filePath = path30.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
3111
3112
  groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
3112
3113
  }
3113
3114
  groups.get(key).lines.push(
@@ -3122,7 +3123,7 @@ function writeEdgeCSV(graph, outputDir) {
3122
3123
  }
3123
3124
  const result = [];
3124
3125
  for (const group of groups.values()) {
3125
- fs27.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
3126
+ fs28.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
3126
3127
  result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
3127
3128
  }
3128
3129
  return result;
@@ -3165,7 +3166,7 @@ async function loadGraphToDB(graph, dbManager) {
3165
3166
  } catch {
3166
3167
  }
3167
3168
  }
3168
- const tmpDir = fs27.mkdtempSync(path29.join(os12.tmpdir(), "code-intel-csv-"));
3169
+ const tmpDir = fs28.mkdtempSync(path30.join(os12.tmpdir(), "code-intel-csv-"));
3169
3170
  try {
3170
3171
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
3171
3172
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -3184,8 +3185,8 @@ async function loadGraphToDB(graph, dbManager) {
3184
3185
  }
3185
3186
  let nodeCount = 0;
3186
3187
  for (const [table, csvPath] of nodeTableFiles) {
3187
- if (!fs27.existsSync(csvPath)) continue;
3188
- const stat = fs27.statSync(csvPath);
3188
+ if (!fs28.existsSync(csvPath)) continue;
3189
+ const stat = fs28.statSync(csvPath);
3189
3190
  if (stat.size < 50) continue;
3190
3191
  try {
3191
3192
  await dbManager.execute(
@@ -3198,8 +3199,8 @@ async function loadGraphToDB(graph, dbManager) {
3198
3199
  }
3199
3200
  let edgeCount = 0;
3200
3201
  for (const group of edgeGroups) {
3201
- if (!fs27.existsSync(group.filePath)) continue;
3202
- const stat = fs27.statSync(group.filePath);
3202
+ if (!fs28.existsSync(group.filePath)) continue;
3203
+ const stat = fs28.statSync(group.filePath);
3203
3204
  if (stat.size < 50) continue;
3204
3205
  try {
3205
3206
  await dbManager.execute(
@@ -3213,7 +3214,7 @@ async function loadGraphToDB(graph, dbManager) {
3213
3214
  return { nodeCount, edgeCount };
3214
3215
  } finally {
3215
3216
  try {
3216
- fs27.rmSync(tmpDir, { recursive: true, force: true });
3217
+ fs28.rmSync(tmpDir, { recursive: true, force: true });
3217
3218
  } catch {
3218
3219
  }
3219
3220
  }
@@ -3315,15 +3316,15 @@ var init_graph_loader = __esm({
3315
3316
  });
3316
3317
  function loadRegistry() {
3317
3318
  try {
3318
- const data = fs27.readFileSync(REPOS_FILE, "utf-8");
3319
+ const data = fs28.readFileSync(REPOS_FILE, "utf-8");
3319
3320
  return JSON.parse(data);
3320
3321
  } catch {
3321
3322
  return [];
3322
3323
  }
3323
3324
  }
3324
3325
  function saveRegistry(entries) {
3325
- fs27.mkdirSync(GLOBAL_DIR, { recursive: true });
3326
- fs27.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
3326
+ fs28.mkdirSync(GLOBAL_DIR, { recursive: true });
3327
+ fs28.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
3327
3328
  }
3328
3329
  function upsertRepo(entry) {
3329
3330
  const entries = loadRegistry();
@@ -3342,28 +3343,28 @@ function removeRepo(repoPath) {
3342
3343
  var GLOBAL_DIR, REPOS_FILE;
3343
3344
  var init_repo_registry = __esm({
3344
3345
  "src/storage/repo-registry.ts"() {
3345
- GLOBAL_DIR = path29.join(os12.homedir(), ".code-intel");
3346
- REPOS_FILE = path29.join(GLOBAL_DIR, "repos.json");
3346
+ GLOBAL_DIR = path30.join(os12.homedir(), ".code-intel");
3347
+ REPOS_FILE = path30.join(GLOBAL_DIR, "repos.json");
3347
3348
  }
3348
3349
  });
3349
3350
  function saveMetadata(repoDir, metadata) {
3350
- const metaDir = path29.join(repoDir, ".code-intel");
3351
- fs27.mkdirSync(metaDir, { recursive: true });
3352
- fs27.writeFileSync(path29.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
3351
+ const metaDir = path30.join(repoDir, ".code-intel");
3352
+ fs28.mkdirSync(metaDir, { recursive: true });
3353
+ fs28.writeFileSync(path30.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
3353
3354
  }
3354
3355
  function loadMetadata(repoDir) {
3355
3356
  try {
3356
- const data = fs27.readFileSync(path29.join(repoDir, ".code-intel", "meta.json"), "utf-8");
3357
+ const data = fs28.readFileSync(path30.join(repoDir, ".code-intel", "meta.json"), "utf-8");
3357
3358
  return JSON.parse(data);
3358
3359
  } catch {
3359
3360
  return null;
3360
3361
  }
3361
3362
  }
3362
3363
  function getDbPath(repoDir) {
3363
- return path29.join(repoDir, ".code-intel", "graph.db");
3364
+ return path30.join(repoDir, ".code-intel", "graph.db");
3364
3365
  }
3365
3366
  function getVectorDbPath(repoDir) {
3366
- return path29.join(repoDir, ".code-intel", "vector.db");
3367
+ return path30.join(repoDir, ".code-intel", "vector.db");
3367
3368
  }
3368
3369
  var init_metadata = __esm({
3369
3370
  "src/storage/metadata.ts"() {
@@ -3419,27 +3420,27 @@ __export(group_registry_exports, {
3419
3420
  saveSyncResult: () => saveSyncResult
3420
3421
  });
3421
3422
  function groupFile(name) {
3422
- return path29.join(GROUPS_DIR, `${name}.json`);
3423
+ return path30.join(GROUPS_DIR, `${name}.json`);
3423
3424
  }
3424
3425
  function loadGroup(name) {
3425
3426
  try {
3426
- return JSON.parse(fs27.readFileSync(groupFile(name), "utf-8"));
3427
+ return JSON.parse(fs28.readFileSync(groupFile(name), "utf-8"));
3427
3428
  } catch {
3428
3429
  return null;
3429
3430
  }
3430
3431
  }
3431
3432
  function saveGroup(group) {
3432
- fs27.mkdirSync(GROUPS_DIR, { recursive: true });
3433
- fs27.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
3433
+ fs28.mkdirSync(GROUPS_DIR, { recursive: true });
3434
+ fs28.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
3434
3435
  }
3435
3436
  function listGroups() {
3436
3437
  const groups = [];
3437
3438
  try {
3438
- for (const file of fs27.readdirSync(GROUPS_DIR)) {
3439
+ for (const file of fs28.readdirSync(GROUPS_DIR)) {
3439
3440
  if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
3440
3441
  try {
3441
3442
  const g = JSON.parse(
3442
- fs27.readFileSync(path29.join(GROUPS_DIR, file), "utf-8")
3443
+ fs28.readFileSync(path30.join(GROUPS_DIR, file), "utf-8")
3443
3444
  );
3444
3445
  groups.push(g);
3445
3446
  } catch {
@@ -3451,16 +3452,16 @@ function listGroups() {
3451
3452
  }
3452
3453
  function deleteGroup(name) {
3453
3454
  try {
3454
- fs27.unlinkSync(groupFile(name));
3455
+ fs28.unlinkSync(groupFile(name));
3455
3456
  } catch {
3456
3457
  }
3457
3458
  try {
3458
- fs27.unlinkSync(path29.join(GROUPS_DIR, `${name}.sync.json`));
3459
+ fs28.unlinkSync(path30.join(GROUPS_DIR, `${name}.sync.json`));
3459
3460
  } catch {
3460
3461
  }
3461
3462
  }
3462
3463
  function groupExists(name) {
3463
- return fs27.existsSync(groupFile(name));
3464
+ return fs28.existsSync(groupFile(name));
3464
3465
  }
3465
3466
  function addMember(groupName, member) {
3466
3467
  const group = loadGroup(groupName);
@@ -3486,16 +3487,16 @@ function removeMember(groupName, groupPath) {
3486
3487
  return group;
3487
3488
  }
3488
3489
  function saveSyncResult(result) {
3489
- fs27.mkdirSync(GROUPS_DIR, { recursive: true });
3490
- fs27.writeFileSync(
3491
- path29.join(GROUPS_DIR, `${result.groupName}.sync.json`),
3490
+ fs28.mkdirSync(GROUPS_DIR, { recursive: true });
3491
+ fs28.writeFileSync(
3492
+ path30.join(GROUPS_DIR, `${result.groupName}.sync.json`),
3492
3493
  JSON.stringify(result, null, 2) + "\n"
3493
3494
  );
3494
3495
  }
3495
3496
  function loadSyncResult(groupName) {
3496
3497
  try {
3497
3498
  return JSON.parse(
3498
- fs27.readFileSync(path29.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
3499
+ fs28.readFileSync(path30.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
3499
3500
  );
3500
3501
  } catch {
3501
3502
  return null;
@@ -3504,7 +3505,7 @@ function loadSyncResult(groupName) {
3504
3505
  var GROUPS_DIR;
3505
3506
  var init_group_registry = __esm({
3506
3507
  "src/multi-repo/group-registry.ts"() {
3507
- GROUPS_DIR = path29.join(os12.homedir(), ".code-intel", "groups");
3508
+ GROUPS_DIR = path30.join(os12.homedir(), ".code-intel", "groups");
3508
3509
  }
3509
3510
  });
3510
3511
 
@@ -3541,10 +3542,10 @@ var init_codes = __esm({
3541
3542
  }
3542
3543
  });
3543
3544
  function secureMkdir(dir) {
3544
- fs27.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
3545
+ fs28.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
3545
3546
  if (process.platform !== "win32") {
3546
3547
  try {
3547
- fs27.chmodSync(dir, SECURE_DIR_MODE);
3548
+ fs28.chmodSync(dir, SECURE_DIR_MODE);
3548
3549
  } catch {
3549
3550
  }
3550
3551
  }
@@ -3552,22 +3553,22 @@ function secureMkdir(dir) {
3552
3553
  function secureChmodFile(file) {
3553
3554
  if (process.platform === "win32") return;
3554
3555
  try {
3555
- fs27.chmodSync(file, SECURE_FILE_MODE);
3556
+ fs28.chmodSync(file, SECURE_FILE_MODE);
3556
3557
  } catch {
3557
3558
  }
3558
3559
  }
3559
3560
  function secureWriteFile(file, data) {
3560
- secureMkdir(path29.dirname(file));
3561
- fs27.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
3561
+ secureMkdir(path30.dirname(file));
3562
+ fs28.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
3562
3563
  secureChmodFile(file);
3563
3564
  }
3564
3565
  function tightenDbFiles(dir) {
3565
3566
  if (process.platform === "win32") return;
3566
- if (!fs27.existsSync(dir)) return;
3567
- for (const name of fs27.readdirSync(dir)) {
3567
+ if (!fs28.existsSync(dir)) return;
3568
+ for (const name of fs28.readdirSync(dir)) {
3568
3569
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
3569
3570
  try {
3570
- fs27.chmodSync(path29.join(dir, name), SECURE_FILE_MODE);
3571
+ fs28.chmodSync(path30.join(dir, name), SECURE_FILE_MODE);
3571
3572
  } catch {
3572
3573
  }
3573
3574
  }
@@ -3581,7 +3582,7 @@ var init_fs_secure = __esm({
3581
3582
  }
3582
3583
  });
3583
3584
  function getUsersDBPath() {
3584
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path29.join(os12.homedir(), ".code-intel", "users.db");
3585
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path30.join(os12.homedir(), ".code-intel", "users.db");
3585
3586
  }
3586
3587
  function getOrCreateUsersDB() {
3587
3588
  if (!_usersDB) {
@@ -3597,7 +3598,7 @@ var init_users_db = __esm({
3597
3598
  UsersDB = class {
3598
3599
  db;
3599
3600
  constructor(dbPath) {
3600
- const dir = path29.dirname(dbPath);
3601
+ const dir = path30.dirname(dbPath);
3601
3602
  secureMkdir(dir);
3602
3603
  this.db = new Database(dbPath);
3603
3604
  this.db.pragma("journal_mode = WAL");
@@ -3874,7 +3875,7 @@ function getScryptN() {
3874
3875
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
3875
3876
  }
3876
3877
  function getSecretsPath() {
3877
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path29.join(os12.homedir(), ".code-intel", ".secrets");
3878
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path30.join(os12.homedir(), ".code-intel", ".secrets");
3878
3879
  }
3879
3880
  function getMasterPassword() {
3880
3881
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
@@ -3916,12 +3917,12 @@ function decryptSecrets(encrypted) {
3916
3917
  return JSON.parse(plaintext.toString("utf8"));
3917
3918
  }
3918
3919
  function loadSecrets(secretsPath = getSecretsPath()) {
3919
- if (!fs27.existsSync(secretsPath)) return {};
3920
- const blob = fs27.readFileSync(secretsPath);
3920
+ if (!fs28.existsSync(secretsPath)) return {};
3921
+ const blob = fs28.readFileSync(secretsPath);
3921
3922
  return decryptSecrets(blob);
3922
3923
  }
3923
3924
  function saveSecrets(blob, secretsPath = getSecretsPath()) {
3924
- secureMkdir(path29.dirname(secretsPath));
3925
+ secureMkdir(path30.dirname(secretsPath));
3925
3926
  const encrypted = encryptSecrets(blob);
3926
3927
  secureWriteFile(secretsPath, encrypted);
3927
3928
  secureChmodFile(secretsPath);
@@ -4395,6 +4396,753 @@ var init_oidc = __esm({
4395
4396
  _cachedIssuer = "";
4396
4397
  }
4397
4398
  });
4399
+
4400
+ // src/query/gql-parser.ts
4401
+ var gql_parser_exports = {};
4402
+ __export(gql_parser_exports, {
4403
+ isGQLParseError: () => isGQLParseError,
4404
+ parseGQL: () => parseGQL
4405
+ });
4406
+ function isGQLParseError(v) {
4407
+ return v.type === "GQLParseError";
4408
+ }
4409
+ function tokenize(input) {
4410
+ const tokens = [];
4411
+ let i = 0;
4412
+ const len = input.length;
4413
+ while (i < len) {
4414
+ if (/\s/.test(input[i])) {
4415
+ i++;
4416
+ continue;
4417
+ }
4418
+ if (input[i] === "#") {
4419
+ while (i < len && input[i] !== "\n") i++;
4420
+ continue;
4421
+ }
4422
+ const pos = i;
4423
+ if (input[i] === '"' || input[i] === "'") {
4424
+ const quote = input[i];
4425
+ i++;
4426
+ let str = "";
4427
+ while (i < len && input[i] !== quote) {
4428
+ if (input[i] === "\\") {
4429
+ i++;
4430
+ if (i < len) {
4431
+ const esc = input[i];
4432
+ str += esc === "n" ? "\n" : esc === "t" ? " " : esc;
4433
+ i++;
4434
+ }
4435
+ } else {
4436
+ str += input[i++];
4437
+ }
4438
+ }
4439
+ if (i >= len) {
4440
+ return { type: "GQLParseError", message: `Unterminated string at position ${pos}`, pos };
4441
+ }
4442
+ i++;
4443
+ tokens.push({ kind: "STRING", value: str, pos });
4444
+ continue;
4445
+ }
4446
+ if (/[0-9]/.test(input[i])) {
4447
+ let num = "";
4448
+ while (i < len && /[0-9]/.test(input[i])) num += input[i++];
4449
+ tokens.push({ kind: "NUMBER", value: num, pos });
4450
+ continue;
4451
+ }
4452
+ if (input[i] === "[") {
4453
+ tokens.push({ kind: "LBRACKET", value: "[", pos });
4454
+ i++;
4455
+ continue;
4456
+ }
4457
+ if (input[i] === "]") {
4458
+ tokens.push({ kind: "RBRACKET", value: "]", pos });
4459
+ i++;
4460
+ continue;
4461
+ }
4462
+ if (input[i] === "*") {
4463
+ tokens.push({ kind: "STAR", value: "*", pos });
4464
+ i++;
4465
+ continue;
4466
+ }
4467
+ if (input[i] === "!" && input[i + 1] === "=") {
4468
+ tokens.push({ kind: "OPERATOR", value: "!=", pos });
4469
+ i += 2;
4470
+ continue;
4471
+ }
4472
+ if (input[i] === "=") {
4473
+ tokens.push({ kind: "OPERATOR", value: "=", pos });
4474
+ i++;
4475
+ continue;
4476
+ }
4477
+ if (/[a-zA-Z_]/.test(input[i])) {
4478
+ let ident = "";
4479
+ while (i < len && /[a-zA-Z0-9_]/.test(input[i])) ident += input[i++];
4480
+ const upper = ident.toUpperCase();
4481
+ if (upper === "CONTAINS" || upper === "STARTS_WITH" || upper === "IN") {
4482
+ tokens.push({ kind: "OPERATOR", value: upper, pos });
4483
+ } else if (KEYWORDS.has(upper)) {
4484
+ tokens.push({ kind: "KEYWORD", value: upper, pos });
4485
+ } else {
4486
+ tokens.push({ kind: "IDENT", value: ident, pos });
4487
+ }
4488
+ continue;
4489
+ }
4490
+ if (input[i] === ",") {
4491
+ i++;
4492
+ continue;
4493
+ }
4494
+ return {
4495
+ type: "GQLParseError",
4496
+ message: `Unexpected character '${input[i]}' at position ${i}`,
4497
+ pos: i
4498
+ };
4499
+ }
4500
+ tokens.push({ kind: "EOF", value: "", pos: len });
4501
+ return tokens;
4502
+ }
4503
+ function parseGQL(input) {
4504
+ const tokens = tokenize(input.trim());
4505
+ if (!Array.isArray(tokens)) return tokens;
4506
+ const parser = new Parser2(tokens);
4507
+ return parser.parse();
4508
+ }
4509
+ var KEYWORDS, Parser2;
4510
+ var init_gql_parser = __esm({
4511
+ "src/query/gql-parser.ts"() {
4512
+ KEYWORDS = /* @__PURE__ */ new Set([
4513
+ "FIND",
4514
+ "TRAVERSE",
4515
+ "PATH",
4516
+ "COUNT",
4517
+ "WHERE",
4518
+ "FROM",
4519
+ "TO",
4520
+ "IN",
4521
+ "BY",
4522
+ "AND",
4523
+ "NOT",
4524
+ "LIMIT",
4525
+ "OFFSET",
4526
+ "DEPTH",
4527
+ "GROUP",
4528
+ "CONTAINS",
4529
+ "STARTS_WITH",
4530
+ "CALLS",
4531
+ "IMPORTS",
4532
+ "EXTENDS",
4533
+ "IMPLEMENTS",
4534
+ "HAS_MEMBER",
4535
+ "ACCESSES",
4536
+ "OVERRIDES",
4537
+ "BELONGS_TO",
4538
+ "STEP_OF",
4539
+ "HANDLES",
4540
+ "CONTAINS_EDGE",
4541
+ "OUTGOING",
4542
+ "INCOMING",
4543
+ "BOTH"
4544
+ ]);
4545
+ Parser2 = class {
4546
+ tokens;
4547
+ pos = 0;
4548
+ constructor(tokens) {
4549
+ this.tokens = tokens;
4550
+ }
4551
+ peek() {
4552
+ return this.tokens[this.pos];
4553
+ }
4554
+ consume() {
4555
+ return this.tokens[this.pos++];
4556
+ }
4557
+ expect(kind, value) {
4558
+ const tok = this.peek();
4559
+ if (tok.kind !== kind) {
4560
+ return {
4561
+ type: "GQLParseError",
4562
+ message: `Expected ${value ?? kind} but got '${tok.value}' at position ${tok.pos}`,
4563
+ pos: tok.pos,
4564
+ expected: value ?? kind,
4565
+ got: tok.value
4566
+ };
4567
+ }
4568
+ if (value !== void 0 && tok.value !== value) {
4569
+ return {
4570
+ type: "GQLParseError",
4571
+ message: `Expected '${value}' but got '${tok.value}' at position ${tok.pos}`,
4572
+ pos: tok.pos,
4573
+ expected: value,
4574
+ got: tok.value
4575
+ };
4576
+ }
4577
+ return this.consume();
4578
+ }
4579
+ matchKeyword(...values) {
4580
+ const tok = this.peek();
4581
+ return tok.kind === "KEYWORD" && values.includes(tok.value);
4582
+ }
4583
+ optionalKeyword(...values) {
4584
+ if (this.matchKeyword(...values)) {
4585
+ return this.consume();
4586
+ }
4587
+ return null;
4588
+ }
4589
+ /** Parse the node kind filter (IDENT, KEYWORD that's a kind, or STAR) */
4590
+ parseNodeKind() {
4591
+ const tok = this.peek();
4592
+ if (tok.kind === "STAR") {
4593
+ this.consume();
4594
+ return "*";
4595
+ }
4596
+ if (tok.kind === "IDENT" || tok.kind === "KEYWORD") {
4597
+ this.consume();
4598
+ return tok.value.toLowerCase();
4599
+ }
4600
+ return {
4601
+ type: "GQLParseError",
4602
+ message: `Expected node kind or '*' at position ${tok.pos}`,
4603
+ pos: tok.pos
4604
+ };
4605
+ }
4606
+ /** Parse a string value (STRING or IDENT) */
4607
+ parseStringValue() {
4608
+ const tok = this.peek();
4609
+ if (tok.kind === "STRING") {
4610
+ this.consume();
4611
+ return tok.value;
4612
+ }
4613
+ if (tok.kind === "IDENT" || tok.kind === "KEYWORD") {
4614
+ this.consume();
4615
+ return tok.value;
4616
+ }
4617
+ return {
4618
+ type: "GQLParseError",
4619
+ message: `Expected string value at position ${tok.pos}`,
4620
+ pos: tok.pos
4621
+ };
4622
+ }
4623
+ /** Parse an IN list: [ value, value, ... ] */
4624
+ parseInList() {
4625
+ const lb = this.expect("LBRACKET");
4626
+ if (isGQLParseError(lb)) return lb;
4627
+ const values = [];
4628
+ while (!this.matchKeyword() && this.peek().kind !== "RBRACKET" && this.peek().kind !== "EOF") {
4629
+ const v = this.parseStringValue();
4630
+ if (typeof v !== "string") return v;
4631
+ values.push(v);
4632
+ }
4633
+ const rb = this.expect("RBRACKET");
4634
+ if (isGQLParseError(rb)) return rb;
4635
+ return values;
4636
+ }
4637
+ /** Parse a single WHERE expression */
4638
+ parseWhereExpr() {
4639
+ const propTok = this.peek();
4640
+ if (propTok.kind !== "IDENT" && propTok.kind !== "KEYWORD") {
4641
+ return {
4642
+ type: "GQLParseError",
4643
+ message: `Expected property name at position ${propTok.pos}`,
4644
+ pos: propTok.pos
4645
+ };
4646
+ }
4647
+ this.consume();
4648
+ const property = propTok.value.toLowerCase();
4649
+ const opTok = this.peek();
4650
+ if (opTok.kind !== "OPERATOR") {
4651
+ return {
4652
+ type: "GQLParseError",
4653
+ message: `Expected operator (=, !=, CONTAINS, STARTS_WITH, IN) at position ${opTok.pos}`,
4654
+ pos: opTok.pos,
4655
+ expected: "operator",
4656
+ got: opTok.value
4657
+ };
4658
+ }
4659
+ this.consume();
4660
+ const operator = opTok.value;
4661
+ if (operator === "IN") {
4662
+ const list = this.parseInList();
4663
+ if (!Array.isArray(list)) return list;
4664
+ return { property, operator, value: list };
4665
+ }
4666
+ const val = this.parseStringValue();
4667
+ if (typeof val !== "string") return val;
4668
+ return { property, operator, value: val };
4669
+ }
4670
+ /** Parse WHERE clause: WHERE expr (AND expr)* */
4671
+ parseWhereClause() {
4672
+ const kw = this.expect("KEYWORD", "WHERE");
4673
+ if (isGQLParseError(kw)) return kw;
4674
+ const exprs = [];
4675
+ const first = this.parseWhereExpr();
4676
+ if ("type" in first && first.type === "GQLParseError") return first;
4677
+ exprs.push(first);
4678
+ while (this.matchKeyword("AND")) {
4679
+ this.consume();
4680
+ const expr = this.parseWhereExpr();
4681
+ if ("type" in expr && expr.type === "GQLParseError") return expr;
4682
+ exprs.push(expr);
4683
+ }
4684
+ return { exprs };
4685
+ }
4686
+ /** Parse FIND statement */
4687
+ parseFindStatement() {
4688
+ this.consume();
4689
+ const kind = this.parseNodeKind();
4690
+ if (typeof kind !== "string") return kind;
4691
+ let where;
4692
+ if (this.matchKeyword("WHERE")) {
4693
+ const w = this.parseWhereClause();
4694
+ if ("type" in w && w.type === "GQLParseError") return w;
4695
+ where = w;
4696
+ }
4697
+ let limit;
4698
+ let offset;
4699
+ while (this.matchKeyword("LIMIT", "OFFSET")) {
4700
+ const kw = this.consume();
4701
+ const numTok = this.peek();
4702
+ if (numTok.kind !== "NUMBER") {
4703
+ return {
4704
+ type: "GQLParseError",
4705
+ message: `Expected number after ${kw.value} at position ${numTok.pos}`,
4706
+ pos: numTok.pos
4707
+ };
4708
+ }
4709
+ this.consume();
4710
+ const n = parseInt(numTok.value, 10);
4711
+ if (kw.value === "LIMIT") limit = n;
4712
+ else offset = n;
4713
+ }
4714
+ return { type: "FIND", target: kind, where, limit, offset };
4715
+ }
4716
+ /** Parse TRAVERSE statement */
4717
+ parseTraverseStatement() {
4718
+ this.consume();
4719
+ const edgeTok = this.peek();
4720
+ if (edgeTok.kind !== "KEYWORD" && edgeTok.kind !== "IDENT") {
4721
+ return {
4722
+ type: "GQLParseError",
4723
+ message: `Expected edge kind after TRAVERSE at position ${edgeTok.pos}`,
4724
+ pos: edgeTok.pos
4725
+ };
4726
+ }
4727
+ this.consume();
4728
+ const edgeKind = edgeTok.value.toLowerCase();
4729
+ const fromKw = this.expect("KEYWORD", "FROM");
4730
+ if (isGQLParseError(fromKw)) return fromKw;
4731
+ const fromVal = this.parseStringValue();
4732
+ if (typeof fromVal !== "string") return fromVal;
4733
+ let depth;
4734
+ let direction;
4735
+ if (this.matchKeyword("DEPTH")) {
4736
+ this.consume();
4737
+ const numTok = this.peek();
4738
+ if (numTok.kind !== "NUMBER") {
4739
+ return {
4740
+ type: "GQLParseError",
4741
+ message: `Expected number after DEPTH at position ${numTok.pos}`,
4742
+ pos: numTok.pos
4743
+ };
4744
+ }
4745
+ this.consume();
4746
+ depth = parseInt(numTok.value, 10);
4747
+ }
4748
+ if (this.matchKeyword("OUTGOING", "INCOMING", "BOTH")) {
4749
+ direction = this.consume().value;
4750
+ }
4751
+ return { type: "TRAVERSE", edgeKind, from: fromVal, depth, direction };
4752
+ }
4753
+ /** Parse PATH statement */
4754
+ parsePathStatement() {
4755
+ this.consume();
4756
+ const fromKw = this.expect("KEYWORD", "FROM");
4757
+ if (isGQLParseError(fromKw)) return fromKw;
4758
+ const fromVal = this.parseStringValue();
4759
+ if (typeof fromVal !== "string") return fromVal;
4760
+ const toKw = this.expect("KEYWORD", "TO");
4761
+ if (isGQLParseError(toKw)) return toKw;
4762
+ const toVal = this.parseStringValue();
4763
+ if (typeof toVal !== "string") return toVal;
4764
+ return { type: "PATH", from: fromVal, to: toVal };
4765
+ }
4766
+ /** Parse COUNT statement */
4767
+ parseCountStatement() {
4768
+ this.consume();
4769
+ const kind = this.parseNodeKind();
4770
+ if (typeof kind !== "string") return kind;
4771
+ let where;
4772
+ if (this.matchKeyword("WHERE")) {
4773
+ const w = this.parseWhereClause();
4774
+ if ("type" in w && w.type === "GQLParseError") return w;
4775
+ where = w;
4776
+ }
4777
+ let groupBy;
4778
+ if (this.matchKeyword("GROUP")) {
4779
+ this.consume();
4780
+ const byKw = this.expect("KEYWORD", "BY");
4781
+ if (isGQLParseError(byKw)) return byKw;
4782
+ const propTok = this.peek();
4783
+ if (propTok.kind !== "IDENT" && propTok.kind !== "KEYWORD") {
4784
+ return {
4785
+ type: "GQLParseError",
4786
+ message: `Expected property name after GROUP BY at position ${propTok.pos}`,
4787
+ pos: propTok.pos
4788
+ };
4789
+ }
4790
+ this.consume();
4791
+ groupBy = propTok.value.toLowerCase();
4792
+ }
4793
+ return { type: "COUNT", target: kind, where, groupBy };
4794
+ }
4795
+ parse() {
4796
+ const tok = this.peek();
4797
+ if (tok.kind !== "KEYWORD") {
4798
+ return {
4799
+ type: "GQLParseError",
4800
+ message: `Expected FIND, TRAVERSE, PATH, or COUNT at position ${tok.pos}`,
4801
+ pos: tok.pos,
4802
+ expected: "FIND | TRAVERSE | PATH | COUNT",
4803
+ got: tok.value
4804
+ };
4805
+ }
4806
+ let result;
4807
+ switch (tok.value) {
4808
+ case "FIND":
4809
+ result = this.parseFindStatement();
4810
+ break;
4811
+ case "TRAVERSE":
4812
+ result = this.parseTraverseStatement();
4813
+ break;
4814
+ case "PATH":
4815
+ result = this.parsePathStatement();
4816
+ break;
4817
+ case "COUNT":
4818
+ result = this.parseCountStatement();
4819
+ break;
4820
+ default:
4821
+ return {
4822
+ type: "GQLParseError",
4823
+ message: `Unknown statement type '${tok.value}' at position ${tok.pos}`,
4824
+ pos: tok.pos,
4825
+ expected: "FIND | TRAVERSE | PATH | COUNT",
4826
+ got: tok.value
4827
+ };
4828
+ }
4829
+ if (isGQLParseError(result)) return result;
4830
+ const remaining = this.peek();
4831
+ if (remaining.kind !== "EOF") {
4832
+ return {
4833
+ type: "GQLParseError",
4834
+ message: `Unexpected token '${remaining.value}' at position ${remaining.pos}`,
4835
+ pos: remaining.pos,
4836
+ got: remaining.value
4837
+ };
4838
+ }
4839
+ return result;
4840
+ }
4841
+ };
4842
+ }
4843
+ });
4844
+
4845
+ // src/query/gql-executor.ts
4846
+ var gql_executor_exports = {};
4847
+ __export(gql_executor_exports, {
4848
+ executeGQL: () => executeGQL
4849
+ });
4850
+ function getNodeProperty(node, property) {
4851
+ switch (property) {
4852
+ case "name":
4853
+ return node.name;
4854
+ case "kind":
4855
+ return node.kind;
4856
+ case "filepath":
4857
+ case "filePath":
4858
+ return node.filePath;
4859
+ case "exported":
4860
+ return node.exported;
4861
+ case "language":
4862
+ return node.metadata?.language ?? void 0;
4863
+ case "cluster":
4864
+ return node.metadata?.cluster ?? void 0;
4865
+ default:
4866
+ return node.metadata?.[property] ?? void 0;
4867
+ }
4868
+ }
4869
+ function evaluateExpr(node, expr) {
4870
+ const val = getNodeProperty(node, expr.property);
4871
+ if (val === void 0) return false;
4872
+ const strVal = String(val).toLowerCase();
4873
+ switch (expr.operator) {
4874
+ case "=":
4875
+ if (typeof expr.value === "string") {
4876
+ return strVal === expr.value.toLowerCase();
4877
+ }
4878
+ return false;
4879
+ case "!=":
4880
+ if (typeof expr.value === "string") {
4881
+ return strVal !== expr.value.toLowerCase();
4882
+ }
4883
+ return true;
4884
+ case "CONTAINS":
4885
+ if (typeof expr.value === "string") {
4886
+ return strVal.includes(expr.value.toLowerCase());
4887
+ }
4888
+ return false;
4889
+ case "STARTS_WITH":
4890
+ if (typeof expr.value === "string") {
4891
+ return strVal.startsWith(expr.value.toLowerCase());
4892
+ }
4893
+ return false;
4894
+ case "IN":
4895
+ if (Array.isArray(expr.value)) {
4896
+ return expr.value.some((v) => strVal === v.toLowerCase());
4897
+ }
4898
+ return false;
4899
+ default:
4900
+ return false;
4901
+ }
4902
+ }
4903
+ function evaluateWhere(node, where) {
4904
+ return where.exprs.every((expr) => evaluateExpr(node, expr));
4905
+ }
4906
+ function executeFIND(stmt, graph) {
4907
+ const start = Date.now();
4908
+ const limit = stmt.limit ?? 1e3;
4909
+ const offset = stmt.offset ?? 0;
4910
+ let totalCount = 0;
4911
+ let truncated = false;
4912
+ const allMatching = [];
4913
+ const deadline = start + EXECUTION_TIMEOUT_MS;
4914
+ for (const node of graph.allNodes()) {
4915
+ if (Date.now() > deadline) {
4916
+ truncated = true;
4917
+ break;
4918
+ }
4919
+ if (stmt.target !== "*" && node.kind !== stmt.target) continue;
4920
+ if (stmt.where && !evaluateWhere(node, stmt.where)) continue;
4921
+ allMatching.push(node);
4922
+ }
4923
+ totalCount = allMatching.length;
4924
+ const paginated = allMatching.slice(offset, offset + limit);
4925
+ return {
4926
+ nodes: paginated,
4927
+ executionTimeMs: Date.now() - start,
4928
+ truncated,
4929
+ totalCount
4930
+ };
4931
+ }
4932
+ function executeTRAVERSE(stmt, graph) {
4933
+ const start = Date.now();
4934
+ const maxDepth = stmt.depth ?? 5;
4935
+ const edgeKind = stmt.edgeKind;
4936
+ const direction = stmt.direction ?? "OUTGOING";
4937
+ const deadline = start + EXECUTION_TIMEOUT_MS;
4938
+ let startNode;
4939
+ for (const node of graph.allNodes()) {
4940
+ if (node.name === stmt.from) {
4941
+ startNode = node;
4942
+ break;
4943
+ }
4944
+ }
4945
+ if (!startNode) {
4946
+ return {
4947
+ nodes: [],
4948
+ edges: [],
4949
+ executionTimeMs: Date.now() - start,
4950
+ truncated: false,
4951
+ totalCount: 0
4952
+ };
4953
+ }
4954
+ const visitedNodes = /* @__PURE__ */ new Set();
4955
+ const visitedEdges = /* @__PURE__ */ new Set();
4956
+ const resultNodes = [];
4957
+ const resultEdges = [];
4958
+ const queue = [{ id: startNode.id, depth: 0 }];
4959
+ visitedNodes.add(startNode.id);
4960
+ resultNodes.push(startNode);
4961
+ let truncated = false;
4962
+ while (queue.length > 0) {
4963
+ if (Date.now() > deadline) {
4964
+ truncated = true;
4965
+ break;
4966
+ }
4967
+ const { id, depth } = queue.shift();
4968
+ if (depth >= maxDepth) continue;
4969
+ const nextEdges = [];
4970
+ if (direction === "OUTGOING" || direction === "BOTH") {
4971
+ for (const edge of graph.findEdgesFrom(id)) {
4972
+ if (!edgeKind || edge.kind === edgeKind) nextEdges.push(edge);
4973
+ }
4974
+ }
4975
+ if (direction === "INCOMING" || direction === "BOTH") {
4976
+ for (const edge of graph.findEdgesTo(id)) {
4977
+ if (!edgeKind || edge.kind === edgeKind) nextEdges.push(edge);
4978
+ }
4979
+ }
4980
+ for (const edge of nextEdges) {
4981
+ if (!visitedEdges.has(edge.id)) {
4982
+ visitedEdges.add(edge.id);
4983
+ resultEdges.push(edge);
4984
+ }
4985
+ const neighborId = direction === "INCOMING" ? edge.source : edge.target;
4986
+ const effectiveNeighborId = direction === "BOTH" ? edge.source === id ? edge.target : edge.source : neighborId;
4987
+ if (!visitedNodes.has(effectiveNeighborId)) {
4988
+ visitedNodes.add(effectiveNeighborId);
4989
+ const neighborNode = graph.getNode(effectiveNeighborId);
4990
+ if (neighborNode) {
4991
+ resultNodes.push(neighborNode);
4992
+ queue.push({ id: effectiveNeighborId, depth: depth + 1 });
4993
+ }
4994
+ }
4995
+ }
4996
+ }
4997
+ return {
4998
+ nodes: resultNodes,
4999
+ edges: resultEdges,
5000
+ executionTimeMs: Date.now() - start,
5001
+ truncated,
5002
+ totalCount: resultNodes.length
5003
+ };
5004
+ }
5005
+ function executePATH(stmt, graph) {
5006
+ const start = Date.now();
5007
+ const deadline = start + EXECUTION_TIMEOUT_MS;
5008
+ let startNode;
5009
+ let endNode;
5010
+ for (const node of graph.allNodes()) {
5011
+ if (node.name === stmt.from) startNode = node;
5012
+ if (node.name === stmt.to) endNode = node;
5013
+ if (startNode && endNode) break;
5014
+ }
5015
+ if (!startNode || !endNode) {
5016
+ return {
5017
+ path: null,
5018
+ nodes: [],
5019
+ executionTimeMs: Date.now() - start,
5020
+ truncated: false,
5021
+ totalCount: 0
5022
+ };
5023
+ }
5024
+ const visited = /* @__PURE__ */ new Set();
5025
+ const parent = /* @__PURE__ */ new Map();
5026
+ const queue = [startNode.id];
5027
+ visited.add(startNode.id);
5028
+ let found = false;
5029
+ let truncated = false;
5030
+ outer: while (queue.length > 0) {
5031
+ if (Date.now() > deadline) {
5032
+ truncated = true;
5033
+ break;
5034
+ }
5035
+ const current2 = queue.shift();
5036
+ for (const edge of graph.findEdgesFrom(current2)) {
5037
+ const next = edge.target;
5038
+ if (!visited.has(next)) {
5039
+ visited.add(next);
5040
+ parent.set(next, { nodeId: current2, edgeId: edge.id });
5041
+ if (next === endNode.id) {
5042
+ found = true;
5043
+ break outer;
5044
+ }
5045
+ queue.push(next);
5046
+ }
5047
+ }
5048
+ for (const edge of graph.findEdgesTo(current2)) {
5049
+ const next = edge.source;
5050
+ if (!visited.has(next)) {
5051
+ visited.add(next);
5052
+ parent.set(next, { nodeId: current2, edgeId: edge.id });
5053
+ if (next === endNode.id) {
5054
+ found = true;
5055
+ break outer;
5056
+ }
5057
+ queue.push(next);
5058
+ }
5059
+ }
5060
+ }
5061
+ if (!found) {
5062
+ return {
5063
+ path: null,
5064
+ nodes: [],
5065
+ executionTimeMs: Date.now() - start,
5066
+ truncated,
5067
+ totalCount: 0
5068
+ };
5069
+ }
5070
+ const pathNodeIds = [];
5071
+ const pathEdgeIds = [];
5072
+ let current = endNode.id;
5073
+ while (current !== startNode.id) {
5074
+ pathNodeIds.unshift(current);
5075
+ const p = parent.get(current);
5076
+ pathEdgeIds.unshift(p.edgeId);
5077
+ current = p.nodeId;
5078
+ }
5079
+ pathNodeIds.unshift(startNode.id);
5080
+ const pathNodes = pathNodeIds.map((id) => graph.getNode(id)).filter(Boolean);
5081
+ const pathEdges = pathEdgeIds.map((id) => graph.getEdge(id)).filter(Boolean);
5082
+ return {
5083
+ path: pathNodes,
5084
+ nodes: pathNodes,
5085
+ edges: pathEdges,
5086
+ executionTimeMs: Date.now() - start,
5087
+ truncated,
5088
+ totalCount: pathNodes.length
5089
+ };
5090
+ }
5091
+ function executeCOUNT(stmt, graph) {
5092
+ const start = Date.now();
5093
+ const deadline = start + EXECUTION_TIMEOUT_MS;
5094
+ let truncated = false;
5095
+ const groups = /* @__PURE__ */ new Map();
5096
+ let total = 0;
5097
+ for (const node of graph.allNodes()) {
5098
+ if (Date.now() > deadline) {
5099
+ truncated = true;
5100
+ break;
5101
+ }
5102
+ if (stmt.target !== "*" && node.kind !== stmt.target) continue;
5103
+ if (stmt.where && !evaluateWhere(node, stmt.where)) continue;
5104
+ total++;
5105
+ if (stmt.groupBy) {
5106
+ const key = String(getNodeProperty(node, stmt.groupBy) ?? "(none)");
5107
+ groups.set(key, (groups.get(key) ?? 0) + 1);
5108
+ } else {
5109
+ groups.set("total", (groups.get("total") ?? 0) + 1);
5110
+ }
5111
+ }
5112
+ const groupList = [...groups.entries()].map(([key, count]) => ({ key, count }));
5113
+ groupList.sort((a, b) => b.count - a.count);
5114
+ return {
5115
+ groups: groupList,
5116
+ executionTimeMs: Date.now() - start,
5117
+ truncated,
5118
+ totalCount: total
5119
+ };
5120
+ }
5121
+ function executeGQL(ast, graph) {
5122
+ switch (ast.type) {
5123
+ case "FIND":
5124
+ return executeFIND(ast, graph);
5125
+ case "TRAVERSE":
5126
+ return executeTRAVERSE(ast, graph);
5127
+ case "PATH":
5128
+ return executePATH(ast, graph);
5129
+ case "COUNT":
5130
+ return executeCOUNT(ast, graph);
5131
+ default:
5132
+ return {
5133
+ nodes: [],
5134
+ executionTimeMs: 0,
5135
+ truncated: false,
5136
+ totalCount: 0
5137
+ };
5138
+ }
5139
+ }
5140
+ var EXECUTION_TIMEOUT_MS;
5141
+ var init_gql_executor = __esm({
5142
+ "src/query/gql-executor.ts"() {
5143
+ EXECUTION_TIMEOUT_MS = 1e4;
5144
+ }
5145
+ });
4398
5146
  function verifyWebSocketHandshake(req) {
4399
5147
  const cookieHeader = req.headers["cookie"] ?? "";
4400
5148
  const cookies = parseCookies(cookieHeader);
@@ -4874,10 +5622,10 @@ var init_file_watcher = __esm({
4874
5622
  }
4875
5623
  // ── private ─────────────────────────────────────────────────────────────────
4876
5624
  readCodeIntelIgnore() {
4877
- const ignoreFile = path29.join(this.workspaceRoot, ".codeintelignore");
5625
+ const ignoreFile = path30.join(this.workspaceRoot, ".codeintelignore");
4878
5626
  try {
4879
- if (!fs27.existsSync(ignoreFile)) return [];
4880
- return fs27.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
5627
+ if (!fs28.existsSync(ignoreFile)) return [];
5628
+ return fs28.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
4881
5629
  } catch {
4882
5630
  return [];
4883
5631
  }
@@ -4920,25 +5668,29 @@ var init_incremental_indexer = __esm({
4920
5668
  return { filesProcessed: 0, nodesRemoved: 0, nodesAdded: 0, duration: 0 };
4921
5669
  }
4922
5670
  let nodesRemoved = 0;
5671
+ const nodesByFilePath = /* @__PURE__ */ new Map();
5672
+ for (const node of graph.allNodes()) {
5673
+ if (!node.filePath) continue;
5674
+ const ids = nodesByFilePath.get(node.filePath);
5675
+ if (ids) ids.push(node.id);
5676
+ else nodesByFilePath.set(node.filePath, [node.id]);
5677
+ }
5678
+ const nodeIdsToRemove = /* @__PURE__ */ new Set();
4923
5679
  for (const absPath of changedFiles) {
4924
- const relPath2 = path29.relative(workspaceRoot, absPath);
4925
- const toRemove = [];
4926
- for (const node of graph.allNodes()) {
4927
- if (node.filePath === relPath2 || node.filePath === absPath) {
4928
- toRemove.push(node.id);
4929
- }
4930
- }
4931
- for (const id of toRemove) {
4932
- graph.removeNodeCascade(id);
4933
- nodesRemoved++;
4934
- }
5680
+ const relPath2 = path30.relative(workspaceRoot, absPath);
5681
+ for (const id of nodesByFilePath.get(relPath2) ?? []) nodeIdsToRemove.add(id);
5682
+ for (const id of nodesByFilePath.get(absPath) ?? []) nodeIdsToRemove.add(id);
4935
5683
  }
4936
- if (fs27.existsSync(dbPath)) {
5684
+ for (const id of nodeIdsToRemove) {
5685
+ graph.removeNodeCascade(id);
5686
+ nodesRemoved++;
5687
+ }
5688
+ if (fs28.existsSync(dbPath)) {
4937
5689
  try {
4938
5690
  const db = new DbManager(dbPath);
4939
5691
  await db.init();
4940
5692
  for (const absPath of changedFiles) {
4941
- const relPath2 = path29.relative(workspaceRoot, absPath);
5693
+ const relPath2 = path30.relative(workspaceRoot, absPath);
4942
5694
  await removeNodesForFile(relPath2, db);
4943
5695
  }
4944
5696
  db.close();
@@ -4948,7 +5700,7 @@ var init_incremental_indexer = __esm({
4948
5700
  }
4949
5701
  const existingFiles = changedFiles.filter((f) => {
4950
5702
  try {
4951
- return fs27.statSync(f).isFile();
5703
+ return fs28.statSync(f).isFile();
4952
5704
  } catch {
4953
5705
  return false;
4954
5706
  }
@@ -4970,13 +5722,13 @@ var init_incremental_indexer = __esm({
4970
5722
  await runPipeline([noopScan, parsePhase, resolvePhase], context2);
4971
5723
  }
4972
5724
  const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
4973
- if (fs27.existsSync(dbPath) && existingFiles.length > 0) {
5725
+ if (fs28.existsSync(dbPath) && existingFiles.length > 0) {
4974
5726
  try {
4975
5727
  const db = new DbManager(dbPath);
4976
5728
  await db.init();
4977
- const changedRelPaths = new Set(changedFiles.map((f) => path29.relative(workspaceRoot, f)));
5729
+ const changedRelPaths = new Set(changedFiles.map((f) => path30.relative(workspaceRoot, f)));
4978
5730
  const nodesToUpsert = [...graph.allNodes()].filter(
4979
- (n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path29.relative(workspaceRoot, n.filePath))
5731
+ (n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path30.relative(workspaceRoot, n.filePath))
4980
5732
  );
4981
5733
  await upsertNodes(nodesToUpsert, db);
4982
5734
  db.close();
@@ -4997,6 +5749,70 @@ var init_incremental_indexer = __esm({
4997
5749
  }
4998
5750
  });
4999
5751
 
5752
+ // src/query/saved-queries.ts
5753
+ var saved_queries_exports = {};
5754
+ __export(saved_queries_exports, {
5755
+ deleteQuery: () => deleteQuery,
5756
+ listQueries: () => listQueries,
5757
+ loadQuery: () => loadQuery,
5758
+ queryExists: () => queryExists,
5759
+ saveQuery: () => saveQuery
5760
+ });
5761
+ function getQueriesDir(workspaceRoot) {
5762
+ return path30.join(workspaceRoot, ".code-intel", "queries");
5763
+ }
5764
+ function ensureQueriesDir(workspaceRoot) {
5765
+ const dir = getQueriesDir(workspaceRoot);
5766
+ if (!fs28.existsSync(dir)) {
5767
+ fs28.mkdirSync(dir, { recursive: true });
5768
+ }
5769
+ return dir;
5770
+ }
5771
+ function saveQuery(workspaceRoot, name, gql) {
5772
+ const dir = ensureQueriesDir(workspaceRoot);
5773
+ const filePath = path30.join(dir, `${name}.gql`);
5774
+ fs28.writeFileSync(filePath, gql, "utf-8");
5775
+ }
5776
+ function loadQuery(workspaceRoot, name) {
5777
+ const dir = getQueriesDir(workspaceRoot);
5778
+ const filePath = path30.join(dir, `${name}.gql`);
5779
+ if (!fs28.existsSync(filePath)) return null;
5780
+ return fs28.readFileSync(filePath, "utf-8");
5781
+ }
5782
+ function listQueries(workspaceRoot) {
5783
+ const dir = getQueriesDir(workspaceRoot);
5784
+ if (!fs28.existsSync(dir)) return [];
5785
+ const files = fs28.readdirSync(dir).filter((f) => f.endsWith(".gql"));
5786
+ return files.map((f) => {
5787
+ const filePath = path30.join(dir, f);
5788
+ const name = f.replace(/\.gql$/, "");
5789
+ const content = fs28.readFileSync(filePath, "utf-8");
5790
+ const stat = fs28.statSync(filePath);
5791
+ return {
5792
+ name,
5793
+ content,
5794
+ filePath,
5795
+ savedAt: stat.mtime.toISOString()
5796
+ };
5797
+ }).sort((a, b) => a.name.localeCompare(b.name));
5798
+ }
5799
+ function deleteQuery(workspaceRoot, name) {
5800
+ const dir = getQueriesDir(workspaceRoot);
5801
+ const filePath = path30.join(dir, `${name}.gql`);
5802
+ if (!fs28.existsSync(filePath)) return false;
5803
+ fs28.unlinkSync(filePath);
5804
+ return true;
5805
+ }
5806
+ function queryExists(workspaceRoot, name) {
5807
+ const dir = getQueriesDir(workspaceRoot);
5808
+ const filePath = path30.join(dir, `${name}.gql`);
5809
+ return fs28.existsSync(filePath);
5810
+ }
5811
+ var init_saved_queries = __esm({
5812
+ "src/query/saved-queries.ts"() {
5813
+ }
5814
+ });
5815
+
5000
5816
  // src/cli/main.ts
5001
5817
  init_logger();
5002
5818
 
@@ -5160,7 +5976,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
5160
5976
  ]);
5161
5977
  function loadIgnorePatterns(workspaceRoot) {
5162
5978
  try {
5163
- const raw = fs27.readFileSync(path29.join(workspaceRoot, ".codeintelignore"), "utf-8");
5979
+ const raw = fs28.readFileSync(path30.join(workspaceRoot, ".codeintelignore"), "utf-8");
5164
5980
  const extras = /* @__PURE__ */ new Set();
5165
5981
  for (const line of raw.split("\n")) {
5166
5982
  const trimmed = line.trim();
@@ -5184,7 +6000,7 @@ var scanPhase = {
5184
6000
  function walk2(dir) {
5185
6001
  let entries;
5186
6002
  try {
5187
- entries = fs27.readdirSync(dir, { withFileTypes: true });
6003
+ entries = fs28.readdirSync(dir, { withFileTypes: true });
5188
6004
  } catch {
5189
6005
  return;
5190
6006
  }
@@ -5193,15 +6009,15 @@ var scanPhase = {
5193
6009
  if (entry.name.startsWith(".")) continue;
5194
6010
  if (IGNORED_DIRS.has(entry.name)) continue;
5195
6011
  if (extraIgnore.has(entry.name)) continue;
5196
- walk2(path29.join(dir, entry.name));
6012
+ walk2(path30.join(dir, entry.name));
5197
6013
  } else if (entry.isFile()) {
5198
6014
  const name = entry.name;
5199
6015
  if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
5200
- const ext = path29.extname(name);
6016
+ const ext = path30.extname(name);
5201
6017
  if (!extensions.has(ext)) continue;
5202
- const fullPath = path29.join(dir, name);
6018
+ const fullPath = path30.join(dir, name);
5203
6019
  try {
5204
- const stat = fs27.statSync(fullPath);
6020
+ const stat = fs28.statSync(fullPath);
5205
6021
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
5206
6022
  } catch {
5207
6023
  continue;
@@ -5228,20 +6044,20 @@ var structurePhase = {
5228
6044
  const dirs = /* @__PURE__ */ new Set();
5229
6045
  let structDone = 0;
5230
6046
  for (const filePath of context2.filePaths) {
5231
- const relativePath = path29.relative(context2.workspaceRoot, filePath);
6047
+ const relativePath = path30.relative(context2.workspaceRoot, filePath);
5232
6048
  const lang = detectLanguage(filePath);
5233
6049
  context2.graph.addNode({
5234
6050
  id: generateNodeId("file", relativePath, relativePath),
5235
6051
  kind: "file",
5236
- name: path29.basename(filePath),
6052
+ name: path30.basename(filePath),
5237
6053
  filePath: relativePath,
5238
6054
  metadata: lang ? { language: lang } : void 0
5239
6055
  });
5240
- let dir = path29.dirname(relativePath);
6056
+ let dir = path30.dirname(relativePath);
5241
6057
  while (dir && dir !== "." && dir !== "") {
5242
6058
  if (dirs.has(dir)) break;
5243
6059
  dirs.add(dir);
5244
- dir = path29.dirname(dir);
6060
+ dir = path30.dirname(dir);
5245
6061
  }
5246
6062
  structDone++;
5247
6063
  context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
@@ -5250,7 +6066,7 @@ var structurePhase = {
5250
6066
  context2.graph.addNode({
5251
6067
  id: generateNodeId("directory", dir, dir),
5252
6068
  kind: "directory",
5253
- name: path29.basename(dir),
6069
+ name: path30.basename(dir),
5254
6070
  filePath: dir
5255
6071
  });
5256
6072
  }
@@ -5361,22 +6177,22 @@ var flowPhase = {
5361
6177
  const queue = [{ nodeId: ep.id, path: [ep.id] }];
5362
6178
  const visited = /* @__PURE__ */ new Set();
5363
6179
  while (queue.length > 0 && flowCount < maxFlows) {
5364
- const { nodeId, path: path30 } = queue.shift();
5365
- if (path30.length > maxDepth) continue;
6180
+ const { nodeId, path: path31 } = queue.shift();
6181
+ if (path31.length > maxDepth) continue;
5366
6182
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
5367
- if (callEdges.length === 0 && path30.length >= 3) {
6183
+ if (callEdges.length === 0 && path31.length >= 3) {
5368
6184
  const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
5369
6185
  graph.addNode({
5370
6186
  id: flowId,
5371
6187
  kind: "flow",
5372
6188
  name: `${ep.name} flow ${flowCount}`,
5373
6189
  filePath: ep.filePath,
5374
- metadata: { steps: path30, entryPoint: ep.name }
6190
+ metadata: { steps: path31, entryPoint: ep.name }
5375
6191
  });
5376
- for (let i = 0; i < path30.length; i++) {
6192
+ for (let i = 0; i < path31.length; i++) {
5377
6193
  graph.addEdge({
5378
- id: generateEdgeId(path30[i], flowId, `step_of_${i}`),
5379
- source: path30[i],
6194
+ id: generateEdgeId(path31[i], flowId, `step_of_${i}`),
6195
+ source: path31[i],
5380
6196
  target: flowId,
5381
6197
  kind: "step_of",
5382
6198
  weight: 1,
@@ -5389,7 +6205,7 @@ var flowPhase = {
5389
6205
  for (const edge of callEdges) {
5390
6206
  if (visited.has(edge.target)) continue;
5391
6207
  visited.add(edge.target);
5392
- queue.push({ nodeId: edge.target, path: [...path30, edge.target] });
6208
+ queue.push({ nodeId: edge.target, path: [...path31, edge.target] });
5393
6209
  }
5394
6210
  }
5395
6211
  }
@@ -5407,7 +6223,7 @@ var LLMGovernanceLogger = class {
5407
6223
  }
5408
6224
  /** Path to the JSONL log file. */
5409
6225
  getLogPath() {
5410
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path29.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
6226
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path30.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
5411
6227
  }
5412
6228
  /**
5413
6229
  * Append an entry to the governance log.
@@ -5423,8 +6239,8 @@ var LLMGovernanceLogger = class {
5423
6239
  ...entry
5424
6240
  };
5425
6241
  const logPath = this.getLogPath();
5426
- fs27.mkdirSync(path29.dirname(logPath), { recursive: true });
5427
- fs27.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
6242
+ fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
6243
+ fs28.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
5428
6244
  } catch {
5429
6245
  }
5430
6246
  }
@@ -5434,7 +6250,7 @@ var LLMGovernanceLogger = class {
5434
6250
  */
5435
6251
  readLog(limit = 100) {
5436
6252
  try {
5437
- const raw = fs27.readFileSync(this.getLogPath(), "utf-8");
6253
+ const raw = fs28.readFileSync(this.getLogPath(), "utf-8");
5438
6254
  const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
5439
6255
  return lines.map((l) => JSON.parse(l));
5440
6256
  } catch {
@@ -5748,7 +6564,7 @@ var LANG_QUERIES2 = {
5748
6564
  };
5749
6565
  function workerScriptPath() {
5750
6566
  const thisFile = fileURLToPath(import.meta.url);
5751
- return path29.join(path29.dirname(thisFile), "parse-worker.js");
6567
+ return path30.join(path30.dirname(thisFile), "parse-worker.js");
5752
6568
  }
5753
6569
  var parsePhaseParallel = {
5754
6570
  name: "parse",
@@ -5764,14 +6580,14 @@ var parsePhaseParallel = {
5764
6580
  const batch = filePaths.slice(i, i + CONCURRENCY);
5765
6581
  await Promise.all(batch.map(async (filePath) => {
5766
6582
  try {
5767
- const source = await fs27.promises.readFile(filePath, "utf-8");
6583
+ const source = await fs28.promises.readFile(filePath, "utf-8");
5768
6584
  context2.fileCache.set(filePath, source);
5769
6585
  } catch {
5770
6586
  }
5771
6587
  }));
5772
6588
  }
5773
6589
  const workerScript = workerScriptPath();
5774
- const workerScriptExists = fs27.existsSync(workerScript);
6590
+ const workerScriptExists = fs28.existsSync(workerScript);
5775
6591
  if (!workerScriptExists || workerCount === 1) {
5776
6592
  logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
5777
6593
  const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
@@ -5783,7 +6599,7 @@ var parsePhaseParallel = {
5783
6599
  if (!lang) continue;
5784
6600
  const source = context2.fileCache.get(filePath);
5785
6601
  if (!source) continue;
5786
- const relativePath = path29.relative(context2.workspaceRoot, filePath);
6602
+ const relativePath = path30.relative(context2.workspaceRoot, filePath);
5787
6603
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
5788
6604
  const fileNode = context2.graph.getNode(fileNodeId);
5789
6605
  if (fileNode) fileNode.content = source.slice(0, 2e3);
@@ -5826,7 +6642,7 @@ var parsePhaseParallel = {
5826
6642
  symbolCount += res.nodes.length;
5827
6643
  if (res.usedTreeSitter) treeSitterCount++;
5828
6644
  else regexCount++;
5829
- const relativePath = path29.relative(context2.workspaceRoot, res.taskId);
6645
+ const relativePath = path30.relative(context2.workspaceRoot, res.taskId);
5830
6646
  const funcs = res.nodes.filter((n) => n.kind === "function" || n.kind === "method").map((n) => ({ id: n.id, startLine: n.startLine ?? 0, endLine: n.endLine })).sort((a, b) => a.startLine - b.startLine);
5831
6647
  if (funcs.length > 0) context2.fileFunctionIndex.set(relativePath, funcs);
5832
6648
  parseDone++;
@@ -5853,7 +6669,7 @@ init_id_generator();
5853
6669
  init_logger();
5854
6670
  function workerScriptPath2() {
5855
6671
  const thisFile = fileURLToPath(import.meta.url);
5856
- return path29.join(path29.dirname(thisFile), "resolve-worker.js");
6672
+ return path30.join(path30.dirname(thisFile), "resolve-worker.js");
5857
6673
  }
5858
6674
  var resolvePhaseParallel = {
5859
6675
  name: "resolve",
@@ -5865,11 +6681,11 @@ var resolvePhaseParallel = {
5865
6681
  const fileFunctionIndex = context2.fileFunctionIndex ?? /* @__PURE__ */ new Map();
5866
6682
  const fileIndex = {};
5867
6683
  for (const fp of filePaths) {
5868
- const rel = path29.relative(workspaceRoot, fp);
6684
+ const rel = path30.relative(workspaceRoot, fp);
5869
6685
  fileIndex[rel] = fp;
5870
6686
  const noExt = rel.replace(/\.\w+$/, "");
5871
6687
  if (!fileIndex[noExt]) fileIndex[noExt] = fp;
5872
- const base = path29.basename(rel, path29.extname(rel));
6688
+ const base = path30.basename(rel, path30.extname(rel));
5873
6689
  if (!fileIndex[base]) fileIndex[base] = fp;
5874
6690
  }
5875
6691
  const symbolIndex = {};
@@ -5883,7 +6699,7 @@ var resolvePhaseParallel = {
5883
6699
  }
5884
6700
  const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
5885
6701
  const workerScript = workerScriptPath2();
5886
- const workerScriptExists = fs27.existsSync(workerScript);
6702
+ const workerScriptExists = fs28.existsSync(workerScript);
5887
6703
  const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os12.cpus().length - 1);
5888
6704
  if (!workerScriptExists || workerCount === 1) {
5889
6705
  logger_default.info(`[resolve-parallel] falling back to sequential`);
@@ -5896,7 +6712,7 @@ var resolvePhaseParallel = {
5896
6712
  if (!lang) continue;
5897
6713
  const source = fileCache.get(filePath);
5898
6714
  if (!source) continue;
5899
- const relativePath = path29.relative(workspaceRoot, filePath);
6715
+ const relativePath = path30.relative(workspaceRoot, filePath);
5900
6716
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
5901
6717
  const funcList = fileFunctionIndex.get(relativePath) ?? [];
5902
6718
  tasks.push({ taskId: filePath, filePath, relativePath, fileNodeId, source, funcList });
@@ -6019,7 +6835,7 @@ init_embedder();
6019
6835
  async function hybridSearch(graph, query, limit, options = {}) {
6020
6836
  const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
6021
6837
  const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
6022
- const hasVectorDb = Boolean(vectorDbPath && fs27.existsSync(vectorDbPath));
6838
+ const hasVectorDb = Boolean(vectorDbPath && fs28.existsSync(vectorDbPath));
6023
6839
  if (!hasVectorDb) {
6024
6840
  const bm25Results2 = await bm25Promise;
6025
6841
  return {
@@ -6273,8 +7089,8 @@ async function syncGroup(group) {
6273
7089
  logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
6274
7090
  continue;
6275
7091
  }
6276
- const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
6277
- if (!fs27.existsSync(dbPath)) {
7092
+ const dbPath = path30.join(regEntry.path, ".code-intel", "graph.db");
7093
+ if (!fs28.existsSync(dbPath)) {
6278
7094
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
6279
7095
  continue;
6280
7096
  }
@@ -6311,8 +7127,8 @@ async function queryGroup(group, query, limit = 20) {
6311
7127
  for (const member of group.members) {
6312
7128
  const regEntry = registry.find((r) => r.name === member.registryName);
6313
7129
  if (!regEntry) continue;
6314
- const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
6315
- if (!fs27.existsSync(dbPath)) continue;
7130
+ const dbPath = path30.join(regEntry.path, ".code-intel", "graph.db");
7131
+ if (!fs28.existsSync(dbPath)) continue;
6316
7132
  const graph = createKnowledgeGraph();
6317
7133
  const db = new DbManager(dbPath);
6318
7134
  try {
@@ -6352,7 +7168,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
6352
7168
  var JobsDB = class {
6353
7169
  db;
6354
7170
  constructor(dbPath) {
6355
- fs27.mkdirSync(path29.dirname(dbPath), { recursive: true });
7171
+ fs28.mkdirSync(path30.dirname(dbPath), { recursive: true });
6356
7172
  this.db = new Database(dbPath);
6357
7173
  this.db.pragma("journal_mode = WAL");
6358
7174
  this.db.pragma("foreign_keys = ON");
@@ -6494,7 +7310,7 @@ var JobsDB = class {
6494
7310
  }
6495
7311
  };
6496
7312
  function getJobsDBPath() {
6497
- return path29.join(os12.homedir(), ".code-intel", "jobs.db");
7313
+ return path30.join(os12.homedir(), ".code-intel", "jobs.db");
6498
7314
  }
6499
7315
  var _jobsDB = null;
6500
7316
  function getOrCreateJobsDB() {
@@ -6586,7 +7402,7 @@ var BACKUP_VERSION = "1.0";
6586
7402
  var ALGORITHM = "aes-256-gcm";
6587
7403
  var IV_LENGTH = 16;
6588
7404
  function getBackupDir() {
6589
- return path29.join(os12.homedir(), ".code-intel", "backups");
7405
+ return path30.join(os12.homedir(), ".code-intel", "backups");
6590
7406
  }
6591
7407
  function getBackupKey() {
6592
7408
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
@@ -6617,30 +7433,30 @@ var BackupService = class {
6617
7433
  constructor(backupDir) {
6618
7434
  this.backupDir = backupDir ?? getBackupDir();
6619
7435
  this.key = getBackupKey();
6620
- fs27.mkdirSync(this.backupDir, { recursive: true });
7436
+ fs28.mkdirSync(this.backupDir, { recursive: true });
6621
7437
  }
6622
7438
  /**
6623
7439
  * Create a backup for a repository.
6624
7440
  * Returns the backup entry.
6625
7441
  */
6626
7442
  createBackup(repoPath) {
6627
- const codeIntelDir = path29.join(repoPath, ".code-intel");
7443
+ const codeIntelDir = path30.join(repoPath, ".code-intel");
6628
7444
  const id = v4();
6629
7445
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
6630
7446
  const filesToBackup = [];
6631
7447
  const candidates = ["graph.db", "vector.db", "meta.json"];
6632
7448
  for (const f of candidates) {
6633
- const fp = path29.join(codeIntelDir, f);
6634
- if (fs27.existsSync(fp)) {
7449
+ const fp = path30.join(codeIntelDir, f);
7450
+ if (fs28.existsSync(fp)) {
6635
7451
  filesToBackup.push({ name: f, localPath: fp });
6636
7452
  }
6637
7453
  }
6638
- const registryPath = path29.join(os12.homedir(), ".code-intel", "registry.json");
6639
- if (fs27.existsSync(registryPath)) {
7454
+ const registryPath = path30.join(os12.homedir(), ".code-intel", "registry.json");
7455
+ if (fs28.existsSync(registryPath)) {
6640
7456
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
6641
7457
  }
6642
- const usersDbPath = path29.join(os12.homedir(), ".code-intel", "users.db");
6643
- if (fs27.existsSync(usersDbPath)) {
7458
+ const usersDbPath = path30.join(os12.homedir(), ".code-intel", "users.db");
7459
+ if (fs28.existsSync(usersDbPath)) {
6644
7460
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
6645
7461
  }
6646
7462
  if (filesToBackup.length === 0) {
@@ -6651,7 +7467,7 @@ var BackupService = class {
6651
7467
  createdAt,
6652
7468
  version: BACKUP_VERSION,
6653
7469
  files: filesToBackup.map((f) => {
6654
- const data = fs27.readFileSync(f.localPath);
7470
+ const data = fs28.readFileSync(f.localPath);
6655
7471
  return {
6656
7472
  name: f.name,
6657
7473
  sha256: crypto5.createHash("sha256").update(data).digest("hex"),
@@ -6665,7 +7481,7 @@ var BackupService = class {
6665
7481
  manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
6666
7482
  parts.push(manifestLenBuf, manifestBuf);
6667
7483
  for (const f of filesToBackup) {
6668
- const data = fs27.readFileSync(f.localPath);
7484
+ const data = fs28.readFileSync(f.localPath);
6669
7485
  const nameBuf = Buffer.from(f.name, "utf-8");
6670
7486
  const nameLenBuf = Buffer.alloc(2);
6671
7487
  nameLenBuf.writeUInt16BE(nameBuf.length, 0);
@@ -6676,8 +7492,8 @@ var BackupService = class {
6676
7492
  const plaintext = Buffer.concat(parts);
6677
7493
  const encrypted = encryptBuffer(plaintext, this.key);
6678
7494
  const backupFileName = `backup-${id}.cib`;
6679
- const backupPath = path29.join(this.backupDir, backupFileName);
6680
- fs27.writeFileSync(backupPath, encrypted);
7495
+ const backupPath = path30.join(this.backupDir, backupFileName);
7496
+ fs28.writeFileSync(backupPath, encrypted);
6681
7497
  const entry = {
6682
7498
  id,
6683
7499
  createdAt,
@@ -6704,9 +7520,9 @@ var BackupService = class {
6704
7520
  async uploadToS3(entry) {
6705
7521
  const cfg = getS3Config();
6706
7522
  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.");
6707
- const fileName = path29.basename(entry.path);
7523
+ const fileName = path30.basename(entry.path);
6708
7524
  const s3Key = `${cfg.prefix}${fileName}`;
6709
- const body = fs27.readFileSync(entry.path);
7525
+ const body = fs28.readFileSync(entry.path);
6710
7526
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
6711
7527
  if (result.statusCode < 200 || result.statusCode >= 300) {
6712
7528
  throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
@@ -6723,8 +7539,8 @@ var BackupService = class {
6723
7539
  if (result.statusCode < 200 || result.statusCode >= 300) {
6724
7540
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
6725
7541
  }
6726
- fs27.mkdirSync(path29.dirname(destPath), { recursive: true });
6727
- fs27.writeFileSync(destPath, Buffer.from(result.body, "binary"));
7542
+ fs28.mkdirSync(path30.dirname(destPath), { recursive: true });
7543
+ fs28.writeFileSync(destPath, Buffer.from(result.body, "binary"));
6728
7544
  }
6729
7545
  /**
6730
7546
  * List backup objects in S3 with the configured prefix.
@@ -6770,10 +7586,10 @@ var BackupService = class {
6770
7586
  if (!entry) {
6771
7587
  throw new Error(`Backup "${backupId}" not found.`);
6772
7588
  }
6773
- if (!fs27.existsSync(entry.path)) {
7589
+ if (!fs28.existsSync(entry.path)) {
6774
7590
  throw new Error(`Backup file not found at: ${entry.path}`);
6775
7591
  }
6776
- const encrypted = fs27.readFileSync(entry.path);
7592
+ const encrypted = fs28.readFileSync(entry.path);
6777
7593
  let plaintext;
6778
7594
  try {
6779
7595
  plaintext = decryptBuffer(encrypted, this.key);
@@ -6787,8 +7603,8 @@ var BackupService = class {
6787
7603
  offset += manifestLen;
6788
7604
  const manifest = JSON.parse(manifestStr);
6789
7605
  const restoreBase = targetRepoPath ?? entry.repoPath;
6790
- const codeIntelDir = path29.join(restoreBase, ".code-intel");
6791
- fs27.mkdirSync(codeIntelDir, { recursive: true });
7606
+ const codeIntelDir = path30.join(restoreBase, ".code-intel");
7607
+ fs28.mkdirSync(codeIntelDir, { recursive: true });
6792
7608
  for (const fileEntry of manifest.files) {
6793
7609
  const nameLen = plaintext.readUInt16BE(offset);
6794
7610
  offset += 2;
@@ -6805,18 +7621,18 @@ var BackupService = class {
6805
7621
  }
6806
7622
  let destPath;
6807
7623
  if (name === "registry.json" || name === "users.db") {
6808
- destPath = path29.join(os12.homedir(), ".code-intel", name);
7624
+ destPath = path30.join(os12.homedir(), ".code-intel", name);
6809
7625
  } else {
6810
- destPath = path29.join(codeIntelDir, name);
7626
+ destPath = path30.join(codeIntelDir, name);
6811
7627
  }
6812
- fs27.writeFileSync(destPath, data);
7628
+ fs28.writeFileSync(destPath, data);
6813
7629
  }
6814
7630
  }
6815
7631
  /**
6816
7632
  * Apply retention policy: keep N daily, M weekly, L monthly backups.
6817
7633
  */
6818
7634
  applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
6819
- const entries = this._loadIndex().filter((e) => fs27.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
7635
+ const entries = this._loadIndex().filter((e) => fs28.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
6820
7636
  const keep = /* @__PURE__ */ new Set();
6821
7637
  const now = /* @__PURE__ */ new Date();
6822
7638
  const dailyCutoff = new Date(now);
@@ -6846,7 +7662,7 @@ var BackupService = class {
6846
7662
  for (const e of entries) {
6847
7663
  if (!keep.has(e.id)) {
6848
7664
  try {
6849
- fs27.unlinkSync(e.path);
7665
+ fs28.unlinkSync(e.path);
6850
7666
  deleted++;
6851
7667
  } catch {
6852
7668
  }
@@ -6858,17 +7674,17 @@ var BackupService = class {
6858
7674
  }
6859
7675
  // ── Index helpers ──────────────────────────────────────────────────────────
6860
7676
  _indexPath() {
6861
- return path29.join(this.backupDir, "index.json");
7677
+ return path30.join(this.backupDir, "index.json");
6862
7678
  }
6863
7679
  _loadIndex() {
6864
7680
  try {
6865
- return JSON.parse(fs27.readFileSync(this._indexPath(), "utf-8"));
7681
+ return JSON.parse(fs28.readFileSync(this._indexPath(), "utf-8"));
6866
7682
  } catch {
6867
7683
  return [];
6868
7684
  }
6869
7685
  }
6870
7686
  _saveIndex(entries) {
6871
- fs27.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
7687
+ fs28.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
6872
7688
  }
6873
7689
  _appendIndex(entry) {
6874
7690
  const entries = this._loadIndex();
@@ -7207,6 +8023,60 @@ var openApiSpec = {
7207
8023
  }
7208
8024
  }
7209
8025
  },
8026
+ "/source": {
8027
+ get: {
8028
+ tags: ["Files"],
8029
+ summary: "Get source code preview with context around specified lines",
8030
+ description: "Returns the file content around the specified line range (\xB120 lines context), with language detection. Requires viewer role.",
8031
+ security: [{ BearerAuth: [] }, { SessionCookie: [] }],
8032
+ parameters: [
8033
+ {
8034
+ name: "file",
8035
+ in: "query",
8036
+ required: true,
8037
+ description: "Absolute path to the file",
8038
+ schema: { type: "string" }
8039
+ },
8040
+ {
8041
+ name: "startLine",
8042
+ in: "query",
8043
+ required: false,
8044
+ description: "Start line number (1-indexed)",
8045
+ schema: { type: "integer", minimum: 1 }
8046
+ },
8047
+ {
8048
+ name: "endLine",
8049
+ in: "query",
8050
+ required: false,
8051
+ description: "End line number (1-indexed)",
8052
+ schema: { type: "integer", minimum: 1 }
8053
+ }
8054
+ ],
8055
+ responses: {
8056
+ "200": {
8057
+ description: "Source code preview",
8058
+ content: {
8059
+ "application/json": {
8060
+ schema: {
8061
+ type: "object",
8062
+ properties: {
8063
+ content: { type: "string", description: "File content (with context lines)" },
8064
+ language: { type: "string", description: "Detected programming language", example: "typescript" },
8065
+ startLine: { type: "integer", description: "Actual start line returned (with context)" },
8066
+ endLine: { type: "integer", description: "Actual end line returned (with context)" }
8067
+ },
8068
+ required: ["content", "language", "startLine", "endLine"]
8069
+ }
8070
+ }
8071
+ }
8072
+ },
8073
+ "400": { description: "Bad request (missing file param or path traversal detected)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8074
+ "401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8075
+ "403": { description: "Forbidden (file outside indexed repos)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8076
+ "404": { description: "File not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
8077
+ }
8078
+ }
8079
+ },
7210
8080
  "/grep": {
7211
8081
  post: {
7212
8082
  tags: ["Files"],
@@ -7320,16 +8190,122 @@ var openApiSpec = {
7320
8190
  "404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
7321
8191
  }
7322
8192
  }
8193
+ },
8194
+ "/query": {
8195
+ post: {
8196
+ tags: ["GQL"],
8197
+ summary: "Execute a GQL (Graph Query Language) query against the knowledge graph",
8198
+ description: "Supports FIND, TRAVERSE, PATH, and COUNT statements. Requires viewer role minimum.",
8199
+ security: [{ BearerAuth: [] }, { SessionCookie: [] }],
8200
+ requestBody: {
8201
+ required: true,
8202
+ content: {
8203
+ "application/json": {
8204
+ schema: {
8205
+ type: "object",
8206
+ properties: {
8207
+ gql: {
8208
+ type: "string",
8209
+ description: "GQL query string",
8210
+ example: 'FIND function WHERE name CONTAINS "auth"'
8211
+ },
8212
+ format: {
8213
+ type: "string",
8214
+ enum: ["json", "table", "csv"],
8215
+ default: "json",
8216
+ description: "Output format"
8217
+ }
8218
+ },
8219
+ required: ["gql"]
8220
+ }
8221
+ }
8222
+ }
8223
+ },
8224
+ responses: {
8225
+ "200": {
8226
+ description: "GQL execution result",
8227
+ content: {
8228
+ "application/json": {
8229
+ schema: {
8230
+ type: "object",
8231
+ properties: {
8232
+ nodes: { type: "array", items: { "$ref": "#/components/schemas/CodeNode" } },
8233
+ edges: { type: "array", items: { type: "object" } },
8234
+ groups: { type: "array", items: { type: "object", properties: { key: { type: "string" }, count: { type: "integer" } } } },
8235
+ path: { type: "array", items: { "$ref": "#/components/schemas/CodeNode" }, nullable: true },
8236
+ executionTimeMs: { type: "number" },
8237
+ truncated: { type: "boolean" },
8238
+ totalCount: { type: "integer" }
8239
+ }
8240
+ }
8241
+ }
8242
+ }
8243
+ },
8244
+ "400": { description: "Missing gql field", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8245
+ "401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8246
+ "403": { description: "Forbidden (insufficient role)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8247
+ "422": { description: "GQL parse error", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
8248
+ }
8249
+ }
8250
+ },
8251
+ "/query/explain": {
8252
+ post: {
8253
+ tags: ["GQL"],
8254
+ summary: "Explain a GQL query \u2014 returns the execution plan without running it",
8255
+ description: "Returns a query plan object describing the steps that would be executed. Requires viewer role minimum.",
8256
+ security: [{ BearerAuth: [] }, { SessionCookie: [] }],
8257
+ requestBody: {
8258
+ required: true,
8259
+ content: {
8260
+ "application/json": {
8261
+ schema: {
8262
+ type: "object",
8263
+ properties: {
8264
+ gql: { type: "string", description: "GQL query string", example: 'FIND function WHERE name CONTAINS "auth"' }
8265
+ },
8266
+ required: ["gql"]
8267
+ }
8268
+ }
8269
+ }
8270
+ },
8271
+ responses: {
8272
+ "200": {
8273
+ description: "Query plan",
8274
+ content: {
8275
+ "application/json": {
8276
+ schema: {
8277
+ type: "object",
8278
+ properties: {
8279
+ plan: {
8280
+ type: "object",
8281
+ properties: {
8282
+ type: { type: "string", enum: ["FIND", "TRAVERSE", "PATH", "COUNT"] },
8283
+ gql: { type: "string" },
8284
+ steps: { type: "array", items: { type: "object" } },
8285
+ estimatedCost: { type: "number" }
8286
+ }
8287
+ },
8288
+ graphSize: { type: "object", properties: { nodes: { type: "integer" }, edges: { type: "integer" } } }
8289
+ }
8290
+ }
8291
+ }
8292
+ }
8293
+ },
8294
+ "400": { description: "Missing gql field", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8295
+ "401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8296
+ "422": { description: "GQL parse error", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
8297
+ }
8298
+ }
7323
8299
  }
7324
8300
  }
7325
8301
  };
7326
8302
 
7327
8303
  // src/http/app.ts
7328
- var __dirname$1 = path29.dirname(fileURLToPath(import.meta.url));
8304
+ var __dirname$1 = path30.dirname(fileURLToPath(import.meta.url));
7329
8305
  var WEB_DIST = (() => {
7330
- const bundled = path29.resolve(__dirname$1, "..", "web");
7331
- if (fs27.existsSync(bundled)) return bundled;
7332
- return path29.resolve(__dirname$1, "..", "..", "..", "web", "dist");
8306
+ const bundled = path30.resolve(__dirname$1, "..", "web");
8307
+ if (fs28.existsSync(bundled)) return bundled;
8308
+ return path30.resolve(__dirname$1, "..", "..", "..", "web", "dist");
7333
8309
  })();
7334
8310
  function getAllowedOrigins() {
7335
8311
  const env = process.env["CODE_INTEL_CORS_ORIGINS"];
@@ -7357,6 +8333,7 @@ function createDefaultLimiter() {
7357
8333
  function createApp(graph, repoName, workspaceRoot, watcherState) {
7358
8334
  const app = express();
7359
8335
  app.set("trust proxy", 1);
8336
+ app.use(compression());
7360
8337
  app.use(
7361
8338
  helmet({
7362
8339
  contentSecurityPolicy: false
@@ -7859,8 +8836,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
7859
8836
  const registry = loadRegistry();
7860
8837
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
7861
8838
  if (!entry) return null;
7862
- const dbPath = path29.join(entry.path, ".code-intel", "graph.db");
7863
- if (!fs27.existsSync(dbPath)) return null;
8839
+ const dbPath = path30.join(entry.path, ".code-intel", "graph.db");
8840
+ if (!fs28.existsSync(dbPath)) return null;
7864
8841
  const repoGraph = createKnowledgeGraph();
7865
8842
  const db = new DbManager(dbPath);
7866
8843
  try {
@@ -7947,7 +8924,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
7947
8924
  return;
7948
8925
  }
7949
8926
  try {
7950
- const content = fs27.readFileSync(file_path, "utf-8");
8927
+ const content = fs28.readFileSync(file_path, "utf-8");
7951
8928
  res.json({ content });
7952
8929
  } catch {
7953
8930
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
@@ -8205,8 +9182,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
8205
9182
  for (const member of group.members) {
8206
9183
  const regEntry = registry.find((r) => r.name === member.registryName);
8207
9184
  if (!regEntry) continue;
8208
- const dbPath = path29.join(regEntry.path, ".code-intel", "graph.db");
8209
- if (!fs27.existsSync(dbPath)) continue;
9185
+ const dbPath = path30.join(regEntry.path, ".code-intel", "graph.db");
9186
+ if (!fs28.existsSync(dbPath)) continue;
8210
9187
  const db = new DbManager(dbPath);
8211
9188
  try {
8212
9189
  await db.init();
@@ -8218,10 +9195,245 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
8218
9195
  }
8219
9196
  res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
8220
9197
  });
8221
- if (fs27.existsSync(WEB_DIST)) {
9198
+ app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
9199
+ const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
9200
+ if (!file) {
9201
+ res.status(400).json({
9202
+ error: {
9203
+ code: ErrorCodes.INVALID_REQUEST,
9204
+ message: "Missing required query parameter: file",
9205
+ requestId: req.requestId,
9206
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9207
+ }
9208
+ });
9209
+ return;
9210
+ }
9211
+ if (file.includes("../")) {
9212
+ res.status(400).json({
9213
+ error: {
9214
+ code: ErrorCodes.INVALID_REQUEST,
9215
+ message: "Path traversal detected",
9216
+ hint: 'File paths must not contain "../"',
9217
+ requestId: req.requestId,
9218
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9219
+ }
9220
+ });
9221
+ return;
9222
+ }
9223
+ let rawResolved = path30.normalize(file);
9224
+ if (!path30.isAbsolute(rawResolved) && workspaceRoot) {
9225
+ rawResolved = path30.join(workspaceRoot, rawResolved);
9226
+ }
9227
+ const resolvedFile = path30.resolve(rawResolved);
9228
+ function isInsideDir(fileAbs, dir) {
9229
+ const rel = path30.relative(path30.resolve(dir), fileAbs);
9230
+ return !rel.startsWith("..") && !path30.isAbsolute(rel);
9231
+ }
9232
+ if (workspaceRoot) {
9233
+ if (!isInsideDir(resolvedFile, workspaceRoot)) {
9234
+ const registry = loadRegistry();
9235
+ const inKnownRepo = registry.some((r) => isInsideDir(resolvedFile, r.path));
9236
+ if (!inKnownRepo) {
9237
+ res.status(403).json({
9238
+ error: {
9239
+ code: ErrorCodes.FORBIDDEN,
9240
+ message: "Access denied",
9241
+ hint: "File path must be within an indexed repository",
9242
+ requestId: req.requestId,
9243
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9244
+ }
9245
+ });
9246
+ return;
9247
+ }
9248
+ }
9249
+ } else {
9250
+ const registry = loadRegistry();
9251
+ const inKnownRepo = registry.some((r) => isInsideDir(resolvedFile, r.path));
9252
+ if (!inKnownRepo) {
9253
+ res.status(403).json({
9254
+ error: {
9255
+ code: ErrorCodes.FORBIDDEN,
9256
+ message: "Access denied",
9257
+ hint: "File path must be within an indexed repository",
9258
+ requestId: req.requestId,
9259
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9260
+ }
9261
+ });
9262
+ return;
9263
+ }
9264
+ }
9265
+ let fileContent;
9266
+ try {
9267
+ fileContent = fs28.readFileSync(resolvedFile, "utf-8");
9268
+ } catch {
9269
+ res.status(404).json({
9270
+ error: {
9271
+ code: ErrorCodes.NOT_FOUND,
9272
+ message: "File not found",
9273
+ requestId: req.requestId,
9274
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9275
+ }
9276
+ });
9277
+ return;
9278
+ }
9279
+ const lines = fileContent.split("\n");
9280
+ const parsedStart = startLineStr ? Number.parseInt(startLineStr, 10) : 1;
9281
+ const parsedEnd = endLineStr ? Number.parseInt(endLineStr, 10) : parsedStart;
9282
+ if (!Number.isFinite(parsedStart) || parsedStart < 1 || !Number.isFinite(parsedEnd) || parsedEnd < 1) {
9283
+ res.status(400).json({
9284
+ error: {
9285
+ code: ErrorCodes.INVALID_REQUEST,
9286
+ message: "Invalid startLine or endLine: must be positive integers",
9287
+ requestId: req.requestId,
9288
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9289
+ }
9290
+ });
9291
+ return;
9292
+ }
9293
+ const startLine = Math.max(1, parsedStart);
9294
+ const endLine = Math.min(lines.length, parsedEnd);
9295
+ const contextStart = Math.max(1, startLine - 20);
9296
+ const contextEnd = Math.min(lines.length, endLine + 20);
9297
+ const content = lines.slice(contextStart - 1, contextEnd).join("\n");
9298
+ const ext = path30.extname(resolvedFile).toLowerCase();
9299
+ const languageMap = {
9300
+ ".ts": "typescript",
9301
+ ".tsx": "typescript",
9302
+ ".js": "javascript",
9303
+ ".jsx": "javascript",
9304
+ ".mjs": "javascript",
9305
+ ".cjs": "javascript",
9306
+ ".py": "python",
9307
+ ".go": "go",
9308
+ ".rs": "rust",
9309
+ ".java": "java",
9310
+ ".cs": "csharp",
9311
+ ".cpp": "cpp",
9312
+ ".cc": "cpp",
9313
+ ".cxx": "cpp",
9314
+ ".c": "c",
9315
+ ".h": "c",
9316
+ ".hpp": "cpp",
9317
+ ".rb": "ruby",
9318
+ ".php": "php",
9319
+ ".swift": "swift",
9320
+ ".kt": "kotlin",
9321
+ ".kts": "kotlin",
9322
+ ".json": "json",
9323
+ ".yaml": "yaml",
9324
+ ".yml": "yaml",
9325
+ ".md": "markdown",
9326
+ ".sh": "bash",
9327
+ ".bash": "bash",
9328
+ ".zsh": "bash",
9329
+ ".sql": "sql",
9330
+ ".html": "html",
9331
+ ".htm": "html",
9332
+ ".css": "css",
9333
+ ".scss": "scss",
9334
+ ".less": "less",
9335
+ ".xml": "xml",
9336
+ ".toml": "toml"
9337
+ };
9338
+ const language = languageMap[ext] ?? "plaintext";
9339
+ res.json({
9340
+ content,
9341
+ language,
9342
+ startLine: contextStart,
9343
+ endLine: contextEnd
9344
+ });
9345
+ });
9346
+ app.post("/api/v1/query", requireRole("viewer"), async (req, res) => {
9347
+ const { gql, format } = req.body;
9348
+ if (!gql || typeof gql !== "string") {
9349
+ res.status(400).json({
9350
+ error: { code: ErrorCodes.INVALID_REQUEST, message: "Missing required field: gql", requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
9351
+ });
9352
+ return;
9353
+ }
9354
+ try {
9355
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
9356
+ const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
9357
+ const ast = parseGQL2(gql);
9358
+ if (isGQLParseError2(ast)) {
9359
+ res.status(422).json({
9360
+ error: {
9361
+ code: ErrorCodes.INVALID_REQUEST,
9362
+ message: `GQL parse error: ${ast.message}`,
9363
+ hint: `Position: ${ast.pos}${ast.expected ? `, expected: ${ast.expected}` : ""}${ast.got ? `, got: ${ast.got}` : ""}`,
9364
+ requestId: req.requestId,
9365
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9366
+ }
9367
+ });
9368
+ return;
9369
+ }
9370
+ const result = executeGQL2(ast, graph);
9371
+ const statusCode = result.truncated ? 408 : 200;
9372
+ res.status(statusCode).json({ ...result, format: format ?? "json" });
9373
+ } catch (err) {
9374
+ 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() } });
9375
+ }
9376
+ });
9377
+ app.post("/api/v1/query/explain", requireRole("viewer"), async (req, res) => {
9378
+ const { gql } = req.body;
9379
+ if (!gql || typeof gql !== "string") {
9380
+ res.status(400).json({
9381
+ error: { code: ErrorCodes.INVALID_REQUEST, message: "Missing required field: gql", requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
9382
+ });
9383
+ return;
9384
+ }
9385
+ try {
9386
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
9387
+ const ast = parseGQL2(gql);
9388
+ if (isGQLParseError2(ast)) {
9389
+ res.status(422).json({
9390
+ error: {
9391
+ code: ErrorCodes.INVALID_REQUEST,
9392
+ message: `GQL parse error: ${ast.message}`,
9393
+ hint: `Position: ${ast.pos}`,
9394
+ requestId: req.requestId,
9395
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9396
+ }
9397
+ });
9398
+ return;
9399
+ }
9400
+ const plan = { type: ast.type, gql };
9401
+ if (ast.type === "FIND") {
9402
+ plan.steps = [
9403
+ { step: 1, op: "SCAN_NODES", filter: ast.target === "*" ? "all" : `kind=${ast.target}` },
9404
+ ...ast.where ? [{ step: 2, op: "WHERE", conditions: ast.where.exprs.length }] : [],
9405
+ ...ast.limit !== void 0 ? [{ step: 3, op: "LIMIT", value: ast.limit }] : []
9406
+ ];
9407
+ plan.estimatedCost = graph.size.nodes;
9408
+ } else if (ast.type === "TRAVERSE") {
9409
+ plan.steps = [
9410
+ { step: 1, op: "FIND_START_NODE", name: ast.from },
9411
+ { step: 2, op: "BFS", edgeKind: ast.edgeKind, maxDepth: ast.depth ?? 5 }
9412
+ ];
9413
+ plan.estimatedCost = Math.min(graph.size.nodes, Math.pow(4, ast.depth ?? 5));
9414
+ } else if (ast.type === "PATH") {
9415
+ plan.steps = [
9416
+ { step: 1, op: "FIND_NODES", from: ast.from, to: ast.to },
9417
+ { step: 2, op: "BFS_SHORTEST_PATH" }
9418
+ ];
9419
+ plan.estimatedCost = graph.size.nodes + graph.size.edges;
9420
+ } else if (ast.type === "COUNT") {
9421
+ plan.steps = [
9422
+ { step: 1, op: "SCAN_NODES", filter: ast.target === "*" ? "all" : `kind=${ast.target}` },
9423
+ ...ast.where ? [{ step: 2, op: "WHERE", conditions: ast.where.exprs.length }] : [],
9424
+ ...ast.groupBy ? [{ step: 3, op: "GROUP_BY", property: ast.groupBy }] : [{ step: 3, op: "COUNT" }]
9425
+ ];
9426
+ plan.estimatedCost = graph.size.nodes;
9427
+ }
9428
+ res.json({ plan, graphSize: graph.size });
9429
+ } catch (err) {
9430
+ res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err), requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() } });
9431
+ }
9432
+ });
9433
+ if (fs28.existsSync(WEB_DIST)) {
8222
9434
  app.use(express.static(WEB_DIST));
8223
9435
  app.get("/{*path}", (_req, res) => {
8224
- res.sendFile(path29.join(WEB_DIST, "index.html"));
9436
+ res.sendFile(path30.join(WEB_DIST, "index.html"));
8225
9437
  });
8226
9438
  }
8227
9439
  app.use("/admin", requireRole("admin"));
@@ -8531,6 +9743,23 @@ function createMcpServer(graph, repoName, workspaceRoot) {
8531
9743
  }
8532
9744
  }
8533
9745
  },
9746
+ // ── query (GQL) ────────────────────────────────────────────────────────
9747
+ {
9748
+ name: "query",
9749
+ description: "Execute a GQL (Graph Query Language) query. Supports FIND, TRAVERSE, PATH, and COUNT. More expressive than raw_query.",
9750
+ inputSchema: {
9751
+ type: "object",
9752
+ properties: {
9753
+ gql: {
9754
+ type: "string",
9755
+ description: 'GQL query string. Examples: "FIND function WHERE name CONTAINS \\"auth\\"", "TRAVERSE CALLS FROM \\"handleLogin\\" DEPTH 3", "PATH FROM \\"createUser\\" TO \\"sendEmail\\"", "COUNT function GROUP BY cluster"'
9756
+ },
9757
+ limit: { type: "number", description: "Override LIMIT in the query (optional)" },
9758
+ ..._tokenProp
9759
+ },
9760
+ required: ["gql"]
9761
+ }
9762
+ },
8534
9763
  // ── Raw query ─────────────────────────────────────────────────────────
8535
9764
  {
8536
9765
  name: "raw_query",
@@ -8711,7 +9940,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
8711
9940
  const limit = a.limit ?? 20;
8712
9941
  const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
8713
9942
  const { results, searchMode } = await hybridSearch(graph, query, limit, { vectorDbPath: vdbPath });
8714
- return { content: [{ type: "text", text: JSON.stringify({ results, searchMode }, null, 2) }] };
9943
+ return { content: [{ type: "text", text: JSON.stringify({ results, searchMode, suggested_next_tools: ["inspect", "query", "blast_radius"] }, null, 2) }] };
8715
9944
  }
8716
9945
  // ── inspect ────────────────────────────────────────────────────────────
8717
9946
  case "inspect": {
@@ -8950,7 +10179,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
8950
10179
  for (const { filePath: changedFile, changedLines } of changedFiles) {
8951
10180
  for (const node of graph.allNodes()) {
8952
10181
  if (!node.filePath) continue;
8953
- const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path29.sep, "");
10182
+ const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path30.sep, "");
8954
10183
  const normChanged = changedFile.replace(/^a\/|^b\//, "");
8955
10184
  if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
8956
10185
  if (node.startLine !== void 0 && node.endLine !== void 0) {
@@ -8999,16 +10228,51 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
8999
10228
  }]
9000
10229
  };
9001
10230
  }
10231
+ // ── query (GQL) ───────────────────────────────────────────────────────────
10232
+ case "query": {
10233
+ const gqlInput = a.gql;
10234
+ if (!gqlInput) {
10235
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Missing required parameter: gql" }) }], isError: true };
10236
+ }
10237
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
10238
+ const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
10239
+ const ast = parseGQL2(gqlInput);
10240
+ if (isGQLParseError2(ast)) {
10241
+ return {
10242
+ content: [{ type: "text", text: JSON.stringify({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
10243
+ isError: true
10244
+ };
10245
+ }
10246
+ if (a.limit !== void 0 && ast.type === "FIND") {
10247
+ ast.limit = a.limit;
10248
+ }
10249
+ const result = executeGQL2(ast, graph);
10250
+ return {
10251
+ content: [{
10252
+ type: "text",
10253
+ text: JSON.stringify({
10254
+ nodes: result.nodes,
10255
+ edges: result.edges,
10256
+ groups: result.groups,
10257
+ path: result.path,
10258
+ executionTimeMs: result.executionTimeMs,
10259
+ truncated: result.truncated,
10260
+ totalCount: result.totalCount
10261
+ }, null, 2)
10262
+ }]
10263
+ };
10264
+ }
9002
10265
  // ── raw_query ──────────────────────────────────────────────────────────
9003
10266
  case "raw_query": {
9004
10267
  const q = a.cypher;
10268
+ const deprecationWarning = "raw_query is deprecated, use query instead";
9005
10269
  const nameMatch = q?.match(/name\s*=\s*['"]([^'"]+)['"]/i);
9006
10270
  if (nameMatch) {
9007
10271
  const results = [];
9008
10272
  for (const node of graph.allNodes()) {
9009
10273
  if (node.name === nameMatch[1]) results.push(node);
9010
10274
  }
9011
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
10275
+ return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
9012
10276
  }
9013
10277
  const kindMatch = q?.match(/:\s*(\w+)/);
9014
10278
  if (kindMatch) {
@@ -9017,9 +10281,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
9017
10281
  if (node.kind === kindMatch[1]) results.push(node);
9018
10282
  if (results.length >= 50) break;
9019
10283
  }
9020
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
10284
+ return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
9021
10285
  }
9022
- return { content: [{ type: "text", text: "Query not recognized. Use name='X' or :kind syntax." }] };
10286
+ return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, error: "Query not recognized. Use name='X' or :kind syntax. Or use the query tool with GQL instead." }) }] };
9023
10287
  }
9024
10288
  // ── group_list ─────────────────────────────────────────────────────────
9025
10289
  case "group_list": {
@@ -9223,20 +10487,20 @@ function parseDiff(diffText) {
9223
10487
  // src/cli/main.ts
9224
10488
  init_metadata();
9225
10489
  async function writeSkillFiles(graph, workspaceRoot, projectName) {
9226
- const outputDir = path29.join(workspaceRoot, ".claude", "skills", "code-intel");
10490
+ const outputDir = path30.join(workspaceRoot, ".claude", "skills", "code-intel");
9227
10491
  const areas = buildAreaMap(graph, workspaceRoot);
9228
10492
  if (areas.length === 0) return { skills: [], outputDir };
9229
- fs27.rmSync(outputDir, { recursive: true, force: true });
9230
- fs27.mkdirSync(outputDir, { recursive: true });
10493
+ fs28.rmSync(outputDir, { recursive: true, force: true });
10494
+ fs28.mkdirSync(outputDir, { recursive: true });
9231
10495
  const skills = [];
9232
10496
  const usedNames = /* @__PURE__ */ new Set();
9233
10497
  for (const area of areas) {
9234
10498
  const kebab = uniqueKebab(area.label, usedNames);
9235
10499
  usedNames.add(kebab);
9236
10500
  const content = renderSkill(area, projectName, kebab);
9237
- const dir = path29.join(outputDir, kebab);
9238
- fs27.mkdirSync(dir, { recursive: true });
9239
- fs27.writeFileSync(path29.join(dir, "SKILL.md"), content, "utf-8");
10501
+ const dir = path30.join(outputDir, kebab);
10502
+ fs28.mkdirSync(dir, { recursive: true });
10503
+ fs28.writeFileSync(path30.join(dir, "SKILL.md"), content, "utf-8");
9240
10504
  skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
9241
10505
  }
9242
10506
  return { skills, outputDir };
@@ -9416,8 +10680,8 @@ var BLOCK_START = "<!-- code-intel:start -->";
9416
10680
  var BLOCK_END = "<!-- code-intel:end -->";
9417
10681
  function writeContextFiles(workspaceRoot, projectName, stats, skills) {
9418
10682
  const block = buildBlock(projectName, stats, skills);
9419
- upsertFile(path29.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
9420
- upsertFile(path29.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
10683
+ upsertFile(path30.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
10684
+ upsertFile(path30.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
9421
10685
  }
9422
10686
  function buildBlock(projectName, stats, skills) {
9423
10687
  const skillRows = skills.map(
@@ -9471,7 +10735,7 @@ ${skillTable}
9471
10735
  ${BLOCK_END}`;
9472
10736
  }
9473
10737
  function upsertFile(filePath, block, fileName) {
9474
- if (!fs27.existsSync(filePath)) {
10738
+ if (!fs28.existsSync(filePath)) {
9475
10739
  const newContent = [
9476
10740
  `# ${fileName}`,
9477
10741
  "",
@@ -9482,17 +10746,17 @@ function upsertFile(filePath, block, fileName) {
9482
10746
  "<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
9483
10747
  ""
9484
10748
  ].join("\n");
9485
- fs27.writeFileSync(filePath, newContent, "utf-8");
10749
+ fs28.writeFileSync(filePath, newContent, "utf-8");
9486
10750
  return;
9487
10751
  }
9488
- const existing = fs27.readFileSync(filePath, "utf-8");
10752
+ const existing = fs28.readFileSync(filePath, "utf-8");
9489
10753
  const startIdx = findLineMarker(existing, BLOCK_START);
9490
10754
  const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
9491
10755
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
9492
10756
  const before = existing.slice(0, startIdx);
9493
10757
  const after = existing.slice(endIdx + BLOCK_END.length);
9494
10758
  const updated = (before + block + after).trimEnd() + "\n";
9495
- fs27.writeFileSync(filePath, updated, "utf-8");
10759
+ fs28.writeFileSync(filePath, updated, "utf-8");
9496
10760
  return;
9497
10761
  }
9498
10762
  const appended = [
@@ -9505,7 +10769,7 @@ function upsertFile(filePath, block, fileName) {
9505
10769
  block,
9506
10770
  ""
9507
10771
  ].join("\n");
9508
- fs27.writeFileSync(filePath, appended, "utf-8");
10772
+ fs28.writeFileSync(filePath, appended, "utf-8");
9509
10773
  }
9510
10774
  function findLineMarker(content, marker, startFrom = 0) {
9511
10775
  let idx = content.indexOf(marker, startFrom);
@@ -9547,14 +10811,14 @@ function getChangedFilesSince(workspaceRoot, baseHash) {
9547
10811
  function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
9548
10812
  const changed = [];
9549
10813
  for (const absPath of allFilePaths) {
9550
- const rel = path29.relative(workspaceRoot, absPath);
10814
+ const rel = path30.relative(workspaceRoot, absPath);
9551
10815
  const stored = storedMtimes[rel];
9552
10816
  if (stored === void 0) {
9553
10817
  changed.push(absPath);
9554
10818
  continue;
9555
10819
  }
9556
10820
  try {
9557
- const { mtimeMs } = fs27.statSync(absPath);
10821
+ const { mtimeMs } = fs28.statSync(absPath);
9558
10822
  if (mtimeMs > stored) changed.push(absPath);
9559
10823
  } catch {
9560
10824
  changed.push(absPath);
@@ -9566,8 +10830,8 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
9566
10830
  const snap = {};
9567
10831
  for (const absPath of filePaths) {
9568
10832
  try {
9569
- const { mtimeMs } = fs27.statSync(absPath);
9570
- snap[path29.relative(workspaceRoot, absPath)] = mtimeMs;
10833
+ const { mtimeMs } = fs28.statSync(absPath);
10834
+ snap[path30.relative(workspaceRoot, absPath)] = mtimeMs;
9571
10835
  } catch {
9572
10836
  }
9573
10837
  }
@@ -9578,8 +10842,8 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
9578
10842
  if (prevCommitHash) {
9579
10843
  const changed = getChangedFilesSince(workspaceRoot, prevCommitHash);
9580
10844
  if (changed !== null) {
9581
- const scanSet = new Set(allFilePaths.map((p) => path29.relative(workspaceRoot, p)));
9582
- const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path29.join(workspaceRoot, rel));
10845
+ const scanSet = new Set(allFilePaths.map((p) => path30.relative(workspaceRoot, p)));
10846
+ const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path30.join(workspaceRoot, rel));
9583
10847
  if (total > 0 && changedInScan.length / total > 0.2) {
9584
10848
  return { incremental: false, fallbackReason: `changed files (${changedInScan.length}) > 20% of total (${total})` };
9585
10849
  }
@@ -9693,17 +10957,17 @@ var MigrationRunner = class {
9693
10957
  autoBackupBeforeMigration() {
9694
10958
  try {
9695
10959
  const dbFile = this.db.name;
9696
- if (!dbFile || !fs27.existsSync(dbFile)) return;
9697
- const backupDir = path29.join(os12.homedir(), ".code-intel", "backups", "pre-migration");
9698
- fs27.mkdirSync(backupDir, { recursive: true });
10960
+ if (!dbFile || !fs28.existsSync(dbFile)) return;
10961
+ const backupDir = path30.join(os12.homedir(), ".code-intel", "backups", "pre-migration");
10962
+ fs28.mkdirSync(backupDir, { recursive: true });
9699
10963
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
9700
- const baseName = path29.basename(dbFile, ".db");
9701
- const backupPath = path29.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
10964
+ const baseName = path30.basename(dbFile, ".db");
10965
+ const backupPath = path30.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
9702
10966
  try {
9703
10967
  this.db.pragma("wal_checkpoint(TRUNCATE)");
9704
10968
  } catch {
9705
10969
  }
9706
- fs27.copyFileSync(dbFile, backupPath);
10970
+ fs28.copyFileSync(dbFile, backupPath);
9707
10971
  } catch {
9708
10972
  }
9709
10973
  }
@@ -9919,7 +11183,7 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
9919
11183
  Docs: https://github.com/vohongtho/code-intel-platform
9920
11184
  `);
9921
11185
  async function analyzeWorkspace(targetPath, options) {
9922
- const workspaceRoot = path29.resolve(targetPath);
11186
+ const workspaceRoot = path30.resolve(targetPath);
9923
11187
  if (!options?.silent) console.log(`Analyzing: ${workspaceRoot}`);
9924
11188
  logger_default.info(`analyze started: ${workspaceRoot}`);
9925
11189
  if (options?.force) {
@@ -9940,14 +11204,14 @@ async function analyzeWorkspace(targetPath, options) {
9940
11204
  ];
9941
11205
  for (const f of wipeFiles) {
9942
11206
  try {
9943
- if (fs27.existsSync(f)) fs27.unlinkSync(f);
11207
+ if (fs28.existsSync(f)) fs28.unlinkSync(f);
9944
11208
  } catch {
9945
11209
  }
9946
11210
  }
9947
11211
  }
9948
11212
  if (!options?.skipGit) {
9949
- const gitDir = path29.join(workspaceRoot, ".git");
9950
- if (!fs27.existsSync(gitDir)) {
11213
+ const gitDir = path30.join(workspaceRoot, ".git");
11214
+ if (!fs28.existsSync(gitDir)) {
9951
11215
  logger_default.warn(`${workspaceRoot} is not a Git repository`);
9952
11216
  }
9953
11217
  }
@@ -10058,17 +11322,17 @@ async function analyzeWorkspace(targetPath, options) {
10058
11322
  const result = await runPipeline(phases, context2);
10059
11323
  if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
10060
11324
  const dbPath = getDbPath(workspaceRoot);
10061
- if (fs27.existsSync(dbPath)) {
11325
+ if (fs28.existsSync(dbPath)) {
10062
11326
  try {
10063
11327
  const db = new DbManager(dbPath);
10064
11328
  await db.init();
10065
11329
  for (const absPath of incrementalChangedFiles) {
10066
- const rel = path29.relative(workspaceRoot, absPath);
11330
+ const rel = path30.relative(workspaceRoot, absPath);
10067
11331
  await removeNodesForFile(rel, db);
10068
11332
  }
10069
11333
  const { upsertNodes: upsertNodesBatch } = await Promise.resolve().then(() => (init_graph_loader(), graph_loader_exports));
10070
11334
  const changedRelPaths = new Set(
10071
- incrementalChangedFiles.map((f) => path29.relative(workspaceRoot, f))
11335
+ incrementalChangedFiles.map((f) => path30.relative(workspaceRoot, f))
10072
11336
  );
10073
11337
  const nodesToUpsert = [...graph.allNodes()].filter(
10074
11338
  (n) => changedRelPaths.has(n.filePath)
@@ -10093,7 +11357,7 @@ async function analyzeWorkspace(targetPath, options) {
10093
11357
  mergedMtimes = newMtimes;
10094
11358
  }
10095
11359
  const currentCommitHash = getCurrentCommitHash(workspaceRoot) ?? void 0;
10096
- const repoName = path29.basename(workspaceRoot);
11360
+ const repoName = path30.basename(workspaceRoot);
10097
11361
  const indexVersion = v4();
10098
11362
  saveMetadata(workspaceRoot, {
10099
11363
  indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10131,7 +11395,7 @@ async function analyzeWorkspace(targetPath, options) {
10131
11395
  ];
10132
11396
  for (const f of newStaleFiles) {
10133
11397
  try {
10134
- if (fs27.existsSync(f)) fs27.unlinkSync(f);
11398
+ if (fs28.existsSync(f)) fs28.unlinkSync(f);
10135
11399
  } catch {
10136
11400
  }
10137
11401
  }
@@ -10148,21 +11412,21 @@ async function analyzeWorkspace(targetPath, options) {
10148
11412
  ];
10149
11413
  for (const f of staleFiles) {
10150
11414
  try {
10151
- if (fs27.existsSync(f)) fs27.unlinkSync(f);
11415
+ if (fs28.existsSync(f)) fs28.unlinkSync(f);
10152
11416
  } catch {
10153
11417
  }
10154
11418
  }
10155
11419
  for (const f of newStaleFiles) {
10156
11420
  if (f === dbPathNew) continue;
10157
- if (fs27.existsSync(f)) {
11421
+ if (fs28.existsSync(f)) {
10158
11422
  const dest = f.replace(dbPathNew, dbPath);
10159
11423
  try {
10160
- fs27.renameSync(f, dest);
11424
+ fs28.renameSync(f, dest);
10161
11425
  } catch {
10162
11426
  }
10163
11427
  }
10164
11428
  }
10165
- fs27.renameSync(dbPathNew, dbPath);
11429
+ fs28.renameSync(dbPathNew, dbPath);
10166
11430
  stopSpinner();
10167
11431
  logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
10168
11432
  if (!options?.silent) {
@@ -10183,7 +11447,7 @@ async function analyzeWorkspace(targetPath, options) {
10183
11447
  const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
10184
11448
  for (const f of staleVdb) {
10185
11449
  try {
10186
- if (fs27.existsSync(f)) fs27.unlinkSync(f);
11450
+ if (fs28.existsSync(f)) fs28.unlinkSync(f);
10187
11451
  } catch {
10188
11452
  }
10189
11453
  }
@@ -10281,8 +11545,8 @@ program.command("setup").description("Configure MCP server for your editors (one
10281
11545
  const configFile = `${configDir}/claude_desktop_config.json`;
10282
11546
  try {
10283
11547
  let existing = {};
10284
- if (fs27.existsSync(configFile)) {
10285
- existing = JSON.parse(fs27.readFileSync(configFile, "utf-8"));
11548
+ if (fs28.existsSync(configFile)) {
11549
+ existing = JSON.parse(fs28.readFileSync(configFile, "utf-8"));
10286
11550
  }
10287
11551
  const merged = {
10288
11552
  ...existing,
@@ -10291,8 +11555,8 @@ program.command("setup").description("Configure MCP server for your editors (one
10291
11555
  ...mcpConfig.mcpServers
10292
11556
  }
10293
11557
  };
10294
- fs27.mkdirSync(configDir, { recursive: true });
10295
- fs27.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
11558
+ fs28.mkdirSync(configDir, { recursive: true });
11559
+ fs28.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
10296
11560
  console.log(`
10297
11561
  \u2705 Written to ${configFile}`);
10298
11562
  } catch (err) {
@@ -10340,8 +11604,14 @@ program.command("analyze").description("Index a repository and build the knowled
10340
11604
  summarize: opts.summarize,
10341
11605
  llmProvider: opts.llmProvider,
10342
11606
  llmModel: opts.llmModel,
10343
- llmBatchSize: opts.llmBatchSize ? parseInt(opts.llmBatchSize, 10) : void 0,
10344
- llmMaxNodes: opts.llmMaxNodes ? parseInt(opts.llmMaxNodes, 10) : void 0
11607
+ llmBatchSize: (() => {
11608
+ const v = parseInt(opts.llmBatchSize ?? "", 10);
11609
+ return Number.isFinite(v) && v >= 1 ? v : void 0;
11610
+ })(),
11611
+ llmMaxNodes: (() => {
11612
+ const v = parseInt(opts.llmMaxNodes ?? "", 10);
11613
+ return Number.isFinite(v) && v >= 1 ? v : void 0;
11614
+ })()
10345
11615
  });
10346
11616
  process.exit(0);
10347
11617
  });
@@ -10356,10 +11626,10 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
10356
11626
  $ code-intel mcp
10357
11627
  $ code-intel mcp ./my-project
10358
11628
  `).action(async (targetPath) => {
10359
- const workspaceRoot = path29.resolve(targetPath);
10360
- const repoName = path29.basename(workspaceRoot);
11629
+ const workspaceRoot = path30.resolve(targetPath);
11630
+ const repoName = path30.basename(workspaceRoot);
10361
11631
  const dbPath = getDbPath(workspaceRoot);
10362
- const existingIndex = fs27.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
11632
+ const existingIndex = fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
10363
11633
  if (existingIndex) {
10364
11634
  const graph = createKnowledgeGraph();
10365
11635
  const db = new DbManager(dbPath);
@@ -10390,10 +11660,10 @@ program.command("serve").description("Start the local HTTP server + web UI for g
10390
11660
  $ code-intel serve --port 8080
10391
11661
  $ code-intel serve --force
10392
11662
  `).action(async (targetPath, options) => {
10393
- const workspaceRoot = path29.resolve(targetPath);
10394
- const repoName = path29.basename(workspaceRoot);
11663
+ const workspaceRoot = path30.resolve(targetPath);
11664
+ const repoName = path30.basename(workspaceRoot);
10395
11665
  const dbPath = getDbPath(workspaceRoot);
10396
- const existingIndex = !options.force && fs27.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
11666
+ const existingIndex = !options.force && fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
10397
11667
  if (existingIndex) {
10398
11668
  const meta = loadMetadata(workspaceRoot);
10399
11669
  if (meta.parser === "regex" || meta.parser === void 0) {
@@ -10427,10 +11697,10 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
10427
11697
  `).action(async (targetPath, options) => {
10428
11698
  const { FileWatcher: FileWatcher2 } = await Promise.resolve().then(() => (init_file_watcher(), file_watcher_exports));
10429
11699
  const { IncrementalIndexer: IncrementalIndexer2 } = await Promise.resolve().then(() => (init_incremental_indexer(), incremental_indexer_exports));
10430
- const workspaceRoot = path29.resolve(targetPath);
10431
- const repoName = path29.basename(workspaceRoot);
11700
+ const workspaceRoot = path30.resolve(targetPath);
11701
+ const repoName = path30.basename(workspaceRoot);
10432
11702
  const dbPath = getDbPath(workspaceRoot);
10433
- const existingIndex = !options.force && fs27.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
11703
+ const existingIndex = !options.force && fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
10434
11704
  let graph;
10435
11705
  if (existingIndex) {
10436
11706
  const meta = loadMetadata(workspaceRoot);
@@ -10465,7 +11735,7 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
10465
11735
  type: "graph:updated",
10466
11736
  indexVersion: meta?.indexVersion ?? "unknown",
10467
11737
  stats: { nodes: graph.size.nodes, edges: graph.size.edges },
10468
- changedFiles: changedFiles.map((f) => path29.relative(workspaceRoot, f)),
11738
+ changedFiles: changedFiles.map((f) => path30.relative(workspaceRoot, f)),
10469
11739
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
10470
11740
  });
10471
11741
  }
@@ -10516,7 +11786,7 @@ program.command("status").description("Show index freshness and statistics for a
10516
11786
  $ code-intel status
10517
11787
  $ code-intel status ./my-project
10518
11788
  `).action((targetPath) => {
10519
- const workspaceRoot = path29.resolve(targetPath);
11789
+ const workspaceRoot = path30.resolve(targetPath);
10520
11790
  const meta = loadMetadata(workspaceRoot);
10521
11791
  if (!meta) {
10522
11792
  console.log(`
@@ -10540,18 +11810,18 @@ function trashDirName(repoPath) {
10540
11810
  return `.code-intel-trash-${date}`;
10541
11811
  }
10542
11812
  function softDeleteCodeIntel(repoPath) {
10543
- const codeIntelDir = path29.join(repoPath, ".code-intel");
10544
- if (!fs27.existsSync(codeIntelDir)) return;
11813
+ const codeIntelDir = path30.join(repoPath, ".code-intel");
11814
+ if (!fs28.existsSync(codeIntelDir)) return;
10545
11815
  const trashName = trashDirName();
10546
- const trashDir = path29.join(repoPath, trashName);
11816
+ const trashDir = path30.join(repoPath, trashName);
10547
11817
  let dest = trashDir;
10548
11818
  let counter = 1;
10549
- while (fs27.existsSync(dest)) {
11819
+ while (fs28.existsSync(dest)) {
10550
11820
  dest = `${trashDir}-${counter++}`;
10551
11821
  }
10552
- fs27.renameSync(codeIntelDir, dest);
10553
- fs27.writeFileSync(
10554
- path29.join(dest, "TRASH_META.json"),
11822
+ fs28.renameSync(codeIntelDir, dest);
11823
+ fs28.writeFileSync(
11824
+ path30.join(dest, "TRASH_META.json"),
10555
11825
  JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
10556
11826
  );
10557
11827
  console.log(` \u2713 Moved to trash: ${dest}`);
@@ -10560,15 +11830,15 @@ function softDeleteCodeIntel(repoPath) {
10560
11830
  function purgeStaleTrashes(repoPath) {
10561
11831
  const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
10562
11832
  try {
10563
- for (const entry of fs27.readdirSync(repoPath)) {
11833
+ for (const entry of fs28.readdirSync(repoPath)) {
10564
11834
  if (!entry.startsWith(".code-intel-trash-")) continue;
10565
- const fullPath = path29.join(repoPath, entry);
10566
- const metaPath = path29.join(fullPath, "TRASH_META.json");
10567
- if (fs27.existsSync(metaPath)) {
11835
+ const fullPath = path30.join(repoPath, entry);
11836
+ const metaPath = path30.join(fullPath, "TRASH_META.json");
11837
+ if (fs28.existsSync(metaPath)) {
10568
11838
  try {
10569
- const meta = JSON.parse(fs27.readFileSync(metaPath, "utf-8"));
11839
+ const meta = JSON.parse(fs28.readFileSync(metaPath, "utf-8"));
10570
11840
  if (new Date(meta.deletedAt).getTime() < cutoff) {
10571
- fs27.rmSync(fullPath, { recursive: true, force: true });
11841
+ fs28.rmSync(fullPath, { recursive: true, force: true });
10572
11842
  console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
10573
11843
  }
10574
11844
  } catch {
@@ -10595,18 +11865,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
10595
11865
  if (opts.listTrash) {
10596
11866
  const repos = loadRegistry();
10597
11867
  const roots = repos.map((r) => r.path);
10598
- if (roots.length === 0) roots.push(path29.resolve("."));
11868
+ if (roots.length === 0) roots.push(path30.resolve("."));
10599
11869
  let found = 0;
10600
11870
  for (const root of roots) {
10601
11871
  try {
10602
- for (const entry of fs27.readdirSync(root)) {
11872
+ for (const entry of fs28.readdirSync(root)) {
10603
11873
  if (!entry.startsWith(".code-intel-trash-")) continue;
10604
- const fullPath = path29.join(root, entry);
10605
- const metaPath = path29.join(fullPath, "TRASH_META.json");
11874
+ const fullPath = path30.join(root, entry);
11875
+ const metaPath = path30.join(fullPath, "TRASH_META.json");
10606
11876
  let deletedAt = "unknown";
10607
- if (fs27.existsSync(metaPath)) {
11877
+ if (fs28.existsSync(metaPath)) {
10608
11878
  try {
10609
- deletedAt = JSON.parse(fs27.readFileSync(metaPath, "utf-8")).deletedAt;
11879
+ deletedAt = JSON.parse(fs28.readFileSync(metaPath, "utf-8")).deletedAt;
10610
11880
  } catch {
10611
11881
  }
10612
11882
  }
@@ -10634,9 +11904,9 @@ program.command("clean").description("Soft-delete the knowledge graph index for
10634
11904
  }
10635
11905
  for (const r of repos) {
10636
11906
  if (opts.purge) {
10637
- const codeIntelDir = path29.join(r.path, ".code-intel");
10638
- if (fs27.existsSync(codeIntelDir)) {
10639
- fs27.rmSync(codeIntelDir, { recursive: true, force: true });
11907
+ const codeIntelDir = path30.join(r.path, ".code-intel");
11908
+ if (fs28.existsSync(codeIntelDir)) {
11909
+ fs28.rmSync(codeIntelDir, { recursive: true, force: true });
10640
11910
  console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
10641
11911
  }
10642
11912
  } else {
@@ -10650,11 +11920,11 @@ program.command("clean").description("Soft-delete the knowledge graph index for
10650
11920
  `);
10651
11921
  return;
10652
11922
  }
10653
- const workspaceRoot = path29.resolve(targetPath);
11923
+ const workspaceRoot = path30.resolve(targetPath);
10654
11924
  if (opts.purge) {
10655
- const codeIntelDir = path29.join(workspaceRoot, ".code-intel");
10656
- if (fs27.existsSync(codeIntelDir)) {
10657
- fs27.rmSync(codeIntelDir, { recursive: true, force: true });
11925
+ const codeIntelDir = path30.join(workspaceRoot, ".code-intel");
11926
+ if (fs28.existsSync(codeIntelDir)) {
11927
+ fs28.rmSync(codeIntelDir, { recursive: true, force: true });
10658
11928
  console.log(`
10659
11929
  \u2713 Hard-deleted ${codeIntelDir}`);
10660
11930
  }
@@ -10666,16 +11936,16 @@ program.command("clean").description("Soft-delete the knowledge graph index for
10666
11936
  console.log(" Index cleaned.\n");
10667
11937
  });
10668
11938
  async function loadOrAnalyzeWorkspace(targetPath) {
10669
- const workspaceRoot = path29.resolve(targetPath);
11939
+ const workspaceRoot = path30.resolve(targetPath);
10670
11940
  const dbPath = getDbPath(workspaceRoot);
10671
- const existingIndex = fs27.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
11941
+ const existingIndex = fs28.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
10672
11942
  if (existingIndex) {
10673
11943
  const graph = createKnowledgeGraph();
10674
11944
  const db = new DbManager(dbPath);
10675
11945
  await db.init();
10676
11946
  await loadGraphFromDB(graph, db);
10677
11947
  db.close();
10678
- return { graph, workspaceRoot, repoName: path29.basename(workspaceRoot) };
11948
+ return { graph, workspaceRoot, repoName: path30.basename(workspaceRoot) };
10679
11949
  }
10680
11950
  return analyzeWorkspace(targetPath, { silent: true });
10681
11951
  }
@@ -11103,9 +12373,9 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
11103
12373
  console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT IN REGISTRY`);
11104
12374
  continue;
11105
12375
  }
11106
- const metaPath = path29.join(regEntry.path, ".code-intel", "meta.json");
12376
+ const metaPath = path30.join(regEntry.path, ".code-intel", "meta.json");
11107
12377
  try {
11108
- const meta = JSON.parse(fs27.readFileSync(metaPath, "utf-8"));
12378
+ const meta = JSON.parse(fs28.readFileSync(metaPath, "utf-8"));
11109
12379
  const indexedAt = meta.indexedAt;
11110
12380
  const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
11111
12381
  const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
@@ -11322,7 +12592,7 @@ backupCmd.command("create [path]").description("Create an encrypted backup of th
11322
12592
  $ code-intel backup create
11323
12593
  $ code-intel backup create ./my-project
11324
12594
  `).action((targetPath = ".") => {
11325
- const repoPath = path29.resolve(targetPath);
12595
+ const repoPath = path30.resolve(targetPath);
11326
12596
  const svc = new BackupService();
11327
12597
  try {
11328
12598
  const entry = svc.createBackup(repoPath);
@@ -11351,7 +12621,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
11351
12621
  Backups (${entries.length}):
11352
12622
  `);
11353
12623
  for (const e of entries) {
11354
- const exists = fs27.existsSync(e.path);
12624
+ const exists = fs28.existsSync(e.path);
11355
12625
  const status = exists ? "\u2713" : "\u2717 (missing)";
11356
12626
  console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
11357
12627
  }
@@ -11364,7 +12634,7 @@ backupCmd.command("restore <id>").description("Restore a backup by ID").option("
11364
12634
  `).action((id, opts) => {
11365
12635
  const svc = new BackupService();
11366
12636
  try {
11367
- const targetPath = opts.target ? path29.resolve(opts.target) : void 0;
12637
+ const targetPath = opts.target ? path30.resolve(opts.target) : void 0;
11368
12638
  svc.restoreBackup(id, targetPath);
11369
12639
  console.log(`
11370
12640
  \u2705 Backup "${id}" restored successfully.
@@ -11383,8 +12653,8 @@ program.command("migrate").description("Manage database schema migrations").opti
11383
12653
  $ code-intel migrate
11384
12654
  $ code-intel migrate --rollback
11385
12655
  `).action((opts) => {
11386
- const dbPath = opts.db ?? path29.join(os12.homedir(), ".code-intel", "users.db");
11387
- if (!fs27.existsSync(dbPath)) {
12656
+ const dbPath = opts.db ?? path30.join(os12.homedir(), ".code-intel", "users.db");
12657
+ if (!fs28.existsSync(dbPath)) {
11388
12658
  console.error(`
11389
12659
  \u2717 Database not found: ${dbPath}
11390
12660
  Run \`code-intel serve\` or \`code-intel user create\` first.
@@ -11499,15 +12769,15 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
11499
12769
  }
11500
12770
  try {
11501
12771
  const tokens = await pollDeviceFlow3(config, deviceResponse);
11502
- const tokenPath = path29.join(os12.homedir(), ".code-intel", "oidc-token.json");
12772
+ const tokenPath = path30.join(os12.homedir(), ".code-intel", "oidc-token.json");
11503
12773
  const tokenData = {
11504
12774
  accessToken: tokens.accessToken,
11505
12775
  refreshToken: tokens.refreshToken,
11506
12776
  server: serverUrl,
11507
12777
  storedAt: (/* @__PURE__ */ new Date()).toISOString()
11508
12778
  };
11509
- fs27.mkdirSync(path29.dirname(tokenPath), { recursive: true });
11510
- fs27.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
12779
+ fs28.mkdirSync(path30.dirname(tokenPath), { recursive: true });
12780
+ fs28.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
11511
12781
  console.log(` \u2705 Authenticated successfully!`);
11512
12782
  console.log(` Token stored at: ${tokenPath}`);
11513
12783
  console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
@@ -11520,13 +12790,13 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
11520
12790
  }
11521
12791
  });
11522
12792
  authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
11523
- const tokenPath = path29.join(os12.homedir(), ".code-intel", "oidc-token.json");
11524
- if (!fs27.existsSync(tokenPath)) {
12793
+ const tokenPath = path30.join(os12.homedir(), ".code-intel", "oidc-token.json");
12794
+ if (!fs28.existsSync(tokenPath)) {
11525
12795
  console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
11526
12796
  return;
11527
12797
  }
11528
12798
  try {
11529
- const data = JSON.parse(fs27.readFileSync(tokenPath, "utf-8"));
12799
+ const data = JSON.parse(fs28.readFileSync(tokenPath, "utf-8"));
11530
12800
  console.log(`
11531
12801
  \u2705 OIDC token stored`);
11532
12802
  console.log(` Server : ${data.server ?? "unknown"}`);
@@ -11538,9 +12808,9 @@ authCmd.command("status").description("Show the current OIDC authentication stat
11538
12808
  }
11539
12809
  });
11540
12810
  authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
11541
- const tokenPath = path29.join(os12.homedir(), ".code-intel", "oidc-token.json");
11542
- if (fs27.existsSync(tokenPath)) {
11543
- fs27.unlinkSync(tokenPath);
12811
+ const tokenPath = path30.join(os12.homedir(), ".code-intel", "oidc-token.json");
12812
+ if (fs28.existsSync(tokenPath)) {
12813
+ fs28.unlinkSync(tokenPath);
11544
12814
  console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
11545
12815
  } else {
11546
12816
  console.log("\n No stored token found.\n");
@@ -11664,8 +12934,8 @@ program.command("config-validate <file>").description("Validate a JSON config fi
11664
12934
  $ code-intel config-validate ./config.json
11665
12935
  $ code-intel config-validate ~/.code-intel/config.json
11666
12936
  `).action((file) => {
11667
- const filePath = path29.resolve(file);
11668
- if (!fs27.existsSync(filePath)) {
12937
+ const filePath = path30.resolve(file);
12938
+ if (!fs28.existsSync(filePath)) {
11669
12939
  console.error(`
11670
12940
  \u2717 File not found: ${filePath}
11671
12941
  `);
@@ -11673,7 +12943,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
11673
12943
  }
11674
12944
  let cfg;
11675
12945
  try {
11676
- cfg = JSON.parse(fs27.readFileSync(filePath, "utf-8"));
12946
+ cfg = JSON.parse(fs28.readFileSync(filePath, "utf-8"));
11677
12947
  } catch (err) {
11678
12948
  console.error(`
11679
12949
  \u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
@@ -11694,7 +12964,7 @@ ${err instanceof Error ? err.message : err}
11694
12964
  });
11695
12965
  (function ensurePermissions() {
11696
12966
  try {
11697
- const dir = path29.join(os12.homedir(), ".code-intel");
12967
+ const dir = path30.join(os12.homedir(), ".code-intel");
11698
12968
  secureMkdir(dir);
11699
12969
  tightenDbFiles(dir);
11700
12970
  } catch {
@@ -11711,10 +12981,10 @@ program.command("health").description("Run code health checks: dead code, circul
11711
12981
  $ code-intel health --json
11712
12982
  $ code-intel health --threshold 80
11713
12983
  `).action(async (targetPath, opts) => {
11714
- const workspaceRoot = path29.resolve(targetPath);
12984
+ const workspaceRoot = path30.resolve(targetPath);
11715
12985
  const dbPath = getDbPath(workspaceRoot);
11716
12986
  const meta = loadMetadata(workspaceRoot);
11717
- if (!meta || !fs27.existsSync(dbPath)) {
12987
+ if (!meta || !fs28.existsSync(dbPath)) {
11718
12988
  console.error(`
11719
12989
  \u2717 ${workspaceRoot} is not indexed.`);
11720
12990
  console.error(" Run `code-intel analyze` first to build the index.\n");
@@ -11778,6 +13048,207 @@ program.command("health").description("Run code health checks: dead code, circul
11778
13048
  }
11779
13049
  }
11780
13050
  });
13051
+ program.command("query").description("Execute a GQL (Graph Query Language) query against the knowledge graph").argument("[gql]", `GQL query string (e.g. "FIND function WHERE name CONTAINS 'auth'")`).option("-f, --file <path>", "Read GQL query from a file").option("--format <format>", "Output format: table|json|csv (default: table)", "table").option("-l, --limit <n>", "Override LIMIT in the query").option("-p, --path <path>", "Path to the repository (default: current directory)", ".").option("--save <name>", "Save the GQL query under a name").option("--run <name>", "Run a saved query by name").option("--list", "List all saved queries").option("--delete <name>", "Delete a saved query by name").addHelpText("after", `
13052
+ Execute GQL queries against the knowledge graph.
13053
+
13054
+ Syntax:
13055
+ FIND function WHERE name CONTAINS "auth"
13056
+ FIND * WHERE kind IN [function, method] LIMIT 50
13057
+ TRAVERSE CALLS FROM "handleLogin" DEPTH 3
13058
+ PATH FROM "createUser" TO "sendEmail"
13059
+ COUNT function GROUP BY cluster
13060
+
13061
+ Examples:
13062
+ $ code-intel query "FIND function WHERE name CONTAINS 'auth'"
13063
+ $ code-intel query --file ./my.gql
13064
+ $ code-intel query "FIND class" --format json
13065
+ $ code-intel query "FIND class" --format csv
13066
+ $ code-intel query "FIND class" --limit 20
13067
+ $ code-intel query --save auth-search "FIND function WHERE name CONTAINS 'auth'"
13068
+ $ code-intel query --run auth-search
13069
+ $ code-intel query --list
13070
+ $ code-intel query --delete auth-search
13071
+ `).action(async (gqlArg, opts) => {
13072
+ const workspaceRoot = path30.resolve(opts.path);
13073
+ const { saveQuery: saveQuery2, loadQuery: loadQuery2, listQueries: listQueries2, deleteQuery: deleteQuery2 } = await Promise.resolve().then(() => (init_saved_queries(), saved_queries_exports));
13074
+ if (opts.list) {
13075
+ const queries = listQueries2(workspaceRoot);
13076
+ if (queries.length === 0) {
13077
+ console.log("\n No saved queries found.");
13078
+ console.log(` Save one with: code-intel query --save <name> "<gql>"
13079
+ `);
13080
+ return;
13081
+ }
13082
+ console.log(`
13083
+ Saved queries (${queries.length}):
13084
+ `);
13085
+ for (const q of queries) {
13086
+ console.log(` \u25C6 ${q.name.padEnd(25)} ${q.content.slice(0, 60)}${q.content.length > 60 ? "\u2026" : ""}`);
13087
+ }
13088
+ console.log("");
13089
+ return;
13090
+ }
13091
+ if (opts.delete) {
13092
+ const { deleteQuery: dq } = await Promise.resolve().then(() => (init_saved_queries(), saved_queries_exports));
13093
+ const deleted = dq(workspaceRoot, opts.delete);
13094
+ if (!deleted) {
13095
+ console.error(`
13096
+ \u2717 Saved query "${opts.delete}" not found.
13097
+ `);
13098
+ process.exit(1);
13099
+ }
13100
+ console.log(`
13101
+ \u2705 Deleted saved query "${opts.delete}"
13102
+ `);
13103
+ return;
13104
+ }
13105
+ if (opts.save) {
13106
+ const gqlContent = gqlArg;
13107
+ if (!gqlContent) {
13108
+ console.error("\n \u2717 Provide a GQL query string to save.");
13109
+ console.error(' Example: code-intel query --save my-query "FIND function"\n');
13110
+ process.exit(1);
13111
+ }
13112
+ saveQuery2(workspaceRoot, opts.save, gqlContent);
13113
+ console.log(`
13114
+ \u2705 Saved query "${opts.save}"`);
13115
+ console.log(` Run with: code-intel query --run ${opts.save}
13116
+ `);
13117
+ return;
13118
+ }
13119
+ let gqlInput;
13120
+ if (opts.run) {
13121
+ const content = loadQuery2(workspaceRoot, opts.run);
13122
+ if (!content) {
13123
+ console.error(`
13124
+ \u2717 Saved query "${opts.run}" not found.`);
13125
+ console.error(` List saved queries with: code-intel query --list
13126
+ `);
13127
+ process.exit(1);
13128
+ }
13129
+ gqlInput = content;
13130
+ } else if (opts.file) {
13131
+ const filePath = path30.resolve(opts.file);
13132
+ if (!fs28.existsSync(filePath)) {
13133
+ console.error(`
13134
+ \u2717 File not found: ${filePath}
13135
+ `);
13136
+ process.exit(1);
13137
+ }
13138
+ gqlInput = fs28.readFileSync(filePath, "utf-8");
13139
+ } else if (gqlArg) {
13140
+ gqlInput = gqlArg;
13141
+ } else {
13142
+ console.error("\n \u2717 Provide a GQL query string, --file <path>, or --run <name>.\n");
13143
+ program.help();
13144
+ process.exit(1);
13145
+ }
13146
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
13147
+ let ast = parseGQL2(gqlInput);
13148
+ if (opts.limit && !isGQLParseError2(ast)) {
13149
+ const limitN = parseInt(opts.limit, 10);
13150
+ if (!isNaN(limitN) && ast.type === "FIND") {
13151
+ ast.limit = limitN;
13152
+ }
13153
+ }
13154
+ if (isGQLParseError2(ast)) {
13155
+ console.error(`
13156
+ \u2717 Parse error: ${ast.message}`);
13157
+ if (ast.pos !== void 0) console.error(` Position: ${ast.pos}`);
13158
+ console.error("");
13159
+ process.exit(1);
13160
+ }
13161
+ const { graph } = await loadOrAnalyzeWorkspace(opts.path);
13162
+ const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
13163
+ const result = executeGQL2(ast, graph);
13164
+ const format = opts.format.toLowerCase();
13165
+ if (result.totalCount === 0 && !result.groups?.length && result.path === null) {
13166
+ console.log(`
13167
+ No results found.
13168
+ `);
13169
+ return;
13170
+ }
13171
+ if (result.path !== null && result.path !== void 0) {
13172
+ if (result.path.length === 0) {
13173
+ console.log("\n No path found.\n");
13174
+ return;
13175
+ }
13176
+ if (format === "json") {
13177
+ console.log(JSON.stringify({ path: result.path, executionTimeMs: result.executionTimeMs }, null, 2));
13178
+ } else if (format === "csv") {
13179
+ console.log("kind,name,filePath");
13180
+ for (const n of result.path) {
13181
+ console.log(`${n.kind},${JSON.stringify(n.name)},${JSON.stringify(n.filePath)}`);
13182
+ }
13183
+ } else {
13184
+ console.log(`
13185
+ Path (${result.path.length} nodes):
13186
+ `);
13187
+ for (let i = 0; i < result.path.length; i++) {
13188
+ const n = result.path[i];
13189
+ const arrow = i < result.path.length - 1 ? " \u2192" : "";
13190
+ console.log(` ${n.kind.padEnd(14)} ${n.name.padEnd(32)} ${n.filePath}${arrow}`);
13191
+ }
13192
+ console.log(`
13193
+ Execution time: ${result.executionTimeMs}ms
13194
+ `);
13195
+ }
13196
+ return;
13197
+ }
13198
+ if (result.groups && !result.nodes?.length) {
13199
+ if (format === "json") {
13200
+ console.log(JSON.stringify({ groups: result.groups, totalCount: result.totalCount, executionTimeMs: result.executionTimeMs }, null, 2));
13201
+ } else if (format === "csv") {
13202
+ console.log("key,count");
13203
+ for (const g of result.groups) {
13204
+ console.log(`${JSON.stringify(g.key)},${g.count}`);
13205
+ }
13206
+ } else {
13207
+ console.log(`
13208
+ Count results (total: ${result.totalCount}):
13209
+ `);
13210
+ for (const g of result.groups) {
13211
+ console.log(` ${g.key.padEnd(30)} ${String(g.count).padStart(6)}`);
13212
+ }
13213
+ console.log(`
13214
+ Execution time: ${result.executionTimeMs}ms${result.truncated ? " \u26A0 (truncated)" : ""}
13215
+ `);
13216
+ }
13217
+ return;
13218
+ }
13219
+ const nodes = result.nodes ?? [];
13220
+ if (nodes.length === 0) {
13221
+ console.log("\n No results found.\n");
13222
+ return;
13223
+ }
13224
+ if (format === "json") {
13225
+ console.log(JSON.stringify({
13226
+ nodes,
13227
+ edges: result.edges,
13228
+ totalCount: result.totalCount,
13229
+ executionTimeMs: result.executionTimeMs,
13230
+ truncated: result.truncated
13231
+ }, null, 2));
13232
+ } else if (format === "csv") {
13233
+ console.log("kind,name,filePath,exported");
13234
+ for (const n of nodes) {
13235
+ console.log(`${n.kind},${JSON.stringify(n.name)},${JSON.stringify(n.filePath)},${n.exported ?? ""}`);
13236
+ }
13237
+ } else {
13238
+ const showing = nodes.length;
13239
+ const total = result.totalCount;
13240
+ const header = result.truncated ? ` (truncated)` : "";
13241
+ console.log(`
13242
+ ${showing} result(s)${total > showing ? ` (of ${total})` : ""}${header}:
13243
+ `);
13244
+ for (const n of nodes) {
13245
+ console.log(` ${n.kind.padEnd(14)} ${n.name.padEnd(32)} ${n.filePath}`);
13246
+ }
13247
+ console.log(`
13248
+ Execution time: ${result.executionTimeMs}ms
13249
+ `);
13250
+ }
13251
+ });
11781
13252
  program.parse();
11782
13253
  //# sourceMappingURL=main.js.map
11783
13254
  //# sourceMappingURL=main.js.map