@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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'module';
2
2
  import { fileURLToPath } from 'url';
3
- import path26 from 'path';
3
+ import path27 from 'path';
4
4
  import fs19, { existsSync } from 'fs';
5
5
  import { Parser, Language, Query } from 'web-tree-sitter';
6
6
  import { NodeSDK } from '@opentelemetry/sdk-node';
@@ -26,6 +26,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSche
26
26
  import { Database, Connection } from '@ladybugdb/core';
27
27
  import { execSync } from 'child_process';
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';
@@ -125,11 +126,11 @@ var init_shared = __esm({
125
126
  }
126
127
  });
127
128
  function findBundledWasmDir() {
128
- const fileDir = path26.dirname(fileURLToPath(import.meta.url));
129
+ const fileDir = path27.dirname(fileURLToPath(import.meta.url));
129
130
  const candidates = [
130
- path26.join(fileDir, "wasm"),
131
+ path27.join(fileDir, "wasm"),
131
132
  // dist/index.js → dist/wasm/
132
- path26.join(fileDir, "../wasm")
133
+ path27.join(fileDir, "../wasm")
133
134
  // dist/cli/main.js → dist/wasm/
134
135
  ];
135
136
  for (const candidate of candidates) {
@@ -170,7 +171,7 @@ function wasmPath(lang) {
170
171
  }
171
172
  const bundled = BUNDLED_WASM_MAP[lang];
172
173
  if (bundled) {
173
- const bundledPath = path26.join(_bundledWasmDir, bundled);
174
+ const bundledPath = path27.join(_bundledWasmDir, bundled);
174
175
  if (existsSync(bundledPath)) return bundledPath;
175
176
  }
176
177
  return null;
@@ -1161,7 +1162,7 @@ var init_logger = __esm({
1161
1162
  };
1162
1163
  }
1163
1164
  /** Global log directory: ~/.code-intel/logs */
1164
- static LOG_DIR = path26.join(os12.homedir(), ".code-intel", "logs");
1165
+ static LOG_DIR = path27.join(os12.homedir(), ".code-intel", "logs");
1165
1166
  static getLogger() {
1166
1167
  if (!_Logger.instance) {
1167
1168
  const isProduction = process.env.NODE_ENV === "production";
@@ -1175,7 +1176,7 @@ var init_logger = __esm({
1175
1176
  }
1176
1177
  transports.push(
1177
1178
  new DailyRotateFile({
1178
- filename: path26.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
1179
+ filename: path27.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
1179
1180
  datePattern: "YYYY-MM-DD",
1180
1181
  maxSize: "20m",
1181
1182
  maxFiles: "14d"
@@ -1973,14 +1974,14 @@ var init_parse_phase = __esm({
1973
1974
  const lang = detectLanguage(filePath);
1974
1975
  if (!lang) {
1975
1976
  if (context2.verbose) {
1976
- const relativePath2 = path26.relative(context2.workspaceRoot, filePath);
1977
+ const relativePath2 = path27.relative(context2.workspaceRoot, filePath);
1977
1978
  logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
1978
1979
  }
1979
1980
  continue;
1980
1981
  }
1981
1982
  const source = context2.fileCache.get(filePath);
1982
1983
  if (!source) continue;
1983
- const relativePath = path26.relative(context2.workspaceRoot, filePath);
1984
+ const relativePath = path27.relative(context2.workspaceRoot, filePath);
1984
1985
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
1985
1986
  const fileNode = context2.graph.getNode(fileNodeId);
1986
1987
  if (fileNode) {
@@ -2220,11 +2221,11 @@ var init_resolve_phase = __esm({
2220
2221
  let heritageEdges = 0;
2221
2222
  const fileIndex = /* @__PURE__ */ new Map();
2222
2223
  for (const fp of filePaths) {
2223
- const rel = path26.relative(workspaceRoot, fp);
2224
+ const rel = path27.relative(workspaceRoot, fp);
2224
2225
  fileIndex.set(rel, fp);
2225
2226
  const noExt = rel.replace(/\.\w+$/, "");
2226
2227
  if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
2227
- const base = path26.basename(rel, path26.extname(rel));
2228
+ const base = path27.basename(rel, path27.extname(rel));
2228
2229
  if (!fileIndex.has(base)) fileIndex.set(base, fp);
2229
2230
  }
2230
2231
  const symbolIndex = /* @__PURE__ */ new Map();
@@ -2255,7 +2256,7 @@ var init_resolve_phase = __esm({
2255
2256
  for (const filePath of filePaths) {
2256
2257
  const lang = detectLanguage(filePath);
2257
2258
  if (!lang) continue;
2258
- const relativePath = path26.relative(workspaceRoot, filePath);
2259
+ const relativePath = path27.relative(workspaceRoot, filePath);
2259
2260
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2260
2261
  const source = fileCache.get(filePath);
2261
2262
  if (!source) continue;
@@ -2268,13 +2269,13 @@ var init_resolve_phase = __esm({
2268
2269
  let resolvedRelPath = null;
2269
2270
  if (cleaned.startsWith(".")) {
2270
2271
  const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
2271
- const fromDir = path26.dirname(relativePath);
2272
+ const fromDir = path27.dirname(relativePath);
2272
2273
  for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
2273
- const candidate = path26.join(fromDir, cleanedNoJs + ext);
2274
- const normalized = path26.normalize(candidate);
2274
+ const candidate = path27.join(fromDir, cleanedNoJs + ext);
2275
+ const normalized = path27.normalize(candidate);
2275
2276
  if (fileIndex.has(normalized)) {
2276
2277
  const absPath = fileIndex.get(normalized);
2277
- resolvedRelPath = path26.relative(workspaceRoot, absPath);
2278
+ resolvedRelPath = path27.relative(workspaceRoot, absPath);
2278
2279
  break;
2279
2280
  }
2280
2281
  }
@@ -2588,7 +2589,7 @@ __export(group_registry_exports, {
2588
2589
  saveSyncResult: () => saveSyncResult
2589
2590
  });
2590
2591
  function groupFile(name) {
2591
- return path26.join(GROUPS_DIR, `${name}.json`);
2592
+ return path27.join(GROUPS_DIR, `${name}.json`);
2592
2593
  }
2593
2594
  function loadGroup(name) {
2594
2595
  try {
@@ -2608,7 +2609,7 @@ function listGroups() {
2608
2609
  if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
2609
2610
  try {
2610
2611
  const g = JSON.parse(
2611
- fs19.readFileSync(path26.join(GROUPS_DIR, file), "utf-8")
2612
+ fs19.readFileSync(path27.join(GROUPS_DIR, file), "utf-8")
2612
2613
  );
2613
2614
  groups.push(g);
2614
2615
  } catch {
@@ -2624,7 +2625,7 @@ function deleteGroup(name) {
2624
2625
  } catch {
2625
2626
  }
2626
2627
  try {
2627
- fs19.unlinkSync(path26.join(GROUPS_DIR, `${name}.sync.json`));
2628
+ fs19.unlinkSync(path27.join(GROUPS_DIR, `${name}.sync.json`));
2628
2629
  } catch {
2629
2630
  }
2630
2631
  }
@@ -2657,14 +2658,14 @@ function removeMember(groupName, groupPath) {
2657
2658
  function saveSyncResult(result) {
2658
2659
  fs19.mkdirSync(GROUPS_DIR, { recursive: true });
2659
2660
  fs19.writeFileSync(
2660
- path26.join(GROUPS_DIR, `${result.groupName}.sync.json`),
2661
+ path27.join(GROUPS_DIR, `${result.groupName}.sync.json`),
2661
2662
  JSON.stringify(result, null, 2) + "\n"
2662
2663
  );
2663
2664
  }
2664
2665
  function loadSyncResult(groupName) {
2665
2666
  try {
2666
2667
  return JSON.parse(
2667
- fs19.readFileSync(path26.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
2668
+ fs19.readFileSync(path27.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
2668
2669
  );
2669
2670
  } catch {
2670
2671
  return null;
@@ -2673,7 +2674,7 @@ function loadSyncResult(groupName) {
2673
2674
  var GROUPS_DIR;
2674
2675
  var init_group_registry = __esm({
2675
2676
  "src/multi-repo/group-registry.ts"() {
2676
- GROUPS_DIR = path26.join(os12.homedir(), ".code-intel", "groups");
2677
+ GROUPS_DIR = path27.join(os12.homedir(), ".code-intel", "groups");
2677
2678
  }
2678
2679
  });
2679
2680
 
@@ -2954,6 +2955,753 @@ var init_health_score = __esm({
2954
2955
  }
2955
2956
  });
2956
2957
 
2958
+ // src/query/gql-parser.ts
2959
+ var gql_parser_exports = {};
2960
+ __export(gql_parser_exports, {
2961
+ isGQLParseError: () => isGQLParseError,
2962
+ parseGQL: () => parseGQL
2963
+ });
2964
+ function isGQLParseError(v) {
2965
+ return v.type === "GQLParseError";
2966
+ }
2967
+ function tokenize(input) {
2968
+ const tokens = [];
2969
+ let i = 0;
2970
+ const len = input.length;
2971
+ while (i < len) {
2972
+ if (/\s/.test(input[i])) {
2973
+ i++;
2974
+ continue;
2975
+ }
2976
+ if (input[i] === "#") {
2977
+ while (i < len && input[i] !== "\n") i++;
2978
+ continue;
2979
+ }
2980
+ const pos = i;
2981
+ if (input[i] === '"' || input[i] === "'") {
2982
+ const quote = input[i];
2983
+ i++;
2984
+ let str = "";
2985
+ while (i < len && input[i] !== quote) {
2986
+ if (input[i] === "\\") {
2987
+ i++;
2988
+ if (i < len) {
2989
+ const esc = input[i];
2990
+ str += esc === "n" ? "\n" : esc === "t" ? " " : esc;
2991
+ i++;
2992
+ }
2993
+ } else {
2994
+ str += input[i++];
2995
+ }
2996
+ }
2997
+ if (i >= len) {
2998
+ return { type: "GQLParseError", message: `Unterminated string at position ${pos}`, pos };
2999
+ }
3000
+ i++;
3001
+ tokens.push({ kind: "STRING", value: str, pos });
3002
+ continue;
3003
+ }
3004
+ if (/[0-9]/.test(input[i])) {
3005
+ let num = "";
3006
+ while (i < len && /[0-9]/.test(input[i])) num += input[i++];
3007
+ tokens.push({ kind: "NUMBER", value: num, pos });
3008
+ continue;
3009
+ }
3010
+ if (input[i] === "[") {
3011
+ tokens.push({ kind: "LBRACKET", value: "[", pos });
3012
+ i++;
3013
+ continue;
3014
+ }
3015
+ if (input[i] === "]") {
3016
+ tokens.push({ kind: "RBRACKET", value: "]", pos });
3017
+ i++;
3018
+ continue;
3019
+ }
3020
+ if (input[i] === "*") {
3021
+ tokens.push({ kind: "STAR", value: "*", pos });
3022
+ i++;
3023
+ continue;
3024
+ }
3025
+ if (input[i] === "!" && input[i + 1] === "=") {
3026
+ tokens.push({ kind: "OPERATOR", value: "!=", pos });
3027
+ i += 2;
3028
+ continue;
3029
+ }
3030
+ if (input[i] === "=") {
3031
+ tokens.push({ kind: "OPERATOR", value: "=", pos });
3032
+ i++;
3033
+ continue;
3034
+ }
3035
+ if (/[a-zA-Z_]/.test(input[i])) {
3036
+ let ident = "";
3037
+ while (i < len && /[a-zA-Z0-9_]/.test(input[i])) ident += input[i++];
3038
+ const upper = ident.toUpperCase();
3039
+ if (upper === "CONTAINS" || upper === "STARTS_WITH" || upper === "IN") {
3040
+ tokens.push({ kind: "OPERATOR", value: upper, pos });
3041
+ } else if (KEYWORDS.has(upper)) {
3042
+ tokens.push({ kind: "KEYWORD", value: upper, pos });
3043
+ } else {
3044
+ tokens.push({ kind: "IDENT", value: ident, pos });
3045
+ }
3046
+ continue;
3047
+ }
3048
+ if (input[i] === ",") {
3049
+ i++;
3050
+ continue;
3051
+ }
3052
+ return {
3053
+ type: "GQLParseError",
3054
+ message: `Unexpected character '${input[i]}' at position ${i}`,
3055
+ pos: i
3056
+ };
3057
+ }
3058
+ tokens.push({ kind: "EOF", value: "", pos: len });
3059
+ return tokens;
3060
+ }
3061
+ function parseGQL(input) {
3062
+ const tokens = tokenize(input.trim());
3063
+ if (!Array.isArray(tokens)) return tokens;
3064
+ const parser = new Parser2(tokens);
3065
+ return parser.parse();
3066
+ }
3067
+ var KEYWORDS, Parser2;
3068
+ var init_gql_parser = __esm({
3069
+ "src/query/gql-parser.ts"() {
3070
+ KEYWORDS = /* @__PURE__ */ new Set([
3071
+ "FIND",
3072
+ "TRAVERSE",
3073
+ "PATH",
3074
+ "COUNT",
3075
+ "WHERE",
3076
+ "FROM",
3077
+ "TO",
3078
+ "IN",
3079
+ "BY",
3080
+ "AND",
3081
+ "NOT",
3082
+ "LIMIT",
3083
+ "OFFSET",
3084
+ "DEPTH",
3085
+ "GROUP",
3086
+ "CONTAINS",
3087
+ "STARTS_WITH",
3088
+ "CALLS",
3089
+ "IMPORTS",
3090
+ "EXTENDS",
3091
+ "IMPLEMENTS",
3092
+ "HAS_MEMBER",
3093
+ "ACCESSES",
3094
+ "OVERRIDES",
3095
+ "BELONGS_TO",
3096
+ "STEP_OF",
3097
+ "HANDLES",
3098
+ "CONTAINS_EDGE",
3099
+ "OUTGOING",
3100
+ "INCOMING",
3101
+ "BOTH"
3102
+ ]);
3103
+ Parser2 = class {
3104
+ tokens;
3105
+ pos = 0;
3106
+ constructor(tokens) {
3107
+ this.tokens = tokens;
3108
+ }
3109
+ peek() {
3110
+ return this.tokens[this.pos];
3111
+ }
3112
+ consume() {
3113
+ return this.tokens[this.pos++];
3114
+ }
3115
+ expect(kind, value) {
3116
+ const tok = this.peek();
3117
+ if (tok.kind !== kind) {
3118
+ return {
3119
+ type: "GQLParseError",
3120
+ message: `Expected ${value ?? kind} but got '${tok.value}' at position ${tok.pos}`,
3121
+ pos: tok.pos,
3122
+ expected: value ?? kind,
3123
+ got: tok.value
3124
+ };
3125
+ }
3126
+ if (value !== void 0 && tok.value !== value) {
3127
+ return {
3128
+ type: "GQLParseError",
3129
+ message: `Expected '${value}' but got '${tok.value}' at position ${tok.pos}`,
3130
+ pos: tok.pos,
3131
+ expected: value,
3132
+ got: tok.value
3133
+ };
3134
+ }
3135
+ return this.consume();
3136
+ }
3137
+ matchKeyword(...values) {
3138
+ const tok = this.peek();
3139
+ return tok.kind === "KEYWORD" && values.includes(tok.value);
3140
+ }
3141
+ optionalKeyword(...values) {
3142
+ if (this.matchKeyword(...values)) {
3143
+ return this.consume();
3144
+ }
3145
+ return null;
3146
+ }
3147
+ /** Parse the node kind filter (IDENT, KEYWORD that's a kind, or STAR) */
3148
+ parseNodeKind() {
3149
+ const tok = this.peek();
3150
+ if (tok.kind === "STAR") {
3151
+ this.consume();
3152
+ return "*";
3153
+ }
3154
+ if (tok.kind === "IDENT" || tok.kind === "KEYWORD") {
3155
+ this.consume();
3156
+ return tok.value.toLowerCase();
3157
+ }
3158
+ return {
3159
+ type: "GQLParseError",
3160
+ message: `Expected node kind or '*' at position ${tok.pos}`,
3161
+ pos: tok.pos
3162
+ };
3163
+ }
3164
+ /** Parse a string value (STRING or IDENT) */
3165
+ parseStringValue() {
3166
+ const tok = this.peek();
3167
+ if (tok.kind === "STRING") {
3168
+ this.consume();
3169
+ return tok.value;
3170
+ }
3171
+ if (tok.kind === "IDENT" || tok.kind === "KEYWORD") {
3172
+ this.consume();
3173
+ return tok.value;
3174
+ }
3175
+ return {
3176
+ type: "GQLParseError",
3177
+ message: `Expected string value at position ${tok.pos}`,
3178
+ pos: tok.pos
3179
+ };
3180
+ }
3181
+ /** Parse an IN list: [ value, value, ... ] */
3182
+ parseInList() {
3183
+ const lb = this.expect("LBRACKET");
3184
+ if (isGQLParseError(lb)) return lb;
3185
+ const values = [];
3186
+ while (!this.matchKeyword() && this.peek().kind !== "RBRACKET" && this.peek().kind !== "EOF") {
3187
+ const v = this.parseStringValue();
3188
+ if (typeof v !== "string") return v;
3189
+ values.push(v);
3190
+ }
3191
+ const rb = this.expect("RBRACKET");
3192
+ if (isGQLParseError(rb)) return rb;
3193
+ return values;
3194
+ }
3195
+ /** Parse a single WHERE expression */
3196
+ parseWhereExpr() {
3197
+ const propTok = this.peek();
3198
+ if (propTok.kind !== "IDENT" && propTok.kind !== "KEYWORD") {
3199
+ return {
3200
+ type: "GQLParseError",
3201
+ message: `Expected property name at position ${propTok.pos}`,
3202
+ pos: propTok.pos
3203
+ };
3204
+ }
3205
+ this.consume();
3206
+ const property = propTok.value.toLowerCase();
3207
+ const opTok = this.peek();
3208
+ if (opTok.kind !== "OPERATOR") {
3209
+ return {
3210
+ type: "GQLParseError",
3211
+ message: `Expected operator (=, !=, CONTAINS, STARTS_WITH, IN) at position ${opTok.pos}`,
3212
+ pos: opTok.pos,
3213
+ expected: "operator",
3214
+ got: opTok.value
3215
+ };
3216
+ }
3217
+ this.consume();
3218
+ const operator = opTok.value;
3219
+ if (operator === "IN") {
3220
+ const list = this.parseInList();
3221
+ if (!Array.isArray(list)) return list;
3222
+ return { property, operator, value: list };
3223
+ }
3224
+ const val = this.parseStringValue();
3225
+ if (typeof val !== "string") return val;
3226
+ return { property, operator, value: val };
3227
+ }
3228
+ /** Parse WHERE clause: WHERE expr (AND expr)* */
3229
+ parseWhereClause() {
3230
+ const kw = this.expect("KEYWORD", "WHERE");
3231
+ if (isGQLParseError(kw)) return kw;
3232
+ const exprs = [];
3233
+ const first = this.parseWhereExpr();
3234
+ if ("type" in first && first.type === "GQLParseError") return first;
3235
+ exprs.push(first);
3236
+ while (this.matchKeyword("AND")) {
3237
+ this.consume();
3238
+ const expr = this.parseWhereExpr();
3239
+ if ("type" in expr && expr.type === "GQLParseError") return expr;
3240
+ exprs.push(expr);
3241
+ }
3242
+ return { exprs };
3243
+ }
3244
+ /** Parse FIND statement */
3245
+ parseFindStatement() {
3246
+ this.consume();
3247
+ const kind = this.parseNodeKind();
3248
+ if (typeof kind !== "string") return kind;
3249
+ let where;
3250
+ if (this.matchKeyword("WHERE")) {
3251
+ const w = this.parseWhereClause();
3252
+ if ("type" in w && w.type === "GQLParseError") return w;
3253
+ where = w;
3254
+ }
3255
+ let limit;
3256
+ let offset;
3257
+ while (this.matchKeyword("LIMIT", "OFFSET")) {
3258
+ const kw = this.consume();
3259
+ const numTok = this.peek();
3260
+ if (numTok.kind !== "NUMBER") {
3261
+ return {
3262
+ type: "GQLParseError",
3263
+ message: `Expected number after ${kw.value} at position ${numTok.pos}`,
3264
+ pos: numTok.pos
3265
+ };
3266
+ }
3267
+ this.consume();
3268
+ const n = parseInt(numTok.value, 10);
3269
+ if (kw.value === "LIMIT") limit = n;
3270
+ else offset = n;
3271
+ }
3272
+ return { type: "FIND", target: kind, where, limit, offset };
3273
+ }
3274
+ /** Parse TRAVERSE statement */
3275
+ parseTraverseStatement() {
3276
+ this.consume();
3277
+ const edgeTok = this.peek();
3278
+ if (edgeTok.kind !== "KEYWORD" && edgeTok.kind !== "IDENT") {
3279
+ return {
3280
+ type: "GQLParseError",
3281
+ message: `Expected edge kind after TRAVERSE at position ${edgeTok.pos}`,
3282
+ pos: edgeTok.pos
3283
+ };
3284
+ }
3285
+ this.consume();
3286
+ const edgeKind = edgeTok.value.toLowerCase();
3287
+ const fromKw = this.expect("KEYWORD", "FROM");
3288
+ if (isGQLParseError(fromKw)) return fromKw;
3289
+ const fromVal = this.parseStringValue();
3290
+ if (typeof fromVal !== "string") return fromVal;
3291
+ let depth;
3292
+ let direction;
3293
+ if (this.matchKeyword("DEPTH")) {
3294
+ this.consume();
3295
+ const numTok = this.peek();
3296
+ if (numTok.kind !== "NUMBER") {
3297
+ return {
3298
+ type: "GQLParseError",
3299
+ message: `Expected number after DEPTH at position ${numTok.pos}`,
3300
+ pos: numTok.pos
3301
+ };
3302
+ }
3303
+ this.consume();
3304
+ depth = parseInt(numTok.value, 10);
3305
+ }
3306
+ if (this.matchKeyword("OUTGOING", "INCOMING", "BOTH")) {
3307
+ direction = this.consume().value;
3308
+ }
3309
+ return { type: "TRAVERSE", edgeKind, from: fromVal, depth, direction };
3310
+ }
3311
+ /** Parse PATH statement */
3312
+ parsePathStatement() {
3313
+ this.consume();
3314
+ const fromKw = this.expect("KEYWORD", "FROM");
3315
+ if (isGQLParseError(fromKw)) return fromKw;
3316
+ const fromVal = this.parseStringValue();
3317
+ if (typeof fromVal !== "string") return fromVal;
3318
+ const toKw = this.expect("KEYWORD", "TO");
3319
+ if (isGQLParseError(toKw)) return toKw;
3320
+ const toVal = this.parseStringValue();
3321
+ if (typeof toVal !== "string") return toVal;
3322
+ return { type: "PATH", from: fromVal, to: toVal };
3323
+ }
3324
+ /** Parse COUNT statement */
3325
+ parseCountStatement() {
3326
+ this.consume();
3327
+ const kind = this.parseNodeKind();
3328
+ if (typeof kind !== "string") return kind;
3329
+ let where;
3330
+ if (this.matchKeyword("WHERE")) {
3331
+ const w = this.parseWhereClause();
3332
+ if ("type" in w && w.type === "GQLParseError") return w;
3333
+ where = w;
3334
+ }
3335
+ let groupBy;
3336
+ if (this.matchKeyword("GROUP")) {
3337
+ this.consume();
3338
+ const byKw = this.expect("KEYWORD", "BY");
3339
+ if (isGQLParseError(byKw)) return byKw;
3340
+ const propTok = this.peek();
3341
+ if (propTok.kind !== "IDENT" && propTok.kind !== "KEYWORD") {
3342
+ return {
3343
+ type: "GQLParseError",
3344
+ message: `Expected property name after GROUP BY at position ${propTok.pos}`,
3345
+ pos: propTok.pos
3346
+ };
3347
+ }
3348
+ this.consume();
3349
+ groupBy = propTok.value.toLowerCase();
3350
+ }
3351
+ return { type: "COUNT", target: kind, where, groupBy };
3352
+ }
3353
+ parse() {
3354
+ const tok = this.peek();
3355
+ if (tok.kind !== "KEYWORD") {
3356
+ return {
3357
+ type: "GQLParseError",
3358
+ message: `Expected FIND, TRAVERSE, PATH, or COUNT at position ${tok.pos}`,
3359
+ pos: tok.pos,
3360
+ expected: "FIND | TRAVERSE | PATH | COUNT",
3361
+ got: tok.value
3362
+ };
3363
+ }
3364
+ let result;
3365
+ switch (tok.value) {
3366
+ case "FIND":
3367
+ result = this.parseFindStatement();
3368
+ break;
3369
+ case "TRAVERSE":
3370
+ result = this.parseTraverseStatement();
3371
+ break;
3372
+ case "PATH":
3373
+ result = this.parsePathStatement();
3374
+ break;
3375
+ case "COUNT":
3376
+ result = this.parseCountStatement();
3377
+ break;
3378
+ default:
3379
+ return {
3380
+ type: "GQLParseError",
3381
+ message: `Unknown statement type '${tok.value}' at position ${tok.pos}`,
3382
+ pos: tok.pos,
3383
+ expected: "FIND | TRAVERSE | PATH | COUNT",
3384
+ got: tok.value
3385
+ };
3386
+ }
3387
+ if (isGQLParseError(result)) return result;
3388
+ const remaining = this.peek();
3389
+ if (remaining.kind !== "EOF") {
3390
+ return {
3391
+ type: "GQLParseError",
3392
+ message: `Unexpected token '${remaining.value}' at position ${remaining.pos}`,
3393
+ pos: remaining.pos,
3394
+ got: remaining.value
3395
+ };
3396
+ }
3397
+ return result;
3398
+ }
3399
+ };
3400
+ }
3401
+ });
3402
+
3403
+ // src/query/gql-executor.ts
3404
+ var gql_executor_exports = {};
3405
+ __export(gql_executor_exports, {
3406
+ executeGQL: () => executeGQL
3407
+ });
3408
+ function getNodeProperty(node, property) {
3409
+ switch (property) {
3410
+ case "name":
3411
+ return node.name;
3412
+ case "kind":
3413
+ return node.kind;
3414
+ case "filepath":
3415
+ case "filePath":
3416
+ return node.filePath;
3417
+ case "exported":
3418
+ return node.exported;
3419
+ case "language":
3420
+ return node.metadata?.language ?? void 0;
3421
+ case "cluster":
3422
+ return node.metadata?.cluster ?? void 0;
3423
+ default:
3424
+ return node.metadata?.[property] ?? void 0;
3425
+ }
3426
+ }
3427
+ function evaluateExpr(node, expr) {
3428
+ const val = getNodeProperty(node, expr.property);
3429
+ if (val === void 0) return false;
3430
+ const strVal = String(val).toLowerCase();
3431
+ switch (expr.operator) {
3432
+ case "=":
3433
+ if (typeof expr.value === "string") {
3434
+ return strVal === expr.value.toLowerCase();
3435
+ }
3436
+ return false;
3437
+ case "!=":
3438
+ if (typeof expr.value === "string") {
3439
+ return strVal !== expr.value.toLowerCase();
3440
+ }
3441
+ return true;
3442
+ case "CONTAINS":
3443
+ if (typeof expr.value === "string") {
3444
+ return strVal.includes(expr.value.toLowerCase());
3445
+ }
3446
+ return false;
3447
+ case "STARTS_WITH":
3448
+ if (typeof expr.value === "string") {
3449
+ return strVal.startsWith(expr.value.toLowerCase());
3450
+ }
3451
+ return false;
3452
+ case "IN":
3453
+ if (Array.isArray(expr.value)) {
3454
+ return expr.value.some((v) => strVal === v.toLowerCase());
3455
+ }
3456
+ return false;
3457
+ default:
3458
+ return false;
3459
+ }
3460
+ }
3461
+ function evaluateWhere(node, where) {
3462
+ return where.exprs.every((expr) => evaluateExpr(node, expr));
3463
+ }
3464
+ function executeFIND(stmt, graph) {
3465
+ const start = Date.now();
3466
+ const limit = stmt.limit ?? 1e3;
3467
+ const offset = stmt.offset ?? 0;
3468
+ let totalCount = 0;
3469
+ let truncated = false;
3470
+ const allMatching = [];
3471
+ const deadline = start + EXECUTION_TIMEOUT_MS;
3472
+ for (const node of graph.allNodes()) {
3473
+ if (Date.now() > deadline) {
3474
+ truncated = true;
3475
+ break;
3476
+ }
3477
+ if (stmt.target !== "*" && node.kind !== stmt.target) continue;
3478
+ if (stmt.where && !evaluateWhere(node, stmt.where)) continue;
3479
+ allMatching.push(node);
3480
+ }
3481
+ totalCount = allMatching.length;
3482
+ const paginated = allMatching.slice(offset, offset + limit);
3483
+ return {
3484
+ nodes: paginated,
3485
+ executionTimeMs: Date.now() - start,
3486
+ truncated,
3487
+ totalCount
3488
+ };
3489
+ }
3490
+ function executeTRAVERSE(stmt, graph) {
3491
+ const start = Date.now();
3492
+ const maxDepth = stmt.depth ?? 5;
3493
+ const edgeKind = stmt.edgeKind;
3494
+ const direction = stmt.direction ?? "OUTGOING";
3495
+ const deadline = start + EXECUTION_TIMEOUT_MS;
3496
+ let startNode;
3497
+ for (const node of graph.allNodes()) {
3498
+ if (node.name === stmt.from) {
3499
+ startNode = node;
3500
+ break;
3501
+ }
3502
+ }
3503
+ if (!startNode) {
3504
+ return {
3505
+ nodes: [],
3506
+ edges: [],
3507
+ executionTimeMs: Date.now() - start,
3508
+ truncated: false,
3509
+ totalCount: 0
3510
+ };
3511
+ }
3512
+ const visitedNodes = /* @__PURE__ */ new Set();
3513
+ const visitedEdges = /* @__PURE__ */ new Set();
3514
+ const resultNodes = [];
3515
+ const resultEdges = [];
3516
+ const queue = [{ id: startNode.id, depth: 0 }];
3517
+ visitedNodes.add(startNode.id);
3518
+ resultNodes.push(startNode);
3519
+ let truncated = false;
3520
+ while (queue.length > 0) {
3521
+ if (Date.now() > deadline) {
3522
+ truncated = true;
3523
+ break;
3524
+ }
3525
+ const { id, depth } = queue.shift();
3526
+ if (depth >= maxDepth) continue;
3527
+ const nextEdges = [];
3528
+ if (direction === "OUTGOING" || direction === "BOTH") {
3529
+ for (const edge of graph.findEdgesFrom(id)) {
3530
+ if (!edgeKind || edge.kind === edgeKind) nextEdges.push(edge);
3531
+ }
3532
+ }
3533
+ if (direction === "INCOMING" || direction === "BOTH") {
3534
+ for (const edge of graph.findEdgesTo(id)) {
3535
+ if (!edgeKind || edge.kind === edgeKind) nextEdges.push(edge);
3536
+ }
3537
+ }
3538
+ for (const edge of nextEdges) {
3539
+ if (!visitedEdges.has(edge.id)) {
3540
+ visitedEdges.add(edge.id);
3541
+ resultEdges.push(edge);
3542
+ }
3543
+ const neighborId = direction === "INCOMING" ? edge.source : edge.target;
3544
+ const effectiveNeighborId = direction === "BOTH" ? edge.source === id ? edge.target : edge.source : neighborId;
3545
+ if (!visitedNodes.has(effectiveNeighborId)) {
3546
+ visitedNodes.add(effectiveNeighborId);
3547
+ const neighborNode = graph.getNode(effectiveNeighborId);
3548
+ if (neighborNode) {
3549
+ resultNodes.push(neighborNode);
3550
+ queue.push({ id: effectiveNeighborId, depth: depth + 1 });
3551
+ }
3552
+ }
3553
+ }
3554
+ }
3555
+ return {
3556
+ nodes: resultNodes,
3557
+ edges: resultEdges,
3558
+ executionTimeMs: Date.now() - start,
3559
+ truncated,
3560
+ totalCount: resultNodes.length
3561
+ };
3562
+ }
3563
+ function executePATH(stmt, graph) {
3564
+ const start = Date.now();
3565
+ const deadline = start + EXECUTION_TIMEOUT_MS;
3566
+ let startNode;
3567
+ let endNode;
3568
+ for (const node of graph.allNodes()) {
3569
+ if (node.name === stmt.from) startNode = node;
3570
+ if (node.name === stmt.to) endNode = node;
3571
+ if (startNode && endNode) break;
3572
+ }
3573
+ if (!startNode || !endNode) {
3574
+ return {
3575
+ path: null,
3576
+ nodes: [],
3577
+ executionTimeMs: Date.now() - start,
3578
+ truncated: false,
3579
+ totalCount: 0
3580
+ };
3581
+ }
3582
+ const visited = /* @__PURE__ */ new Set();
3583
+ const parent = /* @__PURE__ */ new Map();
3584
+ const queue = [startNode.id];
3585
+ visited.add(startNode.id);
3586
+ let found = false;
3587
+ let truncated = false;
3588
+ outer: while (queue.length > 0) {
3589
+ if (Date.now() > deadline) {
3590
+ truncated = true;
3591
+ break;
3592
+ }
3593
+ const current2 = queue.shift();
3594
+ for (const edge of graph.findEdgesFrom(current2)) {
3595
+ const next = edge.target;
3596
+ if (!visited.has(next)) {
3597
+ visited.add(next);
3598
+ parent.set(next, { nodeId: current2, edgeId: edge.id });
3599
+ if (next === endNode.id) {
3600
+ found = true;
3601
+ break outer;
3602
+ }
3603
+ queue.push(next);
3604
+ }
3605
+ }
3606
+ for (const edge of graph.findEdgesTo(current2)) {
3607
+ const next = edge.source;
3608
+ if (!visited.has(next)) {
3609
+ visited.add(next);
3610
+ parent.set(next, { nodeId: current2, edgeId: edge.id });
3611
+ if (next === endNode.id) {
3612
+ found = true;
3613
+ break outer;
3614
+ }
3615
+ queue.push(next);
3616
+ }
3617
+ }
3618
+ }
3619
+ if (!found) {
3620
+ return {
3621
+ path: null,
3622
+ nodes: [],
3623
+ executionTimeMs: Date.now() - start,
3624
+ truncated,
3625
+ totalCount: 0
3626
+ };
3627
+ }
3628
+ const pathNodeIds = [];
3629
+ const pathEdgeIds = [];
3630
+ let current = endNode.id;
3631
+ while (current !== startNode.id) {
3632
+ pathNodeIds.unshift(current);
3633
+ const p = parent.get(current);
3634
+ pathEdgeIds.unshift(p.edgeId);
3635
+ current = p.nodeId;
3636
+ }
3637
+ pathNodeIds.unshift(startNode.id);
3638
+ const pathNodes = pathNodeIds.map((id) => graph.getNode(id)).filter(Boolean);
3639
+ const pathEdges = pathEdgeIds.map((id) => graph.getEdge(id)).filter(Boolean);
3640
+ return {
3641
+ path: pathNodes,
3642
+ nodes: pathNodes,
3643
+ edges: pathEdges,
3644
+ executionTimeMs: Date.now() - start,
3645
+ truncated,
3646
+ totalCount: pathNodes.length
3647
+ };
3648
+ }
3649
+ function executeCOUNT(stmt, graph) {
3650
+ const start = Date.now();
3651
+ const deadline = start + EXECUTION_TIMEOUT_MS;
3652
+ let truncated = false;
3653
+ const groups = /* @__PURE__ */ new Map();
3654
+ let total = 0;
3655
+ for (const node of graph.allNodes()) {
3656
+ if (Date.now() > deadline) {
3657
+ truncated = true;
3658
+ break;
3659
+ }
3660
+ if (stmt.target !== "*" && node.kind !== stmt.target) continue;
3661
+ if (stmt.where && !evaluateWhere(node, stmt.where)) continue;
3662
+ total++;
3663
+ if (stmt.groupBy) {
3664
+ const key = String(getNodeProperty(node, stmt.groupBy) ?? "(none)");
3665
+ groups.set(key, (groups.get(key) ?? 0) + 1);
3666
+ } else {
3667
+ groups.set("total", (groups.get("total") ?? 0) + 1);
3668
+ }
3669
+ }
3670
+ const groupList = [...groups.entries()].map(([key, count]) => ({ key, count }));
3671
+ groupList.sort((a, b) => b.count - a.count);
3672
+ return {
3673
+ groups: groupList,
3674
+ executionTimeMs: Date.now() - start,
3675
+ truncated,
3676
+ totalCount: total
3677
+ };
3678
+ }
3679
+ function executeGQL(ast, graph) {
3680
+ switch (ast.type) {
3681
+ case "FIND":
3682
+ return executeFIND(ast, graph);
3683
+ case "TRAVERSE":
3684
+ return executeTRAVERSE(ast, graph);
3685
+ case "PATH":
3686
+ return executePATH(ast, graph);
3687
+ case "COUNT":
3688
+ return executeCOUNT(ast, graph);
3689
+ default:
3690
+ return {
3691
+ nodes: [],
3692
+ executionTimeMs: 0,
3693
+ truncated: false,
3694
+ totalCount: 0
3695
+ };
3696
+ }
3697
+ }
3698
+ var EXECUTION_TIMEOUT_MS;
3699
+ var init_gql_executor = __esm({
3700
+ "src/query/gql-executor.ts"() {
3701
+ EXECUTION_TIMEOUT_MS = 1e4;
3702
+ }
3703
+ });
3704
+
2957
3705
  // src/errors/codes.ts
2958
3706
  var ErrorCodes, AppError;
2959
3707
  var init_codes = __esm({
@@ -3008,7 +3756,7 @@ function tightenDbFiles(dir) {
3008
3756
  for (const name of fs19.readdirSync(dir)) {
3009
3757
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
3010
3758
  try {
3011
- fs19.chmodSync(path26.join(dir, name), SECURE_FILE_MODE);
3759
+ fs19.chmodSync(path27.join(dir, name), SECURE_FILE_MODE);
3012
3760
  } catch {
3013
3761
  }
3014
3762
  }
@@ -3022,7 +3770,7 @@ var init_fs_secure = __esm({
3022
3770
  }
3023
3771
  });
3024
3772
  function getUsersDBPath() {
3025
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path26.join(os12.homedir(), ".code-intel", "users.db");
3773
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "users.db");
3026
3774
  }
3027
3775
  function getOrCreateUsersDB() {
3028
3776
  if (!_usersDB) {
@@ -3038,7 +3786,7 @@ var init_users_db = __esm({
3038
3786
  UsersDB = class {
3039
3787
  db;
3040
3788
  constructor(dbPath) {
3041
- const dir = path26.dirname(dbPath);
3789
+ const dir = path27.dirname(dbPath);
3042
3790
  secureMkdir(dir);
3043
3791
  this.db = new Database3(dbPath);
3044
3792
  this.db.pragma("journal_mode = WAL");
@@ -3315,7 +4063,7 @@ function getScryptN() {
3315
4063
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
3316
4064
  }
3317
4065
  function getSecretsPath() {
3318
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path26.join(os12.homedir(), ".code-intel", ".secrets");
4066
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path27.join(os12.homedir(), ".code-intel", ".secrets");
3319
4067
  }
3320
4068
  function getMasterPassword() {
3321
4069
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
@@ -3904,7 +4652,7 @@ init_shared();
3904
4652
  init_shared();
3905
4653
  init_typescript();
3906
4654
  function resolveRelative(rawPath, fromFile, workspace) {
3907
- const fromDir = path26.dirname(fromFile);
4655
+ const fromDir = path27.dirname(fromFile);
3908
4656
  const cleaned = rawPath.replace(/['"]/g, "");
3909
4657
  const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
3910
4658
  const resolved = workspace.resolve(fromDir, cleaned);
@@ -3956,7 +4704,7 @@ var pythonModule = {
3956
4704
  resolveImport(rawPath, fromFile, workspace) {
3957
4705
  const cleaned = rawPath.replace(/['"]/g, "");
3958
4706
  const parts = cleaned.split(".");
3959
- const fromDir = path26.dirname(fromFile);
4707
+ const fromDir = path27.dirname(fromFile);
3960
4708
  const relPath = parts.join("/");
3961
4709
  for (const suffix of ["/__init__.py", ".py"]) {
3962
4710
  const r = workspace.resolve(fromDir, relPath + suffix);
@@ -4035,7 +4783,7 @@ var cModule = {
4035
4783
  inheritanceStrategy: "none",
4036
4784
  resolveImport(rawPath, fromFile, workspace) {
4037
4785
  const cleaned = rawPath.replace(/[<>"']/g, "");
4038
- const fromDir = path26.dirname(fromFile);
4786
+ const fromDir = path27.dirname(fromFile);
4039
4787
  return workspace.resolve(fromDir, cleaned);
4040
4788
  },
4041
4789
  isExported(_node) {
@@ -4058,7 +4806,7 @@ var cppModule = {
4058
4806
  inheritanceStrategy: "depth-first",
4059
4807
  resolveImport(rawPath, fromFile, workspace) {
4060
4808
  const cleaned = rawPath.replace(/[<>"']/g, "");
4061
- const fromDir = path26.dirname(fromFile);
4809
+ const fromDir = path27.dirname(fromFile);
4062
4810
  return workspace.resolve(fromDir, cleaned);
4063
4811
  },
4064
4812
  isExported(_node) {
@@ -4220,7 +4968,7 @@ var dartModule = {
4220
4968
  const pkg = cleaned.replace("package:", "");
4221
4969
  return workspace.findByPackage(pkg);
4222
4970
  }
4223
- const fromDir = path26.dirname(fromFile);
4971
+ const fromDir = path27.dirname(fromFile);
4224
4972
  return workspace.resolve(fromDir, cleaned);
4225
4973
  },
4226
4974
  isExported(node) {
@@ -4806,7 +5554,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
4806
5554
  ]);
4807
5555
  function loadIgnorePatterns(workspaceRoot) {
4808
5556
  try {
4809
- const raw = fs19.readFileSync(path26.join(workspaceRoot, ".codeintelignore"), "utf-8");
5557
+ const raw = fs19.readFileSync(path27.join(workspaceRoot, ".codeintelignore"), "utf-8");
4810
5558
  const extras = /* @__PURE__ */ new Set();
4811
5559
  for (const line of raw.split("\n")) {
4812
5560
  const trimmed = line.trim();
@@ -4839,13 +5587,13 @@ var scanPhase = {
4839
5587
  if (entry.name.startsWith(".")) continue;
4840
5588
  if (IGNORED_DIRS.has(entry.name)) continue;
4841
5589
  if (extraIgnore.has(entry.name)) continue;
4842
- walk(path26.join(dir, entry.name));
5590
+ walk(path27.join(dir, entry.name));
4843
5591
  } else if (entry.isFile()) {
4844
5592
  const name = entry.name;
4845
5593
  if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
4846
- const ext = path26.extname(name);
5594
+ const ext = path27.extname(name);
4847
5595
  if (!extensions.has(ext)) continue;
4848
- const fullPath = path26.join(dir, name);
5596
+ const fullPath = path27.join(dir, name);
4849
5597
  try {
4850
5598
  const stat = fs19.statSync(fullPath);
4851
5599
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
@@ -4874,20 +5622,20 @@ var structurePhase = {
4874
5622
  const dirs = /* @__PURE__ */ new Set();
4875
5623
  let structDone = 0;
4876
5624
  for (const filePath of context2.filePaths) {
4877
- const relativePath = path26.relative(context2.workspaceRoot, filePath);
5625
+ const relativePath = path27.relative(context2.workspaceRoot, filePath);
4878
5626
  const lang = detectLanguage(filePath);
4879
5627
  context2.graph.addNode({
4880
5628
  id: generateNodeId("file", relativePath, relativePath),
4881
5629
  kind: "file",
4882
- name: path26.basename(filePath),
5630
+ name: path27.basename(filePath),
4883
5631
  filePath: relativePath,
4884
5632
  metadata: lang ? { language: lang } : void 0
4885
5633
  });
4886
- let dir = path26.dirname(relativePath);
5634
+ let dir = path27.dirname(relativePath);
4887
5635
  while (dir && dir !== "." && dir !== "") {
4888
5636
  if (dirs.has(dir)) break;
4889
5637
  dirs.add(dir);
4890
- dir = path26.dirname(dir);
5638
+ dir = path27.dirname(dir);
4891
5639
  }
4892
5640
  structDone++;
4893
5641
  context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
@@ -4896,7 +5644,7 @@ var structurePhase = {
4896
5644
  context2.graph.addNode({
4897
5645
  id: generateNodeId("directory", dir, dir),
4898
5646
  kind: "directory",
4899
- name: path26.basename(dir),
5647
+ name: path27.basename(dir),
4900
5648
  filePath: dir
4901
5649
  });
4902
5650
  }
@@ -5053,7 +5801,7 @@ var LLMGovernanceLogger = class {
5053
5801
  }
5054
5802
  /** Path to the JSONL log file. */
5055
5803
  getLogPath() {
5056
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path26.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
5804
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
5057
5805
  }
5058
5806
  /**
5059
5807
  * Append an entry to the governance log.
@@ -5069,7 +5817,7 @@ var LLMGovernanceLogger = class {
5069
5817
  ...entry
5070
5818
  };
5071
5819
  const logPath = this.getLogPath();
5072
- fs19.mkdirSync(path26.dirname(logPath), { recursive: true });
5820
+ fs19.mkdirSync(path27.dirname(logPath), { recursive: true });
5073
5821
  fs19.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
5074
5822
  } catch {
5075
5823
  }
@@ -5525,7 +6273,7 @@ var DbManager = class {
5525
6273
  this.dbPath = dbPath;
5526
6274
  }
5527
6275
  async init() {
5528
- fs19.mkdirSync(path26.dirname(this.dbPath), { recursive: true });
6276
+ fs19.mkdirSync(path27.dirname(this.dbPath), { recursive: true });
5529
6277
  this.db = new Database(this.dbPath);
5530
6278
  await this.db.init();
5531
6279
  this.conn = new Connection(this.db);
@@ -5622,7 +6370,7 @@ function writeNodeCSVs(graph, outputDir) {
5622
6370
  const table = NODE_TABLE_MAP[node.kind];
5623
6371
  if (!tableBuffers.has(table)) {
5624
6372
  tableBuffers.set(table, [header]);
5625
- tableFilePaths.set(table, path26.join(outputDir, `${table}.csv`));
6373
+ tableFilePaths.set(table, path27.join(outputDir, `${table}.csv`));
5626
6374
  }
5627
6375
  tableBuffers.get(table).push(
5628
6376
  csvRow([
@@ -5658,7 +6406,7 @@ function writeEdgeCSV(graph, outputDir) {
5658
6406
  const toTable = NODE_TABLE_MAP[targetNode.kind];
5659
6407
  const key = `${fromTable}->${toTable}`;
5660
6408
  if (!groups.has(key)) {
5661
- const filePath = path26.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
6409
+ const filePath = path27.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
5662
6410
  groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
5663
6411
  }
5664
6412
  groups.get(key).lines.push(
@@ -5701,7 +6449,7 @@ async function loadGraphToDB(graph, dbManager) {
5701
6449
  } catch {
5702
6450
  }
5703
6451
  }
5704
- const tmpDir = fs19.mkdtempSync(path26.join(os12.tmpdir(), "code-intel-csv-"));
6452
+ const tmpDir = fs19.mkdtempSync(path27.join(os12.tmpdir(), "code-intel-csv-"));
5705
6453
  try {
5706
6454
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
5707
6455
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -5801,8 +6549,8 @@ function buildNodeProps(node) {
5801
6549
  function escCypher(s) {
5802
6550
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
5803
6551
  }
5804
- var GLOBAL_DIR = path26.join(os12.homedir(), ".code-intel");
5805
- var REPOS_FILE = path26.join(GLOBAL_DIR, "repos.json");
6552
+ var GLOBAL_DIR = path27.join(os12.homedir(), ".code-intel");
6553
+ var REPOS_FILE = path27.join(GLOBAL_DIR, "repos.json");
5806
6554
  function loadRegistry() {
5807
6555
  try {
5808
6556
  const data = fs19.readFileSync(REPOS_FILE, "utf-8");
@@ -5830,23 +6578,23 @@ function removeRepo(repoPath) {
5830
6578
  saveRegistry(entries);
5831
6579
  }
5832
6580
  function saveMetadata(repoDir, metadata) {
5833
- const metaDir = path26.join(repoDir, ".code-intel");
6581
+ const metaDir = path27.join(repoDir, ".code-intel");
5834
6582
  fs19.mkdirSync(metaDir, { recursive: true });
5835
- fs19.writeFileSync(path26.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
6583
+ fs19.writeFileSync(path27.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
5836
6584
  }
5837
6585
  function loadMetadata(repoDir) {
5838
6586
  try {
5839
- const data = fs19.readFileSync(path26.join(repoDir, ".code-intel", "meta.json"), "utf-8");
6587
+ const data = fs19.readFileSync(path27.join(repoDir, ".code-intel", "meta.json"), "utf-8");
5840
6588
  return JSON.parse(data);
5841
6589
  } catch {
5842
6590
  return null;
5843
6591
  }
5844
6592
  }
5845
6593
  function getDbPath(repoDir) {
5846
- return path26.join(repoDir, ".code-intel", "graph.db");
6594
+ return path27.join(repoDir, ".code-intel", "graph.db");
5847
6595
  }
5848
6596
  function getVectorDbPath(repoDir) {
5849
- return path26.join(repoDir, ".code-intel", "vector.db");
6597
+ return path27.join(repoDir, ".code-intel", "vector.db");
5850
6598
  }
5851
6599
 
5852
6600
  // src/mcp-server/server.ts
@@ -6045,7 +6793,7 @@ async function syncGroup(group) {
6045
6793
  logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
6046
6794
  continue;
6047
6795
  }
6048
- const dbPath = path26.join(regEntry.path, ".code-intel", "graph.db");
6796
+ const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
6049
6797
  if (!fs19.existsSync(dbPath)) {
6050
6798
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
6051
6799
  continue;
@@ -6081,7 +6829,7 @@ async function queryGroup(group, query, limit = 20) {
6081
6829
  for (const member of group.members) {
6082
6830
  const regEntry = registry.find((r) => r.name === member.registryName);
6083
6831
  if (!regEntry) continue;
6084
- const dbPath = path26.join(regEntry.path, ".code-intel", "graph.db");
6832
+ const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
6085
6833
  if (!fs19.existsSync(dbPath)) continue;
6086
6834
  const graph = createKnowledgeGraph();
6087
6835
  const db = new DbManager(dbPath);
@@ -6265,6 +7013,23 @@ function createMcpServer(graph, repoName, workspaceRoot) {
6265
7013
  }
6266
7014
  }
6267
7015
  },
7016
+ // ── query (GQL) ────────────────────────────────────────────────────────
7017
+ {
7018
+ name: "query",
7019
+ description: "Execute a GQL (Graph Query Language) query. Supports FIND, TRAVERSE, PATH, and COUNT. More expressive than raw_query.",
7020
+ inputSchema: {
7021
+ type: "object",
7022
+ properties: {
7023
+ gql: {
7024
+ type: "string",
7025
+ 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"'
7026
+ },
7027
+ limit: { type: "number", description: "Override LIMIT in the query (optional)" },
7028
+ ..._tokenProp
7029
+ },
7030
+ required: ["gql"]
7031
+ }
7032
+ },
6268
7033
  // ── Raw query ─────────────────────────────────────────────────────────
6269
7034
  {
6270
7035
  name: "raw_query",
@@ -6445,7 +7210,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
6445
7210
  const limit = a.limit ?? 20;
6446
7211
  const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
6447
7212
  const { results, searchMode } = await hybridSearch(graph, query, limit, { vectorDbPath: vdbPath });
6448
- return { content: [{ type: "text", text: JSON.stringify({ results, searchMode }, null, 2) }] };
7213
+ return { content: [{ type: "text", text: JSON.stringify({ results, searchMode, suggested_next_tools: ["inspect", "query", "blast_radius"] }, null, 2) }] };
6449
7214
  }
6450
7215
  // ── inspect ────────────────────────────────────────────────────────────
6451
7216
  case "inspect": {
@@ -6684,7 +7449,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
6684
7449
  for (const { filePath: changedFile, changedLines } of changedFiles) {
6685
7450
  for (const node of graph.allNodes()) {
6686
7451
  if (!node.filePath) continue;
6687
- const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path26.sep, "");
7452
+ const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path27.sep, "");
6688
7453
  const normChanged = changedFile.replace(/^a\/|^b\//, "");
6689
7454
  if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
6690
7455
  if (node.startLine !== void 0 && node.endLine !== void 0) {
@@ -6733,16 +7498,51 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
6733
7498
  }]
6734
7499
  };
6735
7500
  }
7501
+ // ── query (GQL) ───────────────────────────────────────────────────────────
7502
+ case "query": {
7503
+ const gqlInput = a.gql;
7504
+ if (!gqlInput) {
7505
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Missing required parameter: gql" }) }], isError: true };
7506
+ }
7507
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
7508
+ const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
7509
+ const ast = parseGQL2(gqlInput);
7510
+ if (isGQLParseError2(ast)) {
7511
+ return {
7512
+ content: [{ type: "text", text: JSON.stringify({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
7513
+ isError: true
7514
+ };
7515
+ }
7516
+ if (a.limit !== void 0 && ast.type === "FIND") {
7517
+ ast.limit = a.limit;
7518
+ }
7519
+ const result = executeGQL2(ast, graph);
7520
+ return {
7521
+ content: [{
7522
+ type: "text",
7523
+ text: JSON.stringify({
7524
+ nodes: result.nodes,
7525
+ edges: result.edges,
7526
+ groups: result.groups,
7527
+ path: result.path,
7528
+ executionTimeMs: result.executionTimeMs,
7529
+ truncated: result.truncated,
7530
+ totalCount: result.totalCount
7531
+ }, null, 2)
7532
+ }]
7533
+ };
7534
+ }
6736
7535
  // ── raw_query ──────────────────────────────────────────────────────────
6737
7536
  case "raw_query": {
6738
7537
  const q = a.cypher;
7538
+ const deprecationWarning = "raw_query is deprecated, use query instead";
6739
7539
  const nameMatch = q?.match(/name\s*=\s*['"]([^'"]+)['"]/i);
6740
7540
  if (nameMatch) {
6741
7541
  const results = [];
6742
7542
  for (const node of graph.allNodes()) {
6743
7543
  if (node.name === nameMatch[1]) results.push(node);
6744
7544
  }
6745
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
7545
+ return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
6746
7546
  }
6747
7547
  const kindMatch = q?.match(/:\s*(\w+)/);
6748
7548
  if (kindMatch) {
@@ -6751,9 +7551,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
6751
7551
  if (node.kind === kindMatch[1]) results.push(node);
6752
7552
  if (results.length >= 50) break;
6753
7553
  }
6754
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
7554
+ return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
6755
7555
  }
6756
- return { content: [{ type: "text", text: "Query not recognized. Use name='X' or :kind syntax." }] };
7556
+ 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." }) }] };
6757
7557
  }
6758
7558
  // ── group_list ─────────────────────────────────────────────────────────
6759
7559
  case "group_list": {
@@ -6964,7 +7764,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
6964
7764
  var JobsDB = class {
6965
7765
  db;
6966
7766
  constructor(dbPath) {
6967
- fs19.mkdirSync(path26.dirname(dbPath), { recursive: true });
7767
+ fs19.mkdirSync(path27.dirname(dbPath), { recursive: true });
6968
7768
  this.db = new Database3(dbPath);
6969
7769
  this.db.pragma("journal_mode = WAL");
6970
7770
  this.db.pragma("foreign_keys = ON");
@@ -7106,7 +7906,7 @@ var JobsDB = class {
7106
7906
  }
7107
7907
  };
7108
7908
  function getJobsDBPath() {
7109
- return path26.join(os12.homedir(), ".code-intel", "jobs.db");
7909
+ return path27.join(os12.homedir(), ".code-intel", "jobs.db");
7110
7910
  }
7111
7911
  var _jobsDB = null;
7112
7912
  function getOrCreateJobsDB() {
@@ -7198,7 +7998,7 @@ var BACKUP_VERSION = "1.0";
7198
7998
  var ALGORITHM = "aes-256-gcm";
7199
7999
  var IV_LENGTH = 16;
7200
8000
  function getBackupDir() {
7201
- return path26.join(os12.homedir(), ".code-intel", "backups");
8001
+ return path27.join(os12.homedir(), ".code-intel", "backups");
7202
8002
  }
7203
8003
  function getBackupKey() {
7204
8004
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
@@ -7236,22 +8036,22 @@ var BackupService = class {
7236
8036
  * Returns the backup entry.
7237
8037
  */
7238
8038
  createBackup(repoPath) {
7239
- const codeIntelDir = path26.join(repoPath, ".code-intel");
8039
+ const codeIntelDir = path27.join(repoPath, ".code-intel");
7240
8040
  const id = v4();
7241
8041
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7242
8042
  const filesToBackup = [];
7243
8043
  const candidates = ["graph.db", "vector.db", "meta.json"];
7244
8044
  for (const f of candidates) {
7245
- const fp = path26.join(codeIntelDir, f);
8045
+ const fp = path27.join(codeIntelDir, f);
7246
8046
  if (fs19.existsSync(fp)) {
7247
8047
  filesToBackup.push({ name: f, localPath: fp });
7248
8048
  }
7249
8049
  }
7250
- const registryPath = path26.join(os12.homedir(), ".code-intel", "registry.json");
8050
+ const registryPath = path27.join(os12.homedir(), ".code-intel", "registry.json");
7251
8051
  if (fs19.existsSync(registryPath)) {
7252
8052
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
7253
8053
  }
7254
- const usersDbPath = path26.join(os12.homedir(), ".code-intel", "users.db");
8054
+ const usersDbPath = path27.join(os12.homedir(), ".code-intel", "users.db");
7255
8055
  if (fs19.existsSync(usersDbPath)) {
7256
8056
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
7257
8057
  }
@@ -7288,7 +8088,7 @@ var BackupService = class {
7288
8088
  const plaintext = Buffer.concat(parts);
7289
8089
  const encrypted = encryptBuffer(plaintext, this.key);
7290
8090
  const backupFileName = `backup-${id}.cib`;
7291
- const backupPath = path26.join(this.backupDir, backupFileName);
8091
+ const backupPath = path27.join(this.backupDir, backupFileName);
7292
8092
  fs19.writeFileSync(backupPath, encrypted);
7293
8093
  const entry = {
7294
8094
  id,
@@ -7316,7 +8116,7 @@ var BackupService = class {
7316
8116
  async uploadToS3(entry) {
7317
8117
  const cfg = getS3Config();
7318
8118
  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.");
7319
- const fileName = path26.basename(entry.path);
8119
+ const fileName = path27.basename(entry.path);
7320
8120
  const s3Key = `${cfg.prefix}${fileName}`;
7321
8121
  const body = fs19.readFileSync(entry.path);
7322
8122
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
@@ -7335,7 +8135,7 @@ var BackupService = class {
7335
8135
  if (result.statusCode < 200 || result.statusCode >= 300) {
7336
8136
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
7337
8137
  }
7338
- fs19.mkdirSync(path26.dirname(destPath), { recursive: true });
8138
+ fs19.mkdirSync(path27.dirname(destPath), { recursive: true });
7339
8139
  fs19.writeFileSync(destPath, Buffer.from(result.body, "binary"));
7340
8140
  }
7341
8141
  /**
@@ -7399,7 +8199,7 @@ var BackupService = class {
7399
8199
  offset += manifestLen;
7400
8200
  const manifest = JSON.parse(manifestStr);
7401
8201
  const restoreBase = targetRepoPath ?? entry.repoPath;
7402
- const codeIntelDir = path26.join(restoreBase, ".code-intel");
8202
+ const codeIntelDir = path27.join(restoreBase, ".code-intel");
7403
8203
  fs19.mkdirSync(codeIntelDir, { recursive: true });
7404
8204
  for (const fileEntry of manifest.files) {
7405
8205
  const nameLen = plaintext.readUInt16BE(offset);
@@ -7417,9 +8217,9 @@ var BackupService = class {
7417
8217
  }
7418
8218
  let destPath;
7419
8219
  if (name === "registry.json" || name === "users.db") {
7420
- destPath = path26.join(os12.homedir(), ".code-intel", name);
8220
+ destPath = path27.join(os12.homedir(), ".code-intel", name);
7421
8221
  } else {
7422
- destPath = path26.join(codeIntelDir, name);
8222
+ destPath = path27.join(codeIntelDir, name);
7423
8223
  }
7424
8224
  fs19.writeFileSync(destPath, data);
7425
8225
  }
@@ -7470,7 +8270,7 @@ var BackupService = class {
7470
8270
  }
7471
8271
  // ── Index helpers ──────────────────────────────────────────────────────────
7472
8272
  _indexPath() {
7473
- return path26.join(this.backupDir, "index.json");
8273
+ return path27.join(this.backupDir, "index.json");
7474
8274
  }
7475
8275
  _loadIndex() {
7476
8276
  try {
@@ -7971,6 +8771,60 @@ var openApiSpec = {
7971
8771
  }
7972
8772
  }
7973
8773
  },
8774
+ "/source": {
8775
+ get: {
8776
+ tags: ["Files"],
8777
+ summary: "Get source code preview with context around specified lines",
8778
+ description: "Returns the file content around the specified line range (\xB120 lines context), with language detection. Requires viewer role.",
8779
+ security: [{ BearerAuth: [] }, { SessionCookie: [] }],
8780
+ parameters: [
8781
+ {
8782
+ name: "file",
8783
+ in: "query",
8784
+ required: true,
8785
+ description: "Absolute path to the file",
8786
+ schema: { type: "string" }
8787
+ },
8788
+ {
8789
+ name: "startLine",
8790
+ in: "query",
8791
+ required: false,
8792
+ description: "Start line number (1-indexed)",
8793
+ schema: { type: "integer", minimum: 1 }
8794
+ },
8795
+ {
8796
+ name: "endLine",
8797
+ in: "query",
8798
+ required: false,
8799
+ description: "End line number (1-indexed)",
8800
+ schema: { type: "integer", minimum: 1 }
8801
+ }
8802
+ ],
8803
+ responses: {
8804
+ "200": {
8805
+ description: "Source code preview",
8806
+ content: {
8807
+ "application/json": {
8808
+ schema: {
8809
+ type: "object",
8810
+ properties: {
8811
+ content: { type: "string", description: "File content (with context lines)" },
8812
+ language: { type: "string", description: "Detected programming language", example: "typescript" },
8813
+ startLine: { type: "integer", description: "Actual start line returned (with context)" },
8814
+ endLine: { type: "integer", description: "Actual end line returned (with context)" }
8815
+ },
8816
+ required: ["content", "language", "startLine", "endLine"]
8817
+ }
8818
+ }
8819
+ }
8820
+ },
8821
+ "400": { description: "Bad request (missing file param or path traversal detected)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8822
+ "401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8823
+ "403": { description: "Forbidden (file outside indexed repos)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8824
+ "404": { description: "File not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
8825
+ }
8826
+ }
8827
+ },
7974
8828
  "/grep": {
7975
8829
  post: {
7976
8830
  tags: ["Files"],
@@ -8084,16 +8938,122 @@ var openApiSpec = {
8084
8938
  "404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
8085
8939
  }
8086
8940
  }
8941
+ },
8942
+ "/query": {
8943
+ post: {
8944
+ tags: ["GQL"],
8945
+ summary: "Execute a GQL (Graph Query Language) query against the knowledge graph",
8946
+ description: "Supports FIND, TRAVERSE, PATH, and COUNT statements. Requires viewer role minimum.",
8947
+ security: [{ BearerAuth: [] }, { SessionCookie: [] }],
8948
+ requestBody: {
8949
+ required: true,
8950
+ content: {
8951
+ "application/json": {
8952
+ schema: {
8953
+ type: "object",
8954
+ properties: {
8955
+ gql: {
8956
+ type: "string",
8957
+ description: "GQL query string",
8958
+ example: 'FIND function WHERE name CONTAINS "auth"'
8959
+ },
8960
+ format: {
8961
+ type: "string",
8962
+ enum: ["json", "table", "csv"],
8963
+ default: "json",
8964
+ description: "Output format"
8965
+ }
8966
+ },
8967
+ required: ["gql"]
8968
+ }
8969
+ }
8970
+ }
8971
+ },
8972
+ responses: {
8973
+ "200": {
8974
+ description: "GQL execution result",
8975
+ content: {
8976
+ "application/json": {
8977
+ schema: {
8978
+ type: "object",
8979
+ properties: {
8980
+ nodes: { type: "array", items: { "$ref": "#/components/schemas/CodeNode" } },
8981
+ edges: { type: "array", items: { type: "object" } },
8982
+ groups: { type: "array", items: { type: "object", properties: { key: { type: "string" }, count: { type: "integer" } } } },
8983
+ path: { type: "array", items: { "$ref": "#/components/schemas/CodeNode" }, nullable: true },
8984
+ executionTimeMs: { type: "number" },
8985
+ truncated: { type: "boolean" },
8986
+ totalCount: { type: "integer" }
8987
+ }
8988
+ }
8989
+ }
8990
+ }
8991
+ },
8992
+ "400": { description: "Missing gql field", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8993
+ "401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8994
+ "403": { description: "Forbidden (insufficient role)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
8995
+ "422": { description: "GQL parse error", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
8996
+ }
8997
+ }
8998
+ },
8999
+ "/query/explain": {
9000
+ post: {
9001
+ tags: ["GQL"],
9002
+ summary: "Explain a GQL query \u2014 returns the execution plan without running it",
9003
+ description: "Returns a query plan object describing the steps that would be executed. Requires viewer role minimum.",
9004
+ security: [{ BearerAuth: [] }, { SessionCookie: [] }],
9005
+ requestBody: {
9006
+ required: true,
9007
+ content: {
9008
+ "application/json": {
9009
+ schema: {
9010
+ type: "object",
9011
+ properties: {
9012
+ gql: { type: "string", description: "GQL query string", example: 'FIND function WHERE name CONTAINS "auth"' }
9013
+ },
9014
+ required: ["gql"]
9015
+ }
9016
+ }
9017
+ }
9018
+ },
9019
+ responses: {
9020
+ "200": {
9021
+ description: "Query plan",
9022
+ content: {
9023
+ "application/json": {
9024
+ schema: {
9025
+ type: "object",
9026
+ properties: {
9027
+ plan: {
9028
+ type: "object",
9029
+ properties: {
9030
+ type: { type: "string", enum: ["FIND", "TRAVERSE", "PATH", "COUNT"] },
9031
+ gql: { type: "string" },
9032
+ steps: { type: "array", items: { type: "object" } },
9033
+ estimatedCost: { type: "number" }
9034
+ }
9035
+ },
9036
+ graphSize: { type: "object", properties: { nodes: { type: "integer" }, edges: { type: "integer" } } }
9037
+ }
9038
+ }
9039
+ }
9040
+ }
9041
+ },
9042
+ "400": { description: "Missing gql field", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
9043
+ "401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
9044
+ "422": { description: "GQL parse error", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
9045
+ }
9046
+ }
8087
9047
  }
8088
9048
  }
8089
9049
  };
8090
9050
 
8091
9051
  // src/http/app.ts
8092
- var __dirname$1 = path26.dirname(fileURLToPath(import.meta.url));
9052
+ var __dirname$1 = path27.dirname(fileURLToPath(import.meta.url));
8093
9053
  var WEB_DIST = (() => {
8094
- const bundled = path26.resolve(__dirname$1, "..", "web");
9054
+ const bundled = path27.resolve(__dirname$1, "..", "web");
8095
9055
  if (fs19.existsSync(bundled)) return bundled;
8096
- return path26.resolve(__dirname$1, "..", "..", "..", "web", "dist");
9056
+ return path27.resolve(__dirname$1, "..", "..", "..", "web", "dist");
8097
9057
  })();
8098
9058
  function getAllowedOrigins() {
8099
9059
  const env = process.env["CODE_INTEL_CORS_ORIGINS"];
@@ -8121,6 +9081,7 @@ function createDefaultLimiter() {
8121
9081
  function createApp(graph, repoName, workspaceRoot, watcherState) {
8122
9082
  const app = express();
8123
9083
  app.set("trust proxy", 1);
9084
+ app.use(compression());
8124
9085
  app.use(
8125
9086
  helmet({
8126
9087
  contentSecurityPolicy: false
@@ -8623,7 +9584,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
8623
9584
  const registry = loadRegistry();
8624
9585
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
8625
9586
  if (!entry) return null;
8626
- const dbPath = path26.join(entry.path, ".code-intel", "graph.db");
9587
+ const dbPath = path27.join(entry.path, ".code-intel", "graph.db");
8627
9588
  if (!fs19.existsSync(dbPath)) return null;
8628
9589
  const repoGraph = createKnowledgeGraph();
8629
9590
  const db = new DbManager(dbPath);
@@ -8969,7 +9930,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
8969
9930
  for (const member of group.members) {
8970
9931
  const regEntry = registry.find((r) => r.name === member.registryName);
8971
9932
  if (!regEntry) continue;
8972
- const dbPath = path26.join(regEntry.path, ".code-intel", "graph.db");
9933
+ const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
8973
9934
  if (!fs19.existsSync(dbPath)) continue;
8974
9935
  const db = new DbManager(dbPath);
8975
9936
  try {
@@ -8982,10 +9943,245 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
8982
9943
  }
8983
9944
  res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
8984
9945
  });
9946
+ app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
9947
+ const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
9948
+ if (!file) {
9949
+ res.status(400).json({
9950
+ error: {
9951
+ code: ErrorCodes.INVALID_REQUEST,
9952
+ message: "Missing required query parameter: file",
9953
+ requestId: req.requestId,
9954
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9955
+ }
9956
+ });
9957
+ return;
9958
+ }
9959
+ if (file.includes("../")) {
9960
+ res.status(400).json({
9961
+ error: {
9962
+ code: ErrorCodes.INVALID_REQUEST,
9963
+ message: "Path traversal detected",
9964
+ hint: 'File paths must not contain "../"',
9965
+ requestId: req.requestId,
9966
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9967
+ }
9968
+ });
9969
+ return;
9970
+ }
9971
+ let rawResolved = path27.normalize(file);
9972
+ if (!path27.isAbsolute(rawResolved) && workspaceRoot) {
9973
+ rawResolved = path27.join(workspaceRoot, rawResolved);
9974
+ }
9975
+ const resolvedFile = path27.resolve(rawResolved);
9976
+ function isInsideDir(fileAbs, dir) {
9977
+ const rel = path27.relative(path27.resolve(dir), fileAbs);
9978
+ return !rel.startsWith("..") && !path27.isAbsolute(rel);
9979
+ }
9980
+ if (workspaceRoot) {
9981
+ if (!isInsideDir(resolvedFile, workspaceRoot)) {
9982
+ const registry = loadRegistry();
9983
+ const inKnownRepo = registry.some((r) => isInsideDir(resolvedFile, r.path));
9984
+ if (!inKnownRepo) {
9985
+ res.status(403).json({
9986
+ error: {
9987
+ code: ErrorCodes.FORBIDDEN,
9988
+ message: "Access denied",
9989
+ hint: "File path must be within an indexed repository",
9990
+ requestId: req.requestId,
9991
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9992
+ }
9993
+ });
9994
+ return;
9995
+ }
9996
+ }
9997
+ } else {
9998
+ const registry = loadRegistry();
9999
+ const inKnownRepo = registry.some((r) => isInsideDir(resolvedFile, r.path));
10000
+ if (!inKnownRepo) {
10001
+ res.status(403).json({
10002
+ error: {
10003
+ code: ErrorCodes.FORBIDDEN,
10004
+ message: "Access denied",
10005
+ hint: "File path must be within an indexed repository",
10006
+ requestId: req.requestId,
10007
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10008
+ }
10009
+ });
10010
+ return;
10011
+ }
10012
+ }
10013
+ let fileContent;
10014
+ try {
10015
+ fileContent = fs19.readFileSync(resolvedFile, "utf-8");
10016
+ } catch {
10017
+ res.status(404).json({
10018
+ error: {
10019
+ code: ErrorCodes.NOT_FOUND,
10020
+ message: "File not found",
10021
+ requestId: req.requestId,
10022
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10023
+ }
10024
+ });
10025
+ return;
10026
+ }
10027
+ const lines = fileContent.split("\n");
10028
+ const parsedStart = startLineStr ? Number.parseInt(startLineStr, 10) : 1;
10029
+ const parsedEnd = endLineStr ? Number.parseInt(endLineStr, 10) : parsedStart;
10030
+ if (!Number.isFinite(parsedStart) || parsedStart < 1 || !Number.isFinite(parsedEnd) || parsedEnd < 1) {
10031
+ res.status(400).json({
10032
+ error: {
10033
+ code: ErrorCodes.INVALID_REQUEST,
10034
+ message: "Invalid startLine or endLine: must be positive integers",
10035
+ requestId: req.requestId,
10036
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10037
+ }
10038
+ });
10039
+ return;
10040
+ }
10041
+ const startLine = Math.max(1, parsedStart);
10042
+ const endLine = Math.min(lines.length, parsedEnd);
10043
+ const contextStart = Math.max(1, startLine - 20);
10044
+ const contextEnd = Math.min(lines.length, endLine + 20);
10045
+ const content = lines.slice(contextStart - 1, contextEnd).join("\n");
10046
+ const ext = path27.extname(resolvedFile).toLowerCase();
10047
+ const languageMap = {
10048
+ ".ts": "typescript",
10049
+ ".tsx": "typescript",
10050
+ ".js": "javascript",
10051
+ ".jsx": "javascript",
10052
+ ".mjs": "javascript",
10053
+ ".cjs": "javascript",
10054
+ ".py": "python",
10055
+ ".go": "go",
10056
+ ".rs": "rust",
10057
+ ".java": "java",
10058
+ ".cs": "csharp",
10059
+ ".cpp": "cpp",
10060
+ ".cc": "cpp",
10061
+ ".cxx": "cpp",
10062
+ ".c": "c",
10063
+ ".h": "c",
10064
+ ".hpp": "cpp",
10065
+ ".rb": "ruby",
10066
+ ".php": "php",
10067
+ ".swift": "swift",
10068
+ ".kt": "kotlin",
10069
+ ".kts": "kotlin",
10070
+ ".json": "json",
10071
+ ".yaml": "yaml",
10072
+ ".yml": "yaml",
10073
+ ".md": "markdown",
10074
+ ".sh": "bash",
10075
+ ".bash": "bash",
10076
+ ".zsh": "bash",
10077
+ ".sql": "sql",
10078
+ ".html": "html",
10079
+ ".htm": "html",
10080
+ ".css": "css",
10081
+ ".scss": "scss",
10082
+ ".less": "less",
10083
+ ".xml": "xml",
10084
+ ".toml": "toml"
10085
+ };
10086
+ const language = languageMap[ext] ?? "plaintext";
10087
+ res.json({
10088
+ content,
10089
+ language,
10090
+ startLine: contextStart,
10091
+ endLine: contextEnd
10092
+ });
10093
+ });
10094
+ app.post("/api/v1/query", requireRole("viewer"), async (req, res) => {
10095
+ const { gql, format } = req.body;
10096
+ if (!gql || typeof gql !== "string") {
10097
+ res.status(400).json({
10098
+ error: { code: ErrorCodes.INVALID_REQUEST, message: "Missing required field: gql", requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
10099
+ });
10100
+ return;
10101
+ }
10102
+ try {
10103
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
10104
+ const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
10105
+ const ast = parseGQL2(gql);
10106
+ if (isGQLParseError2(ast)) {
10107
+ res.status(422).json({
10108
+ error: {
10109
+ code: ErrorCodes.INVALID_REQUEST,
10110
+ message: `GQL parse error: ${ast.message}`,
10111
+ hint: `Position: ${ast.pos}${ast.expected ? `, expected: ${ast.expected}` : ""}${ast.got ? `, got: ${ast.got}` : ""}`,
10112
+ requestId: req.requestId,
10113
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10114
+ }
10115
+ });
10116
+ return;
10117
+ }
10118
+ const result = executeGQL2(ast, graph);
10119
+ const statusCode = result.truncated ? 408 : 200;
10120
+ res.status(statusCode).json({ ...result, format: format ?? "json" });
10121
+ } catch (err) {
10122
+ 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() } });
10123
+ }
10124
+ });
10125
+ app.post("/api/v1/query/explain", requireRole("viewer"), async (req, res) => {
10126
+ const { gql } = req.body;
10127
+ if (!gql || typeof gql !== "string") {
10128
+ res.status(400).json({
10129
+ error: { code: ErrorCodes.INVALID_REQUEST, message: "Missing required field: gql", requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
10130
+ });
10131
+ return;
10132
+ }
10133
+ try {
10134
+ const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
10135
+ const ast = parseGQL2(gql);
10136
+ if (isGQLParseError2(ast)) {
10137
+ res.status(422).json({
10138
+ error: {
10139
+ code: ErrorCodes.INVALID_REQUEST,
10140
+ message: `GQL parse error: ${ast.message}`,
10141
+ hint: `Position: ${ast.pos}`,
10142
+ requestId: req.requestId,
10143
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10144
+ }
10145
+ });
10146
+ return;
10147
+ }
10148
+ const plan = { type: ast.type, gql };
10149
+ if (ast.type === "FIND") {
10150
+ plan.steps = [
10151
+ { step: 1, op: "SCAN_NODES", filter: ast.target === "*" ? "all" : `kind=${ast.target}` },
10152
+ ...ast.where ? [{ step: 2, op: "WHERE", conditions: ast.where.exprs.length }] : [],
10153
+ ...ast.limit !== void 0 ? [{ step: 3, op: "LIMIT", value: ast.limit }] : []
10154
+ ];
10155
+ plan.estimatedCost = graph.size.nodes;
10156
+ } else if (ast.type === "TRAVERSE") {
10157
+ plan.steps = [
10158
+ { step: 1, op: "FIND_START_NODE", name: ast.from },
10159
+ { step: 2, op: "BFS", edgeKind: ast.edgeKind, maxDepth: ast.depth ?? 5 }
10160
+ ];
10161
+ plan.estimatedCost = Math.min(graph.size.nodes, Math.pow(4, ast.depth ?? 5));
10162
+ } else if (ast.type === "PATH") {
10163
+ plan.steps = [
10164
+ { step: 1, op: "FIND_NODES", from: ast.from, to: ast.to },
10165
+ { step: 2, op: "BFS_SHORTEST_PATH" }
10166
+ ];
10167
+ plan.estimatedCost = graph.size.nodes + graph.size.edges;
10168
+ } else if (ast.type === "COUNT") {
10169
+ plan.steps = [
10170
+ { step: 1, op: "SCAN_NODES", filter: ast.target === "*" ? "all" : `kind=${ast.target}` },
10171
+ ...ast.where ? [{ step: 2, op: "WHERE", conditions: ast.where.exprs.length }] : [],
10172
+ ...ast.groupBy ? [{ step: 3, op: "GROUP_BY", property: ast.groupBy }] : [{ step: 3, op: "COUNT" }]
10173
+ ];
10174
+ plan.estimatedCost = graph.size.nodes;
10175
+ }
10176
+ res.json({ plan, graphSize: graph.size });
10177
+ } catch (err) {
10178
+ 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() } });
10179
+ }
10180
+ });
8985
10181
  if (fs19.existsSync(WEB_DIST)) {
8986
10182
  app.use(express.static(WEB_DIST));
8987
10183
  app.get("/{*path}", (_req, res) => {
8988
- res.sendFile(path26.join(WEB_DIST, "index.html"));
10184
+ res.sendFile(path27.join(WEB_DIST, "index.html"));
8989
10185
  });
8990
10186
  }
8991
10187
  app.use("/admin", requireRole("admin"));