@vohongtho.infotech/code-intel 0.9.0 → 1.0.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,8 +1,3 @@
1
- import { createRequire } from 'module';
2
- import { fileURLToPath } from 'url';
3
- import path31 from 'path';
4
- import fs24, { existsSync } from 'fs';
5
- import { Parser, Language, Query } from 'web-tree-sitter';
6
1
  import { NodeSDK } from '@opentelemetry/sdk-node';
7
2
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
8
3
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
@@ -11,9 +6,14 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
11
6
  import { trace, context, SpanStatusCode } from '@opentelemetry/api';
12
7
  import winston from 'winston';
13
8
  import DailyRotateFile from 'winston-daily-rotate-file';
9
+ import fs25, { existsSync } from 'fs';
10
+ import path32 from 'path';
14
11
  import os13 from 'os';
12
+ import { createRequire } from 'module';
13
+ import { fileURLToPath } from 'url';
14
+ import { Parser, Language, Query } from 'web-tree-sitter';
15
15
  import { execSync } from 'child_process';
16
- import Database3 from 'better-sqlite3';
16
+ import Database2 from 'better-sqlite3';
17
17
  import bcrypt from 'bcrypt';
18
18
  import crypto5 from 'crypto';
19
19
  import { v4 } from 'uuid';
@@ -68,220 +68,553 @@ var init_id_generator = __esm({
68
68
  }
69
69
  });
70
70
 
71
- // src/shared/languages.ts
72
- var init_languages = __esm({
73
- "src/shared/languages.ts"() {
74
- }
71
+ // src/observability/tracing.ts
72
+ var tracing_exports = {};
73
+ __export(tracing_exports, {
74
+ SpanStatusCode: () => SpanStatusCode,
75
+ context: () => context,
76
+ getActiveTraceContext: () => getActiveTraceContext,
77
+ getTracer: () => getTracer,
78
+ initTracing: () => initTracing,
79
+ isTracingEnabled: () => isTracingEnabled,
80
+ sanitizeAttrs: () => sanitizeAttrs,
81
+ shutdownTracing: () => shutdownTracing,
82
+ trace: () => trace,
83
+ withSpan: () => withSpan
75
84
  });
76
-
77
- // src/shared/detection.ts
78
- function detectLanguage(filePath) {
79
- const ext = filePath.slice(filePath.lastIndexOf("."));
80
- return EXTENSION_MAP[ext] ?? null;
85
+ function isTracingEnabled() {
86
+ return process.env["CODE_INTEL_OTEL_ENABLED"] === "true";
81
87
  }
82
- function getSupportedExtensions() {
83
- return Object.keys(EXTENSION_MAP);
88
+ function initTracing() {
89
+ if (!isTracingEnabled()) return;
90
+ if (_sdk) return;
91
+ const endpoint = process.env["CODE_INTEL_OTEL_ENDPOINT"] ?? "http://localhost:4318";
92
+ const serviceName = process.env["CODE_INTEL_OTEL_SERVICE"] ?? "code-intel";
93
+ const deploymentEnv = process.env["CODE_INTEL_OTEL_ENV"] ?? process.env["NODE_ENV"] ?? "development";
94
+ const exporter = new OTLPTraceExporter({ url: `${endpoint}/v1/traces` });
95
+ _sdk = new NodeSDK({
96
+ resource: resourceFromAttributes({
97
+ [SEMRESATTRS_SERVICE_NAME]: serviceName,
98
+ [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: deploymentEnv
99
+ }),
100
+ traceExporter: exporter,
101
+ instrumentations: [
102
+ getNodeAutoInstrumentations({
103
+ // Disable noisy file-system instrumentation
104
+ "@opentelemetry/instrumentation-fs": { enabled: false }
105
+ })
106
+ ]
107
+ });
108
+ _sdk.start();
84
109
  }
85
- var EXTENSION_MAP;
86
- var init_detection = __esm({
87
- "src/shared/detection.ts"() {
88
- init_languages();
89
- EXTENSION_MAP = {
90
- ".ts": "typescript" /* TypeScript */,
91
- ".tsx": "typescript" /* TypeScript */,
92
- ".mts": "typescript" /* TypeScript */,
93
- ".cts": "typescript" /* TypeScript */,
94
- ".js": "javascript" /* JavaScript */,
95
- ".jsx": "javascript" /* JavaScript */,
96
- ".mjs": "javascript" /* JavaScript */,
97
- ".cjs": "javascript" /* JavaScript */,
98
- ".py": "python" /* Python */,
99
- ".pyi": "python" /* Python */,
100
- ".java": "java" /* Java */,
101
- ".go": "go" /* Go */,
102
- ".c": "c" /* C */,
103
- ".h": "c" /* C */,
104
- ".cpp": "cpp" /* Cpp */,
105
- ".cxx": "cpp" /* Cpp */,
106
- ".cc": "cpp" /* Cpp */,
107
- ".hpp": "cpp" /* Cpp */,
108
- ".hxx": "cpp" /* Cpp */,
109
- ".cs": "csharp" /* CSharp */,
110
- ".rs": "rust" /* Rust */,
111
- ".php": "php" /* PHP */,
112
- ".kt": "kotlin" /* Kotlin */,
113
- ".kts": "kotlin" /* Kotlin */,
114
- ".rb": "ruby" /* Ruby */,
115
- ".swift": "swift" /* Swift */,
116
- ".dart": "dart" /* Dart */
117
- };
118
- }
119
- });
120
-
121
- // src/shared/index.ts
122
- var init_shared = __esm({
123
- "src/shared/index.ts"() {
124
- init_languages();
125
- init_detection();
126
- }
127
- });
128
- function findBundledWasmDir() {
129
- const fileDir = path31.dirname(fileURLToPath(import.meta.url));
130
- const candidates = [
131
- path31.join(fileDir, "wasm"),
132
- // dist/index.js → dist/wasm/
133
- path31.join(fileDir, "../wasm")
134
- // dist/cli/main.js → dist/wasm/
135
- ];
136
- for (const candidate of candidates) {
137
- if (existsSync(candidate)) return candidate;
110
+ async function shutdownTracing() {
111
+ if (_sdk) {
112
+ await _sdk.shutdown();
113
+ _sdk = null;
138
114
  }
139
- return candidates[0];
140
115
  }
141
- function wasmPath(lang) {
142
- const WASM_PACKAGE_MAP = {
143
- ["typescript" /* TypeScript */]: "tree-sitter-typescript/tree-sitter-typescript.wasm",
144
- ["javascript" /* JavaScript */]: "tree-sitter-javascript/tree-sitter-javascript.wasm",
145
- ["python" /* Python */]: "tree-sitter-python/tree-sitter-python.wasm",
146
- ["java" /* Java */]: "tree-sitter-java/tree-sitter-java.wasm",
147
- ["go" /* Go */]: "tree-sitter-go/tree-sitter-go.wasm",
148
- ["c" /* C */]: "tree-sitter-c/tree-sitter-c.wasm",
149
- ["cpp" /* Cpp */]: "tree-sitter-cpp/tree-sitter-cpp.wasm",
150
- ["csharp" /* CSharp */]: "tree-sitter-c-sharp/tree-sitter-c_sharp.wasm",
151
- ["rust" /* Rust */]: "tree-sitter-rust/tree-sitter-rust.wasm",
152
- ["php" /* PHP */]: "tree-sitter-php/tree-sitter-php.wasm",
153
- ["ruby" /* Ruby */]: "tree-sitter-ruby/tree-sitter-ruby.wasm",
154
- // These are optional dependencies; their packages may or may not include
155
- // a WASM. If require.resolve fails we fall back to the bundled wasm/.
156
- ["swift" /* Swift */]: "tree-sitter-swift/tree-sitter-swift.wasm",
157
- ["kotlin" /* Kotlin */]: "tree-sitter-kotlin/tree-sitter-kotlin.wasm",
158
- ["dart" /* Dart */]: "tree-sitter-dart/tree-sitter-dart.wasm"
159
- };
160
- const BUNDLED_WASM_MAP = {
161
- ["swift" /* Swift */]: "tree-sitter-swift.wasm",
162
- ["kotlin" /* Kotlin */]: "tree-sitter-kotlin.wasm",
163
- ["dart" /* Dart */]: "tree-sitter-dart.wasm"
164
- };
165
- const relative = WASM_PACKAGE_MAP[lang];
166
- if (relative) {
116
+ function getTracer() {
117
+ return trace.getTracer(TRACER_NAME);
118
+ }
119
+ async function withSpan(name, attrs, fn) {
120
+ return getTracer().startActiveSpan(name, { attributes: sanitizeAttrs(attrs) }, async (span) => {
167
121
  try {
168
- return _require.resolve(relative);
169
- } catch {
122
+ const result = await fn(span);
123
+ span.setStatus({ code: SpanStatusCode.OK });
124
+ return result;
125
+ } catch (err) {
126
+ span.setStatus({
127
+ code: SpanStatusCode.ERROR,
128
+ message: err instanceof Error ? err.message : String(err)
129
+ });
130
+ span.recordException(err);
131
+ throw err;
132
+ } finally {
133
+ span.end();
170
134
  }
171
- }
172
- const bundled = BUNDLED_WASM_MAP[lang];
173
- if (bundled) {
174
- const bundledPath = path31.join(_bundledWasmDir, bundled);
175
- if (existsSync(bundledPath)) return bundledPath;
176
- }
177
- return null;
178
- }
179
- async function initParser() {
180
- if (!initPromise) {
181
- initPromise = Parser.init();
182
- }
183
- return initPromise;
184
- }
185
- async function getLanguage(lang) {
186
- if (languageCache.has(lang)) return languageCache.get(lang);
187
- const path32 = wasmPath(lang);
188
- if (!path32) {
189
- languageCache.set(lang, null);
190
- return null;
191
- }
192
- try {
193
- await initParser();
194
- const language = await Language.load(path32);
195
- languageCache.set(lang, language);
196
- return language;
197
- } catch {
198
- languageCache.set(lang, null);
199
- return null;
200
- }
135
+ });
201
136
  }
202
- async function getParser(lang) {
203
- const language = await getLanguage(lang);
204
- if (!language) return null;
205
- let parser = parserCache.get(lang);
206
- if (!parser) {
207
- parser = new Parser();
208
- parserCache.set(lang, parser);
137
+ function sanitizeAttrs(attrs) {
138
+ const safe = {};
139
+ for (const [k, v] of Object.entries(attrs)) {
140
+ if (BLOCKED_ATTR_KEYS.test(k)) continue;
141
+ safe[k] = v;
209
142
  }
210
- parser.setLanguage(language);
211
- return parser;
212
- }
213
- async function parseSource(lang, source) {
214
- const parser = await getParser(lang);
215
- if (!parser) return null;
216
- return parser.parse(source);
143
+ return safe;
217
144
  }
218
- async function isTreeSitterAvailable(lang) {
219
- return await getLanguage(lang) !== null;
145
+ function getActiveTraceContext() {
146
+ const span = trace.getActiveSpan();
147
+ if (!span) return { traceId: "", spanId: "" };
148
+ const ctx = span.spanContext();
149
+ return { traceId: ctx.traceId, spanId: ctx.spanId };
220
150
  }
221
- var _require, _bundledWasmDir, initPromise, languageCache, parserCache;
222
- var init_parser_manager = __esm({
223
- "src/parsing/parser-manager.ts"() {
224
- init_shared();
225
- _require = createRequire(import.meta.url);
226
- _bundledWasmDir = findBundledWasmDir();
227
- initPromise = null;
228
- languageCache = /* @__PURE__ */ new Map();
229
- parserCache = /* @__PURE__ */ new Map();
151
+ var _sdk, TRACER_NAME, BLOCKED_ATTR_KEYS;
152
+ var init_tracing = __esm({
153
+ "src/observability/tracing.ts"() {
154
+ _sdk = null;
155
+ TRACER_NAME = "code-intel";
156
+ BLOCKED_ATTR_KEYS = /secret|password|token|key|auth|credential/i;
230
157
  }
231
158
  });
232
- function getOrCompileQuery(language, querySource) {
233
- let langMap = _queryCache.get(language);
234
- if (!langMap) {
235
- langMap = /* @__PURE__ */ new Map();
236
- _queryCache.set(language, langMap);
237
- }
238
- let q = langMap.get(querySource);
239
- if (!q) {
240
- q = new Query(language, querySource);
241
- langMap.set(querySource, q);
242
- }
243
- return q;
159
+ function getActiveTraceCtx() {
160
+ if (_getTraceCtx === "pending") return { traceId: "", spanId: "" };
161
+ if (_getTraceCtx) return _getTraceCtx();
162
+ _getTraceCtx = "pending";
163
+ Promise.resolve().then(() => (init_tracing(), tracing_exports)).then((mod) => {
164
+ _getTraceCtx = mod.getActiveTraceContext;
165
+ }).catch(() => {
166
+ _getTraceCtx = null;
167
+ });
168
+ return { traceId: "", spanId: "" };
244
169
  }
245
- function runQuery(tree, language, querySource) {
246
- const query = getOrCompileQuery(language, querySource);
247
- const matches = query.matches(tree.rootNode);
248
- const captures = [];
249
- for (const match of matches) {
250
- for (const capture of match.captures) {
251
- captures.push({
252
- name: capture.name,
253
- node: capture.node,
254
- text: capture.node.text
255
- });
256
- }
170
+ var _getTraceCtx, SENSITIVE_KEYS, SENSITIVE_PATTERNS, SENSITIVE_KEYS_REGEX, Logger, logger_default;
171
+ var init_logger = __esm({
172
+ "src/shared/logger.ts"() {
173
+ _getTraceCtx = null;
174
+ SENSITIVE_KEYS = [
175
+ "password",
176
+ "passwd",
177
+ "pass",
178
+ "pwd",
179
+ "secret",
180
+ "secretkey",
181
+ "secret_key",
182
+ "secretaccesskey",
183
+ "accesskeyid",
184
+ "credentials",
185
+ "auth",
186
+ "authentication",
187
+ "login",
188
+ "api_key",
189
+ "apikey",
190
+ "api",
191
+ "access_key",
192
+ "access_token",
193
+ "accesskey",
194
+ "auth_key",
195
+ "auth_token",
196
+ "authkey",
197
+ "token",
198
+ "jwt",
199
+ "bearer_token",
200
+ "refresh_token",
201
+ "session_token",
202
+ "session_key",
203
+ "oauth_token",
204
+ "connection_string",
205
+ "conn_string",
206
+ "db_uri",
207
+ "db_url",
208
+ "database_url",
209
+ "mongodb_uri",
210
+ "mysql_uri",
211
+ "postgres_uri",
212
+ "sql_uri",
213
+ "db_username",
214
+ "db_password",
215
+ "db_host",
216
+ "db_port",
217
+ "db_name",
218
+ "encryption_key",
219
+ "crypto_key",
220
+ "private_key",
221
+ "public_key",
222
+ "ssl_key",
223
+ "ssh_key",
224
+ "pgp_key",
225
+ "rsa_key",
226
+ "aes_key",
227
+ "email",
228
+ "phone",
229
+ "telephone",
230
+ "mobile",
231
+ "ssn",
232
+ "social_security",
233
+ "credit_card",
234
+ "cc_number",
235
+ "card_number",
236
+ "cvv",
237
+ "expiry_date",
238
+ "birth_date",
239
+ "dob",
240
+ "address",
241
+ "zip_code",
242
+ "postal_code",
243
+ "bank_account",
244
+ "iban",
245
+ "swift_code",
246
+ "routing_number",
247
+ "tax_id",
248
+ "vat_number",
249
+ "financial_id",
250
+ "certificate",
251
+ "client_cert",
252
+ "server_cert",
253
+ "ca_cert",
254
+ "aws_key",
255
+ "aws_secret",
256
+ "azure_key",
257
+ "gcp_key",
258
+ "s3_key",
259
+ "cloudinary_key",
260
+ "stripe_key",
261
+ "paypal_key",
262
+ "twilio_key",
263
+ "app_secret",
264
+ "client_secret",
265
+ "consumer_secret",
266
+ "encryption_secret",
267
+ "master_key",
268
+ "root_password",
269
+ "admin_password",
270
+ "config_secret",
271
+ "env_secret",
272
+ "deploy_key",
273
+ "ci_key",
274
+ "session_id",
275
+ "cookie_secret",
276
+ "csrf_token",
277
+ "xsrf_token",
278
+ "license_key",
279
+ "product_key",
280
+ "serial_number",
281
+ "activation_code"
282
+ ];
283
+ SENSITIVE_PATTERNS = [
284
+ /(?:password|passwd|secret|api_key|access_token|auth_token|token)\s*[:=]\s*([^\s,]+)/gi,
285
+ /\b\d{16}\b/gi,
286
+ /\b\d{3}-\d{2}-\d{4}\b/gi,
287
+ /\b[A-Za-z0-9]{32}\b/gi,
288
+ /\b[A-Za-z0-9_-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b/gi,
289
+ /\b\d{10}\b/gi,
290
+ /\b[A-Za-z0-9]{64}\b/gi,
291
+ /(?:connection_string|db_uri|db_url|mongodb_uri)\s*[:=]\s*([^\s,]+)/gi,
292
+ /(?:apikey|api_key|auth_key)\s*[:=]\s*([^\s,]+)/gi,
293
+ /(?:bearer\s+)([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)/gi
294
+ ];
295
+ SENSITIVE_KEYS_REGEX = new RegExp(`^(${SENSITIVE_KEYS.join("|")})$`, "i");
296
+ Logger = class _Logger {
297
+ static instance = null;
298
+ static maskSensitiveData(value) {
299
+ if (typeof value === "string" && value.length > 5) {
300
+ const firstChar = value.at(0);
301
+ const lastChar = value.at(-1);
302
+ return firstChar + "*".repeat(value.length - 2) + lastChar;
303
+ }
304
+ return value;
305
+ }
306
+ static maskSensitive(message, args = []) {
307
+ const maskString = (input) => {
308
+ if (typeof input !== "string") return input;
309
+ return SENSITIVE_PATTERNS.reduce((str, pattern) => {
310
+ return str.replace(
311
+ pattern,
312
+ (match, value) => value ? match.replace(value, _Logger.maskSensitiveData(value)) : match
313
+ );
314
+ }, input);
315
+ };
316
+ const deepMask = (obj) => {
317
+ if (typeof obj === "string") return maskString(obj);
318
+ if (Array.isArray(obj)) return obj.map((item) => deepMask(item));
319
+ if (typeof obj === "object" && obj !== null) {
320
+ return Object.entries(obj).reduce(
321
+ (acc, [key, value]) => {
322
+ if (value === void 0) return acc;
323
+ const isSensitiveKey = SENSITIVE_KEYS_REGEX.test(key);
324
+ acc[key] = isSensitiveKey && typeof value === "string" ? _Logger.maskSensitiveData(value) : deepMask(value);
325
+ return acc;
326
+ },
327
+ {}
328
+ );
329
+ }
330
+ return obj;
331
+ };
332
+ return {
333
+ maskedMessage: maskString(message),
334
+ maskedArgs: args.map((arg) => deepMask(arg))
335
+ };
336
+ }
337
+ /** Global log directory: ~/.code-intel/logs */
338
+ static LOG_DIR = path32.join(os13.homedir(), ".code-intel", "logs");
339
+ static getLogger() {
340
+ if (!_Logger.instance) {
341
+ const isProduction = process.env.NODE_ENV === "production";
342
+ const logLevel = process.env.LOG_LEVEL ?? "info";
343
+ const transports = [];
344
+ transports.push(new winston.transports.Console());
345
+ if (!isProduction) {
346
+ try {
347
+ if (!fs25.existsSync(_Logger.LOG_DIR)) {
348
+ fs25.mkdirSync(_Logger.LOG_DIR, { recursive: true });
349
+ }
350
+ transports.push(
351
+ new DailyRotateFile({
352
+ filename: path32.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
353
+ datePattern: "YYYY-MM-DD",
354
+ maxSize: "20m",
355
+ maxFiles: "14d"
356
+ })
357
+ );
358
+ } catch {
359
+ }
360
+ }
361
+ _Logger.instance = winston.createLogger({
362
+ level: logLevel,
363
+ format: winston.format.combine(
364
+ winston.format.timestamp(),
365
+ winston.format.printf(({ timestamp, level, message, ...meta }) => {
366
+ const args = meta[/* @__PURE__ */ Symbol.for("splat")] || [];
367
+ const { maskedMessage, maskedArgs } = _Logger.maskSensitive(message, args);
368
+ const formattedArgs = maskedArgs.map(
369
+ (arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
370
+ );
371
+ const suffix = formattedArgs.length ? " " + formattedArgs.join(" ") : "";
372
+ let traceCtx = "";
373
+ try {
374
+ const { traceId, spanId } = getActiveTraceCtx();
375
+ if (traceId) traceCtx = ` [trace=${traceId} span=${spanId}]`;
376
+ } catch {
377
+ }
378
+ return `${timestamp} [${level.toUpperCase()}]${traceCtx}: ${maskedMessage}${suffix}`;
379
+ })
380
+ ),
381
+ transports
382
+ });
383
+ }
384
+ return _Logger.instance;
385
+ }
386
+ static info(message, ...args) {
387
+ _Logger.getLogger().info(message, ...args);
388
+ }
389
+ static warn(message, ...args) {
390
+ _Logger.getLogger().warn(message, ...args);
391
+ }
392
+ static error(message, ...args) {
393
+ _Logger.getLogger().error(message, ...args);
394
+ }
395
+ static debug(message, ...args) {
396
+ _Logger.getLogger().debug(message, ...args);
397
+ }
398
+ };
399
+ logger_default = Logger;
400
+ Logger.getLogger();
257
401
  }
258
- return captures;
259
- }
260
- function runQueryMatches(tree, language, querySource) {
261
- const query = getOrCompileQuery(language, querySource);
262
- const raw = query.matches(tree.rootNode);
263
- return raw.map((m) => ({
264
- patternIndex: m.patternIndex,
265
- captures: m.captures.map((c) => ({
266
- name: c.name,
267
- node: c.node,
268
- text: c.node.text
269
- }))
270
- }));
271
- }
272
- var _queryCache;
273
- var init_query_runner = __esm({
274
- "src/parsing/query-runner.ts"() {
275
- _queryCache = /* @__PURE__ */ new WeakMap();
402
+ });
403
+
404
+ // src/shared/languages.ts
405
+ var init_languages = __esm({
406
+ "src/shared/languages.ts"() {
276
407
  }
277
408
  });
278
409
 
279
- // src/parsing/queries/typescript.ts
280
- var typescriptQueries;
281
- var init_typescript = __esm({
282
- "src/parsing/queries/typescript.ts"() {
283
- typescriptQueries = `
284
- ;; Class declaration
410
+ // src/shared/detection.ts
411
+ function detectLanguage(filePath) {
412
+ const ext = filePath.slice(filePath.lastIndexOf("."));
413
+ return EXTENSION_MAP[ext] ?? null;
414
+ }
415
+ function getSupportedExtensions() {
416
+ return Object.keys(EXTENSION_MAP);
417
+ }
418
+ var EXTENSION_MAP;
419
+ var init_detection = __esm({
420
+ "src/shared/detection.ts"() {
421
+ init_languages();
422
+ EXTENSION_MAP = {
423
+ ".ts": "typescript" /* TypeScript */,
424
+ ".tsx": "typescript" /* TypeScript */,
425
+ ".mts": "typescript" /* TypeScript */,
426
+ ".cts": "typescript" /* TypeScript */,
427
+ ".js": "javascript" /* JavaScript */,
428
+ ".jsx": "javascript" /* JavaScript */,
429
+ ".mjs": "javascript" /* JavaScript */,
430
+ ".cjs": "javascript" /* JavaScript */,
431
+ ".py": "python" /* Python */,
432
+ ".pyi": "python" /* Python */,
433
+ ".java": "java" /* Java */,
434
+ ".go": "go" /* Go */,
435
+ ".c": "c" /* C */,
436
+ ".h": "c" /* C */,
437
+ ".cpp": "cpp" /* Cpp */,
438
+ ".cxx": "cpp" /* Cpp */,
439
+ ".cc": "cpp" /* Cpp */,
440
+ ".hpp": "cpp" /* Cpp */,
441
+ ".hxx": "cpp" /* Cpp */,
442
+ ".cs": "csharp" /* CSharp */,
443
+ ".rs": "rust" /* Rust */,
444
+ ".php": "php" /* PHP */,
445
+ ".kt": "kotlin" /* Kotlin */,
446
+ ".kts": "kotlin" /* Kotlin */,
447
+ ".rb": "ruby" /* Ruby */,
448
+ ".swift": "swift" /* Swift */,
449
+ ".dart": "dart" /* Dart */
450
+ };
451
+ }
452
+ });
453
+
454
+ // src/shared/index.ts
455
+ var init_shared = __esm({
456
+ "src/shared/index.ts"() {
457
+ init_languages();
458
+ init_detection();
459
+ }
460
+ });
461
+ function findBundledWasmDir() {
462
+ const fileDir = path32.dirname(fileURLToPath(import.meta.url));
463
+ const candidates = [
464
+ path32.join(fileDir, "wasm"),
465
+ // dist/index.js → dist/wasm/
466
+ path32.join(fileDir, "../wasm")
467
+ // dist/cli/main.js → dist/wasm/
468
+ ];
469
+ for (const candidate of candidates) {
470
+ if (existsSync(candidate)) return candidate;
471
+ }
472
+ return candidates[0];
473
+ }
474
+ function wasmPath(lang) {
475
+ const WASM_PACKAGE_MAP = {
476
+ ["typescript" /* TypeScript */]: "tree-sitter-typescript/tree-sitter-typescript.wasm",
477
+ ["javascript" /* JavaScript */]: "tree-sitter-javascript/tree-sitter-javascript.wasm",
478
+ ["python" /* Python */]: "tree-sitter-python/tree-sitter-python.wasm",
479
+ ["java" /* Java */]: "tree-sitter-java/tree-sitter-java.wasm",
480
+ ["go" /* Go */]: "tree-sitter-go/tree-sitter-go.wasm",
481
+ ["c" /* C */]: "tree-sitter-c/tree-sitter-c.wasm",
482
+ ["cpp" /* Cpp */]: "tree-sitter-cpp/tree-sitter-cpp.wasm",
483
+ ["csharp" /* CSharp */]: "tree-sitter-c-sharp/tree-sitter-c_sharp.wasm",
484
+ ["rust" /* Rust */]: "tree-sitter-rust/tree-sitter-rust.wasm",
485
+ ["php" /* PHP */]: "tree-sitter-php/tree-sitter-php.wasm",
486
+ ["ruby" /* Ruby */]: "tree-sitter-ruby/tree-sitter-ruby.wasm",
487
+ // These are optional dependencies; their packages may or may not include
488
+ // a WASM. If require.resolve fails we fall back to the bundled wasm/.
489
+ ["swift" /* Swift */]: "tree-sitter-swift/tree-sitter-swift.wasm",
490
+ ["kotlin" /* Kotlin */]: "tree-sitter-kotlin/tree-sitter-kotlin.wasm",
491
+ ["dart" /* Dart */]: "tree-sitter-dart/tree-sitter-dart.wasm"
492
+ };
493
+ const BUNDLED_WASM_MAP = {
494
+ ["swift" /* Swift */]: "tree-sitter-swift.wasm",
495
+ ["kotlin" /* Kotlin */]: "tree-sitter-kotlin.wasm",
496
+ ["dart" /* Dart */]: "tree-sitter-dart.wasm"
497
+ };
498
+ const relative = WASM_PACKAGE_MAP[lang];
499
+ if (relative) {
500
+ try {
501
+ return _require.resolve(relative);
502
+ } catch {
503
+ }
504
+ }
505
+ const bundled = BUNDLED_WASM_MAP[lang];
506
+ if (bundled) {
507
+ const bundledPath = path32.join(_bundledWasmDir, bundled);
508
+ if (existsSync(bundledPath)) return bundledPath;
509
+ }
510
+ return null;
511
+ }
512
+ async function initParser() {
513
+ if (!initPromise) {
514
+ initPromise = Parser.init();
515
+ }
516
+ return initPromise;
517
+ }
518
+ async function getLanguage(lang) {
519
+ if (languageCache.has(lang)) return languageCache.get(lang);
520
+ const path33 = wasmPath(lang);
521
+ if (!path33) {
522
+ languageCache.set(lang, null);
523
+ return null;
524
+ }
525
+ try {
526
+ await initParser();
527
+ const language = await Language.load(path33);
528
+ languageCache.set(lang, language);
529
+ return language;
530
+ } catch {
531
+ languageCache.set(lang, null);
532
+ return null;
533
+ }
534
+ }
535
+ async function getParser(lang) {
536
+ const language = await getLanguage(lang);
537
+ if (!language) return null;
538
+ let parser = parserCache.get(lang);
539
+ if (!parser) {
540
+ parser = new Parser();
541
+ parserCache.set(lang, parser);
542
+ }
543
+ parser.setLanguage(language);
544
+ return parser;
545
+ }
546
+ async function parseSource(lang, source) {
547
+ const parser = await getParser(lang);
548
+ if (!parser) return null;
549
+ return parser.parse(source);
550
+ }
551
+ async function isTreeSitterAvailable(lang) {
552
+ return await getLanguage(lang) !== null;
553
+ }
554
+ var _require, _bundledWasmDir, initPromise, languageCache, parserCache;
555
+ var init_parser_manager = __esm({
556
+ "src/parsing/parser-manager.ts"() {
557
+ init_shared();
558
+ _require = createRequire(import.meta.url);
559
+ _bundledWasmDir = findBundledWasmDir();
560
+ initPromise = null;
561
+ languageCache = /* @__PURE__ */ new Map();
562
+ parserCache = /* @__PURE__ */ new Map();
563
+ }
564
+ });
565
+ function getOrCompileQuery(language, querySource) {
566
+ let langMap = _queryCache.get(language);
567
+ if (!langMap) {
568
+ langMap = /* @__PURE__ */ new Map();
569
+ _queryCache.set(language, langMap);
570
+ }
571
+ let q = langMap.get(querySource);
572
+ if (!q) {
573
+ q = new Query(language, querySource);
574
+ langMap.set(querySource, q);
575
+ }
576
+ return q;
577
+ }
578
+ function runQuery(tree, language, querySource) {
579
+ const query = getOrCompileQuery(language, querySource);
580
+ const matches = query.matches(tree.rootNode);
581
+ const captures = [];
582
+ for (const match of matches) {
583
+ for (const capture of match.captures) {
584
+ captures.push({
585
+ name: capture.name,
586
+ node: capture.node,
587
+ text: capture.node.text
588
+ });
589
+ }
590
+ }
591
+ return captures;
592
+ }
593
+ function runQueryMatches(tree, language, querySource) {
594
+ const query = getOrCompileQuery(language, querySource);
595
+ const raw = query.matches(tree.rootNode);
596
+ return raw.map((m) => ({
597
+ patternIndex: m.patternIndex,
598
+ captures: m.captures.map((c) => ({
599
+ name: c.name,
600
+ node: c.node,
601
+ text: c.node.text
602
+ }))
603
+ }));
604
+ }
605
+ var _queryCache;
606
+ var init_query_runner = __esm({
607
+ "src/parsing/query-runner.ts"() {
608
+ _queryCache = /* @__PURE__ */ new WeakMap();
609
+ }
610
+ });
611
+
612
+ // src/parsing/queries/typescript.ts
613
+ var typescriptQueries;
614
+ var init_typescript = __esm({
615
+ "src/parsing/queries/typescript.ts"() {
616
+ typescriptQueries = `
617
+ ;; Class declaration
285
618
  (class_declaration
286
619
  name: (type_identifier) @def.class.name) @def.class
287
620
 
@@ -889,342 +1222,9 @@ var init_swift = __esm({
889
1222
 
890
1223
  ;; Property declaration
891
1224
  (property_declaration
892
- (pattern
893
- (simple_identifier) @def.property.name)) @def.property
894
- `;
895
- }
896
- });
897
-
898
- // src/observability/tracing.ts
899
- var tracing_exports = {};
900
- __export(tracing_exports, {
901
- SpanStatusCode: () => SpanStatusCode,
902
- context: () => context,
903
- getActiveTraceContext: () => getActiveTraceContext,
904
- getTracer: () => getTracer,
905
- initTracing: () => initTracing,
906
- isTracingEnabled: () => isTracingEnabled,
907
- sanitizeAttrs: () => sanitizeAttrs,
908
- shutdownTracing: () => shutdownTracing,
909
- trace: () => trace,
910
- withSpan: () => withSpan
911
- });
912
- function isTracingEnabled() {
913
- return process.env["CODE_INTEL_OTEL_ENABLED"] === "true";
914
- }
915
- function initTracing() {
916
- if (!isTracingEnabled()) return;
917
- if (_sdk) return;
918
- const endpoint = process.env["CODE_INTEL_OTEL_ENDPOINT"] ?? "http://localhost:4318";
919
- const serviceName = process.env["CODE_INTEL_OTEL_SERVICE"] ?? "code-intel";
920
- const deploymentEnv = process.env["CODE_INTEL_OTEL_ENV"] ?? process.env["NODE_ENV"] ?? "development";
921
- const exporter = new OTLPTraceExporter({ url: `${endpoint}/v1/traces` });
922
- _sdk = new NodeSDK({
923
- resource: resourceFromAttributes({
924
- [SEMRESATTRS_SERVICE_NAME]: serviceName,
925
- [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: deploymentEnv
926
- }),
927
- traceExporter: exporter,
928
- instrumentations: [
929
- getNodeAutoInstrumentations({
930
- // Disable noisy file-system instrumentation
931
- "@opentelemetry/instrumentation-fs": { enabled: false }
932
- })
933
- ]
934
- });
935
- _sdk.start();
936
- }
937
- async function shutdownTracing() {
938
- if (_sdk) {
939
- await _sdk.shutdown();
940
- _sdk = null;
941
- }
942
- }
943
- function getTracer() {
944
- return trace.getTracer(TRACER_NAME);
945
- }
946
- async function withSpan(name, attrs, fn) {
947
- return getTracer().startActiveSpan(name, { attributes: sanitizeAttrs(attrs) }, async (span) => {
948
- try {
949
- const result = await fn(span);
950
- span.setStatus({ code: SpanStatusCode.OK });
951
- return result;
952
- } catch (err) {
953
- span.setStatus({
954
- code: SpanStatusCode.ERROR,
955
- message: err instanceof Error ? err.message : String(err)
956
- });
957
- span.recordException(err);
958
- throw err;
959
- } finally {
960
- span.end();
961
- }
962
- });
963
- }
964
- function sanitizeAttrs(attrs) {
965
- const safe = {};
966
- for (const [k, v] of Object.entries(attrs)) {
967
- if (BLOCKED_ATTR_KEYS.test(k)) continue;
968
- safe[k] = v;
969
- }
970
- return safe;
971
- }
972
- function getActiveTraceContext() {
973
- const span = trace.getActiveSpan();
974
- if (!span) return { traceId: "", spanId: "" };
975
- const ctx = span.spanContext();
976
- return { traceId: ctx.traceId, spanId: ctx.spanId };
977
- }
978
- var _sdk, TRACER_NAME, BLOCKED_ATTR_KEYS;
979
- var init_tracing = __esm({
980
- "src/observability/tracing.ts"() {
981
- _sdk = null;
982
- TRACER_NAME = "code-intel";
983
- BLOCKED_ATTR_KEYS = /secret|password|token|key|auth|credential/i;
984
- }
985
- });
986
- function getActiveTraceCtx() {
987
- if (_getTraceCtx === "pending") return { traceId: "", spanId: "" };
988
- if (_getTraceCtx) return _getTraceCtx();
989
- _getTraceCtx = "pending";
990
- Promise.resolve().then(() => (init_tracing(), tracing_exports)).then((mod) => {
991
- _getTraceCtx = mod.getActiveTraceContext;
992
- }).catch(() => {
993
- _getTraceCtx = null;
994
- });
995
- return { traceId: "", spanId: "" };
996
- }
997
- var _getTraceCtx, SENSITIVE_KEYS, SENSITIVE_PATTERNS, SENSITIVE_KEYS_REGEX, Logger, logger_default;
998
- var init_logger = __esm({
999
- "src/shared/logger.ts"() {
1000
- _getTraceCtx = null;
1001
- SENSITIVE_KEYS = [
1002
- "password",
1003
- "passwd",
1004
- "pass",
1005
- "pwd",
1006
- "secret",
1007
- "secretkey",
1008
- "secret_key",
1009
- "secretaccesskey",
1010
- "accesskeyid",
1011
- "credentials",
1012
- "auth",
1013
- "authentication",
1014
- "login",
1015
- "api_key",
1016
- "apikey",
1017
- "api",
1018
- "access_key",
1019
- "access_token",
1020
- "accesskey",
1021
- "auth_key",
1022
- "auth_token",
1023
- "authkey",
1024
- "token",
1025
- "jwt",
1026
- "bearer_token",
1027
- "refresh_token",
1028
- "session_token",
1029
- "session_key",
1030
- "oauth_token",
1031
- "connection_string",
1032
- "conn_string",
1033
- "db_uri",
1034
- "db_url",
1035
- "database_url",
1036
- "mongodb_uri",
1037
- "mysql_uri",
1038
- "postgres_uri",
1039
- "sql_uri",
1040
- "db_username",
1041
- "db_password",
1042
- "db_host",
1043
- "db_port",
1044
- "db_name",
1045
- "encryption_key",
1046
- "crypto_key",
1047
- "private_key",
1048
- "public_key",
1049
- "ssl_key",
1050
- "ssh_key",
1051
- "pgp_key",
1052
- "rsa_key",
1053
- "aes_key",
1054
- "email",
1055
- "phone",
1056
- "telephone",
1057
- "mobile",
1058
- "ssn",
1059
- "social_security",
1060
- "credit_card",
1061
- "cc_number",
1062
- "card_number",
1063
- "cvv",
1064
- "expiry_date",
1065
- "birth_date",
1066
- "dob",
1067
- "address",
1068
- "zip_code",
1069
- "postal_code",
1070
- "bank_account",
1071
- "iban",
1072
- "swift_code",
1073
- "routing_number",
1074
- "tax_id",
1075
- "vat_number",
1076
- "financial_id",
1077
- "certificate",
1078
- "client_cert",
1079
- "server_cert",
1080
- "ca_cert",
1081
- "aws_key",
1082
- "aws_secret",
1083
- "azure_key",
1084
- "gcp_key",
1085
- "s3_key",
1086
- "cloudinary_key",
1087
- "stripe_key",
1088
- "paypal_key",
1089
- "twilio_key",
1090
- "app_secret",
1091
- "client_secret",
1092
- "consumer_secret",
1093
- "encryption_secret",
1094
- "master_key",
1095
- "root_password",
1096
- "admin_password",
1097
- "config_secret",
1098
- "env_secret",
1099
- "deploy_key",
1100
- "ci_key",
1101
- "session_id",
1102
- "cookie_secret",
1103
- "csrf_token",
1104
- "xsrf_token",
1105
- "license_key",
1106
- "product_key",
1107
- "serial_number",
1108
- "activation_code"
1109
- ];
1110
- SENSITIVE_PATTERNS = [
1111
- /(?:password|passwd|secret|api_key|access_token|auth_token|token)\s*[:=]\s*([^\s,]+)/gi,
1112
- /\b\d{16}\b/gi,
1113
- /\b\d{3}-\d{2}-\d{4}\b/gi,
1114
- /\b[A-Za-z0-9]{32}\b/gi,
1115
- /\b[A-Za-z0-9_-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b/gi,
1116
- /\b\d{10}\b/gi,
1117
- /\b[A-Za-z0-9]{64}\b/gi,
1118
- /(?:connection_string|db_uri|db_url|mongodb_uri)\s*[:=]\s*([^\s,]+)/gi,
1119
- /(?:apikey|api_key|auth_key)\s*[:=]\s*([^\s,]+)/gi,
1120
- /(?:bearer\s+)([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)/gi
1121
- ];
1122
- SENSITIVE_KEYS_REGEX = new RegExp(`^(${SENSITIVE_KEYS.join("|")})$`, "i");
1123
- Logger = class _Logger {
1124
- static instance = null;
1125
- static maskSensitiveData(value) {
1126
- if (typeof value === "string" && value.length > 5) {
1127
- const firstChar = value.at(0);
1128
- const lastChar = value.at(-1);
1129
- return firstChar + "*".repeat(value.length - 2) + lastChar;
1130
- }
1131
- return value;
1132
- }
1133
- static maskSensitive(message, args = []) {
1134
- const maskString = (input) => {
1135
- if (typeof input !== "string") return input;
1136
- return SENSITIVE_PATTERNS.reduce((str, pattern) => {
1137
- return str.replace(
1138
- pattern,
1139
- (match, value) => value ? match.replace(value, _Logger.maskSensitiveData(value)) : match
1140
- );
1141
- }, input);
1142
- };
1143
- const deepMask = (obj) => {
1144
- if (typeof obj === "string") return maskString(obj);
1145
- if (Array.isArray(obj)) return obj.map((item) => deepMask(item));
1146
- if (typeof obj === "object" && obj !== null) {
1147
- return Object.entries(obj).reduce(
1148
- (acc, [key, value]) => {
1149
- if (value === void 0) return acc;
1150
- const isSensitiveKey = SENSITIVE_KEYS_REGEX.test(key);
1151
- acc[key] = isSensitiveKey && typeof value === "string" ? _Logger.maskSensitiveData(value) : deepMask(value);
1152
- return acc;
1153
- },
1154
- {}
1155
- );
1156
- }
1157
- return obj;
1158
- };
1159
- return {
1160
- maskedMessage: maskString(message),
1161
- maskedArgs: args.map((arg) => deepMask(arg))
1162
- };
1163
- }
1164
- /** Global log directory: ~/.code-intel/logs */
1165
- static LOG_DIR = path31.join(os13.homedir(), ".code-intel", "logs");
1166
- static getLogger() {
1167
- if (!_Logger.instance) {
1168
- const isProduction = process.env.NODE_ENV === "production";
1169
- const logLevel = process.env.LOG_LEVEL ?? "info";
1170
- const transports = [];
1171
- transports.push(new winston.transports.Console());
1172
- if (!isProduction) {
1173
- try {
1174
- if (!fs24.existsSync(_Logger.LOG_DIR)) {
1175
- fs24.mkdirSync(_Logger.LOG_DIR, { recursive: true });
1176
- }
1177
- transports.push(
1178
- new DailyRotateFile({
1179
- filename: path31.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
1180
- datePattern: "YYYY-MM-DD",
1181
- maxSize: "20m",
1182
- maxFiles: "14d"
1183
- })
1184
- );
1185
- } catch {
1186
- }
1187
- }
1188
- _Logger.instance = winston.createLogger({
1189
- level: logLevel,
1190
- format: winston.format.combine(
1191
- winston.format.timestamp(),
1192
- winston.format.printf(({ timestamp, level, message, ...meta }) => {
1193
- const args = meta[/* @__PURE__ */ Symbol.for("splat")] || [];
1194
- const { maskedMessage, maskedArgs } = _Logger.maskSensitive(message, args);
1195
- const formattedArgs = maskedArgs.map(
1196
- (arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
1197
- );
1198
- const suffix = formattedArgs.length ? " " + formattedArgs.join(" ") : "";
1199
- let traceCtx = "";
1200
- try {
1201
- const { traceId, spanId } = getActiveTraceCtx();
1202
- if (traceId) traceCtx = ` [trace=${traceId} span=${spanId}]`;
1203
- } catch {
1204
- }
1205
- return `${timestamp} [${level.toUpperCase()}]${traceCtx}: ${maskedMessage}${suffix}`;
1206
- })
1207
- ),
1208
- transports
1209
- });
1210
- }
1211
- return _Logger.instance;
1212
- }
1213
- static info(message, ...args) {
1214
- _Logger.getLogger().info(message, ...args);
1215
- }
1216
- static warn(message, ...args) {
1217
- _Logger.getLogger().warn(message, ...args);
1218
- }
1219
- static error(message, ...args) {
1220
- _Logger.getLogger().error(message, ...args);
1221
- }
1222
- static debug(message, ...args) {
1223
- _Logger.getLogger().debug(message, ...args);
1224
- }
1225
- };
1226
- logger_default = Logger;
1227
- Logger.getLogger();
1225
+ (pattern
1226
+ (simple_identifier) @def.property.name)) @def.property
1227
+ `;
1228
1228
  }
1229
1229
  });
1230
1230
 
@@ -1961,7 +1961,7 @@ var init_parse_phase = __esm({
1961
1961
  const batch = filePaths.slice(i, i + CONCURRENCY);
1962
1962
  await Promise.all(batch.map(async (filePath) => {
1963
1963
  try {
1964
- const source = await fs24.promises.readFile(filePath, "utf-8");
1964
+ const source = await fs25.promises.readFile(filePath, "utf-8");
1965
1965
  context2.fileCache.set(filePath, source);
1966
1966
  } catch {
1967
1967
  }
@@ -1974,14 +1974,14 @@ var init_parse_phase = __esm({
1974
1974
  const lang = detectLanguage(filePath);
1975
1975
  if (!lang) {
1976
1976
  if (context2.verbose) {
1977
- const relativePath2 = path31.relative(context2.workspaceRoot, filePath);
1977
+ const relativePath2 = path32.relative(context2.workspaceRoot, filePath);
1978
1978
  logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
1979
1979
  }
1980
1980
  continue;
1981
1981
  }
1982
1982
  const source = context2.fileCache.get(filePath);
1983
1983
  if (!source) continue;
1984
- const relativePath = path31.relative(context2.workspaceRoot, filePath);
1984
+ const relativePath = path32.relative(context2.workspaceRoot, filePath);
1985
1985
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
1986
1986
  const fileNode = context2.graph.getNode(fileNodeId);
1987
1987
  if (fileNode) {
@@ -2221,11 +2221,11 @@ var init_resolve_phase = __esm({
2221
2221
  let heritageEdges = 0;
2222
2222
  const fileIndex = /* @__PURE__ */ new Map();
2223
2223
  for (const fp of filePaths) {
2224
- const rel = path31.relative(workspaceRoot, fp);
2224
+ const rel = path32.relative(workspaceRoot, fp);
2225
2225
  fileIndex.set(rel, fp);
2226
2226
  const noExt = rel.replace(/\.\w+$/, "");
2227
2227
  if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
2228
- const base = path31.basename(rel, path31.extname(rel));
2228
+ const base = path32.basename(rel, path32.extname(rel));
2229
2229
  if (!fileIndex.has(base)) fileIndex.set(base, fp);
2230
2230
  }
2231
2231
  const symbolIndex = /* @__PURE__ */ new Map();
@@ -2256,7 +2256,7 @@ var init_resolve_phase = __esm({
2256
2256
  for (const filePath of filePaths) {
2257
2257
  const lang = detectLanguage(filePath);
2258
2258
  if (!lang) continue;
2259
- const relativePath = path31.relative(workspaceRoot, filePath);
2259
+ const relativePath = path32.relative(workspaceRoot, filePath);
2260
2260
  const fileNodeId = generateNodeId("file", relativePath, relativePath);
2261
2261
  const source = fileCache.get(filePath);
2262
2262
  if (!source) continue;
@@ -2269,13 +2269,13 @@ var init_resolve_phase = __esm({
2269
2269
  let resolvedRelPath = null;
2270
2270
  if (cleaned.startsWith(".")) {
2271
2271
  const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
2272
- const fromDir = path31.dirname(relativePath);
2272
+ const fromDir = path32.dirname(relativePath);
2273
2273
  for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
2274
- const candidate = path31.join(fromDir, cleanedNoJs + ext);
2275
- const normalized = path31.normalize(candidate);
2274
+ const candidate = path32.join(fromDir, cleanedNoJs + ext);
2275
+ const normalized = path32.normalize(candidate);
2276
2276
  if (fileIndex.has(normalized)) {
2277
2277
  const absPath = fileIndex.get(normalized);
2278
- resolvedRelPath = path31.relative(workspaceRoot, absPath);
2278
+ resolvedRelPath = path32.relative(workspaceRoot, absPath);
2279
2279
  break;
2280
2280
  }
2281
2281
  }
@@ -2589,27 +2589,27 @@ __export(group_registry_exports, {
2589
2589
  saveSyncResult: () => saveSyncResult
2590
2590
  });
2591
2591
  function groupFile(name) {
2592
- return path31.join(GROUPS_DIR, `${name}.json`);
2592
+ return path32.join(GROUPS_DIR, `${name}.json`);
2593
2593
  }
2594
2594
  function loadGroup(name) {
2595
2595
  try {
2596
- return JSON.parse(fs24.readFileSync(groupFile(name), "utf-8"));
2596
+ return JSON.parse(fs25.readFileSync(groupFile(name), "utf-8"));
2597
2597
  } catch {
2598
2598
  return null;
2599
2599
  }
2600
2600
  }
2601
2601
  function saveGroup(group) {
2602
- fs24.mkdirSync(GROUPS_DIR, { recursive: true });
2603
- fs24.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
2602
+ fs25.mkdirSync(GROUPS_DIR, { recursive: true });
2603
+ fs25.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
2604
2604
  }
2605
2605
  function listGroups() {
2606
2606
  const groups = [];
2607
2607
  try {
2608
- for (const file of fs24.readdirSync(GROUPS_DIR)) {
2608
+ for (const file of fs25.readdirSync(GROUPS_DIR)) {
2609
2609
  if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
2610
2610
  try {
2611
2611
  const g = JSON.parse(
2612
- fs24.readFileSync(path31.join(GROUPS_DIR, file), "utf-8")
2612
+ fs25.readFileSync(path32.join(GROUPS_DIR, file), "utf-8")
2613
2613
  );
2614
2614
  groups.push(g);
2615
2615
  } catch {
@@ -2621,16 +2621,16 @@ function listGroups() {
2621
2621
  }
2622
2622
  function deleteGroup(name) {
2623
2623
  try {
2624
- fs24.unlinkSync(groupFile(name));
2624
+ fs25.unlinkSync(groupFile(name));
2625
2625
  } catch {
2626
2626
  }
2627
2627
  try {
2628
- fs24.unlinkSync(path31.join(GROUPS_DIR, `${name}.sync.json`));
2628
+ fs25.unlinkSync(path32.join(GROUPS_DIR, `${name}.sync.json`));
2629
2629
  } catch {
2630
2630
  }
2631
2631
  }
2632
2632
  function groupExists(name) {
2633
- return fs24.existsSync(groupFile(name));
2633
+ return fs25.existsSync(groupFile(name));
2634
2634
  }
2635
2635
  function addMember(groupName, member) {
2636
2636
  const group = loadGroup(groupName);
@@ -2656,16 +2656,16 @@ function removeMember(groupName, groupPath) {
2656
2656
  return group;
2657
2657
  }
2658
2658
  function saveSyncResult(result) {
2659
- fs24.mkdirSync(GROUPS_DIR, { recursive: true });
2660
- fs24.writeFileSync(
2661
- path31.join(GROUPS_DIR, `${result.groupName}.sync.json`),
2659
+ fs25.mkdirSync(GROUPS_DIR, { recursive: true });
2660
+ fs25.writeFileSync(
2661
+ path32.join(GROUPS_DIR, `${result.groupName}.sync.json`),
2662
2662
  JSON.stringify(result, null, 2) + "\n"
2663
2663
  );
2664
2664
  }
2665
2665
  function loadSyncResult(groupName) {
2666
2666
  try {
2667
2667
  return JSON.parse(
2668
- fs24.readFileSync(path31.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
2668
+ fs25.readFileSync(path32.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
2669
2669
  );
2670
2670
  } catch {
2671
2671
  return null;
@@ -2674,7 +2674,7 @@ function loadSyncResult(groupName) {
2674
2674
  var GROUPS_DIR;
2675
2675
  var init_group_registry = __esm({
2676
2676
  "src/multi-repo/group-registry.ts"() {
2677
- GROUPS_DIR = path31.join(os13.homedir(), ".code-intel", "groups");
2677
+ GROUPS_DIR = path32.join(os13.homedir(), ".code-intel", "groups");
2678
2678
  }
2679
2679
  });
2680
2680
 
@@ -2964,7 +2964,7 @@ __export(gql_parser_exports, {
2964
2964
  function isGQLParseError(v) {
2965
2965
  return v.type === "GQLParseError";
2966
2966
  }
2967
- function tokenize(input) {
2967
+ function tokenize2(input) {
2968
2968
  const tokens = [];
2969
2969
  let i = 0;
2970
2970
  const len = input.length;
@@ -3059,7 +3059,7 @@ function tokenize(input) {
3059
3059
  return tokens;
3060
3060
  }
3061
3061
  function parseGQL(input) {
3062
- const tokens = tokenize(input.trim());
3062
+ const tokens = tokenize2(input.trim());
3063
3063
  if (!Array.isArray(tokens)) return tokens;
3064
3064
  const parser = new Parser2(tokens);
3065
3065
  return parser.parse();
@@ -3852,7 +3852,7 @@ function isTestFile(filePath) {
3852
3852
  if (filePath.includes(".test.") || filePath.includes(".spec.")) return true;
3853
3853
  if (filePath.includes("_test.") || filePath.endsWith("_test.go")) return true;
3854
3854
  if (filePath.includes("__tests__")) return true;
3855
- const base = path31.basename(filePath);
3855
+ const base = path32.basename(filePath);
3856
3856
  if (base.startsWith("Test") && filePath.endsWith(".java")) return true;
3857
3857
  return false;
3858
3858
  }
@@ -3897,7 +3897,7 @@ function computeCoverage(graph, scope) {
3897
3897
  }
3898
3898
  const baseNameToTestFiles = /* @__PURE__ */ new Map();
3899
3899
  for (const testPath of testFilePaths) {
3900
- const base = path31.basename(testPath);
3900
+ const base = path32.basename(testPath);
3901
3901
  const stripped = base.replace(/\.test\.[^.]+$/, "").replace(/\.spec\.[^.]+$/, "").replace(/_test\.[^.]+$/, "").replace(/_test$/, "");
3902
3902
  const existing = baseNameToTestFiles.get(stripped) ?? [];
3903
3903
  existing.push(testPath);
@@ -3930,7 +3930,7 @@ function computeCoverage(graph, scope) {
3930
3930
  }
3931
3931
  }
3932
3932
  }
3933
- const nodeBase = path31.basename(node.filePath).replace(/\.[^.]+$/, "");
3933
+ const nodeBase = path32.basename(node.filePath).replace(/\.[^.]+$/, "");
3934
3934
  const matchingTestFiles = baseNameToTestFiles.get(nodeBase) ?? [];
3935
3935
  for (const tf of matchingTestFiles) {
3936
3936
  if (!testFiles.includes(tf)) testFiles.push(tf);
@@ -4000,7 +4000,7 @@ var init_secret_scanner = __esm({
4000
4000
  const ignorePatterns = [...options?.ignorePatterns ?? []];
4001
4001
  if (options?.workspaceRoot) {
4002
4002
  try {
4003
- const raw = fs24.readFileSync(path31.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
4003
+ const raw = fs25.readFileSync(path32.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
4004
4004
  for (const line of raw.split("\n")) {
4005
4005
  const trimmed = line.trim();
4006
4006
  if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
@@ -4288,10 +4288,10 @@ var init_codes = __esm({
4288
4288
  }
4289
4289
  });
4290
4290
  function secureMkdir(dir) {
4291
- fs24.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
4291
+ fs25.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
4292
4292
  if (process.platform !== "win32") {
4293
4293
  try {
4294
- fs24.chmodSync(dir, SECURE_DIR_MODE);
4294
+ fs25.chmodSync(dir, SECURE_DIR_MODE);
4295
4295
  } catch {
4296
4296
  }
4297
4297
  }
@@ -4299,17 +4299,17 @@ function secureMkdir(dir) {
4299
4299
  function secureChmodFile(file) {
4300
4300
  if (process.platform === "win32") return;
4301
4301
  try {
4302
- fs24.chmodSync(file, SECURE_FILE_MODE);
4302
+ fs25.chmodSync(file, SECURE_FILE_MODE);
4303
4303
  } catch {
4304
4304
  }
4305
4305
  }
4306
4306
  function tightenDbFiles(dir) {
4307
4307
  if (process.platform === "win32") return;
4308
- if (!fs24.existsSync(dir)) return;
4309
- for (const name of fs24.readdirSync(dir)) {
4308
+ if (!fs25.existsSync(dir)) return;
4309
+ for (const name of fs25.readdirSync(dir)) {
4310
4310
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
4311
4311
  try {
4312
- fs24.chmodSync(path31.join(dir, name), SECURE_FILE_MODE);
4312
+ fs25.chmodSync(path32.join(dir, name), SECURE_FILE_MODE);
4313
4313
  } catch {
4314
4314
  }
4315
4315
  }
@@ -4323,7 +4323,7 @@ var init_fs_secure = __esm({
4323
4323
  }
4324
4324
  });
4325
4325
  function getUsersDBPath() {
4326
- return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path31.join(os13.homedir(), ".code-intel", "users.db");
4326
+ return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path32.join(os13.homedir(), ".code-intel", "users.db");
4327
4327
  }
4328
4328
  function getOrCreateUsersDB() {
4329
4329
  if (!_usersDB) {
@@ -4339,9 +4339,9 @@ var init_users_db = __esm({
4339
4339
  UsersDB = class {
4340
4340
  db;
4341
4341
  constructor(dbPath) {
4342
- const dir = path31.dirname(dbPath);
4342
+ const dir = path32.dirname(dbPath);
4343
4343
  secureMkdir(dir);
4344
- this.db = new Database3(dbPath);
4344
+ this.db = new Database2(dbPath);
4345
4345
  this.db.pragma("journal_mode = WAL");
4346
4346
  this.db.pragma("foreign_keys = ON");
4347
4347
  this.createTables();
@@ -4616,7 +4616,7 @@ function getScryptN() {
4616
4616
  return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
4617
4617
  }
4618
4618
  function getSecretsPath() {
4619
- return process.env["CODE_INTEL_SECRETS_PATH"] ?? path31.join(os13.homedir(), ".code-intel", ".secrets");
4619
+ return process.env["CODE_INTEL_SECRETS_PATH"] ?? path32.join(os13.homedir(), ".code-intel", ".secrets");
4620
4620
  }
4621
4621
  function getMasterPassword() {
4622
4622
  const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
@@ -4647,8 +4647,8 @@ function decryptSecrets(encrypted) {
4647
4647
  return JSON.parse(plaintext.toString("utf8"));
4648
4648
  }
4649
4649
  function loadSecrets(secretsPath = getSecretsPath()) {
4650
- if (!fs24.existsSync(secretsPath)) return {};
4651
- const blob = fs24.readFileSync(secretsPath);
4650
+ if (!fs25.existsSync(secretsPath)) return {};
4651
+ const blob = fs25.readFileSync(secretsPath);
4652
4652
  return decryptSecrets(blob);
4653
4653
  }
4654
4654
  function getSecret(key, secretsPath = getSecretsPath()) {
@@ -5158,6 +5158,71 @@ function createKnowledgeGraph() {
5158
5158
  // src/graph/index.ts
5159
5159
  init_id_generator();
5160
5160
 
5161
+ // src/storage/schema.ts
5162
+ var NODE_TABLE_MAP = {
5163
+ file: "file_nodes",
5164
+ directory: "dir_nodes",
5165
+ function: "func_nodes",
5166
+ class: "class_nodes",
5167
+ interface: "iface_nodes",
5168
+ method: "method_nodes",
5169
+ constructor: "ctor_nodes",
5170
+ variable: "var_nodes",
5171
+ property: "prop_nodes",
5172
+ struct: "struct_nodes",
5173
+ enum: "enum_nodes",
5174
+ trait: "trait_nodes",
5175
+ namespace: "ns_nodes",
5176
+ module: "mod_nodes",
5177
+ type_alias: "type_nodes",
5178
+ constant: "const_nodes",
5179
+ route: "route_nodes",
5180
+ cluster: "cluster_nodes",
5181
+ flow: "flow_nodes",
5182
+ vulnerability: "vuln_nodes"
5183
+ };
5184
+ var ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
5185
+ function getCreateNodeTableDDL(tableName) {
5186
+ return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
5187
+ id STRING,
5188
+ name STRING,
5189
+ file_path STRING,
5190
+ start_line INT64,
5191
+ end_line INT64,
5192
+ exported BOOLEAN,
5193
+ content STRING,
5194
+ metadata STRING,
5195
+ PRIMARY KEY (id)
5196
+ )`;
5197
+ }
5198
+ function getCreateEdgeTableDDL() {
5199
+ const uniqueTables = ALL_NODE_TABLES;
5200
+ const fromToPairs = [];
5201
+ for (const from of uniqueTables) {
5202
+ for (const to of uniqueTables) {
5203
+ fromToPairs.push(`FROM ${from} TO ${to}`);
5204
+ }
5205
+ }
5206
+ return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
5207
+ ${fromToPairs.join(",\n ")},
5208
+ kind STRING,
5209
+ weight DOUBLE,
5210
+ label STRING
5211
+ )`];
5212
+ }
5213
+
5214
+ // src/graph/lazy-knowledge-graph.ts
5215
+ init_logger();
5216
+ Object.fromEntries(
5217
+ Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
5218
+ );
5219
+ function isLazyGraph(g) {
5220
+ return "lazy" in g && g.lazy === true;
5221
+ }
5222
+
5223
+ // src/graph/compact-knowledge-graph.ts
5224
+ init_logger();
5225
+
5161
5226
  // src/parsing/index.ts
5162
5227
  init_parser_manager();
5163
5228
  init_query_runner();
@@ -5215,7 +5280,7 @@ init_shared();
5215
5280
  init_shared();
5216
5281
  init_typescript();
5217
5282
  function resolveRelative(rawPath, fromFile, workspace) {
5218
- const fromDir = path31.dirname(fromFile);
5283
+ const fromDir = path32.dirname(fromFile);
5219
5284
  const cleaned = rawPath.replace(/['"]/g, "");
5220
5285
  const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
5221
5286
  const resolved = workspace.resolve(fromDir, cleaned);
@@ -5267,7 +5332,7 @@ var pythonModule = {
5267
5332
  resolveImport(rawPath, fromFile, workspace) {
5268
5333
  const cleaned = rawPath.replace(/['"]/g, "");
5269
5334
  const parts = cleaned.split(".");
5270
- const fromDir = path31.dirname(fromFile);
5335
+ const fromDir = path32.dirname(fromFile);
5271
5336
  const relPath = parts.join("/");
5272
5337
  for (const suffix of ["/__init__.py", ".py"]) {
5273
5338
  const r = workspace.resolve(fromDir, relPath + suffix);
@@ -5346,7 +5411,7 @@ var cModule = {
5346
5411
  inheritanceStrategy: "none",
5347
5412
  resolveImport(rawPath, fromFile, workspace) {
5348
5413
  const cleaned = rawPath.replace(/[<>"']/g, "");
5349
- const fromDir = path31.dirname(fromFile);
5414
+ const fromDir = path32.dirname(fromFile);
5350
5415
  return workspace.resolve(fromDir, cleaned);
5351
5416
  },
5352
5417
  isExported(_node) {
@@ -5369,7 +5434,7 @@ var cppModule = {
5369
5434
  inheritanceStrategy: "depth-first",
5370
5435
  resolveImport(rawPath, fromFile, workspace) {
5371
5436
  const cleaned = rawPath.replace(/[<>"']/g, "");
5372
- const fromDir = path31.dirname(fromFile);
5437
+ const fromDir = path32.dirname(fromFile);
5373
5438
  return workspace.resolve(fromDir, cleaned);
5374
5439
  },
5375
5440
  isExported(_node) {
@@ -5531,7 +5596,7 @@ var dartModule = {
5531
5596
  const pkg = cleaned.replace("package:", "");
5532
5597
  return workspace.findByPackage(pkg);
5533
5598
  }
5534
- const fromDir = path31.dirname(fromFile);
5599
+ const fromDir = path32.dirname(fromFile);
5535
5600
  return workspace.resolve(fromDir, cleaned);
5536
5601
  },
5537
5602
  isExported(node) {
@@ -5886,25 +5951,25 @@ function validateDAG(phases) {
5886
5951
  const visiting = /* @__PURE__ */ new Set();
5887
5952
  const visited = /* @__PURE__ */ new Set();
5888
5953
  const phaseMap = new Map(phases.map((p) => [p.name, p]));
5889
- function dfs(name, path32) {
5954
+ function dfs(name, path33) {
5890
5955
  if (visiting.has(name)) {
5891
- const cycleStart = path32.indexOf(name);
5892
- const cycle = path32.slice(cycleStart).concat(name);
5956
+ const cycleStart = path33.indexOf(name);
5957
+ const cycle = path33.slice(cycleStart).concat(name);
5893
5958
  errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
5894
5959
  return true;
5895
5960
  }
5896
5961
  if (visited.has(name)) return false;
5897
5962
  visiting.add(name);
5898
- path32.push(name);
5963
+ path33.push(name);
5899
5964
  const phase = phaseMap.get(name);
5900
5965
  if (phase) {
5901
5966
  for (const dep of phase.dependencies) {
5902
- if (dfs(dep, path32)) return true;
5967
+ if (dfs(dep, path33)) return true;
5903
5968
  }
5904
5969
  }
5905
5970
  visiting.delete(name);
5906
5971
  visited.add(name);
5907
- path32.pop();
5972
+ path33.pop();
5908
5973
  return false;
5909
5974
  }
5910
5975
  for (const phase of phases) {
@@ -6032,6 +6097,7 @@ ${errors.map((e) => e.message).join("\n")}`);
6032
6097
  for (const phase of sorted) {
6033
6098
  context2.onProgress?.(phase.name, "running");
6034
6099
  const phaseStart = Date.now();
6100
+ const memBefore = context2.profile ? Math.round(process.memoryUsage().heapUsed / 1024 / 1024) : void 0;
6035
6101
  const runPhase = async () => {
6036
6102
  const depResults = /* @__PURE__ */ new Map();
6037
6103
  for (const dep of phase.dependencies) {
@@ -6055,6 +6121,10 @@ ${errors.map((e) => e.message).join("\n")}`);
6055
6121
  }
6056
6122
  const durationSec = (Date.now() - phaseStart) / 1e3;
6057
6123
  pipelinePhaseDurationSeconds.observe({ phase: phase.name, status: result.status }, durationSec);
6124
+ if (memBefore !== void 0) {
6125
+ result.memoryBeforeMB = memBefore;
6126
+ result.memoryAfterMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
6127
+ }
6058
6128
  results.set(phase.name, result);
6059
6129
  context2.onProgress?.(phase.name, result.status);
6060
6130
  if (result.status === "failed") {
@@ -6065,7 +6135,9 @@ ${errors.map((e) => e.message).join("\n")}`);
6065
6135
  const result = {
6066
6136
  status: "failed",
6067
6137
  duration: Date.now() - phaseStart,
6068
- message: err instanceof Error ? err.message : String(err)
6138
+ message: err instanceof Error ? err.message : String(err),
6139
+ memoryBeforeMB: memBefore,
6140
+ memoryAfterMB: memBefore !== void 0 ? Math.round(process.memoryUsage().heapUsed / 1024 / 1024) : void 0
6069
6141
  };
6070
6142
  pipelinePhaseDurationSeconds.observe({ phase: phase.name, status: "failed" }, (Date.now() - phaseStart) / 1e3);
6071
6143
  results.set(phase.name, result);
@@ -6117,7 +6189,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
6117
6189
  ]);
6118
6190
  function loadIgnorePatterns(workspaceRoot) {
6119
6191
  try {
6120
- const raw = fs24.readFileSync(path31.join(workspaceRoot, ".codeintelignore"), "utf-8");
6192
+ const raw = fs25.readFileSync(path32.join(workspaceRoot, ".codeintelignore"), "utf-8");
6121
6193
  const extras = /* @__PURE__ */ new Set();
6122
6194
  for (const line of raw.split("\n")) {
6123
6195
  const trimmed = line.trim();
@@ -6141,7 +6213,7 @@ var scanPhase = {
6141
6213
  function walk(dir) {
6142
6214
  let entries;
6143
6215
  try {
6144
- entries = fs24.readdirSync(dir, { withFileTypes: true });
6216
+ entries = fs25.readdirSync(dir, { withFileTypes: true });
6145
6217
  } catch {
6146
6218
  return;
6147
6219
  }
@@ -6150,15 +6222,15 @@ var scanPhase = {
6150
6222
  if (entry.name.startsWith(".")) continue;
6151
6223
  if (IGNORED_DIRS.has(entry.name)) continue;
6152
6224
  if (extraIgnore.has(entry.name)) continue;
6153
- walk(path31.join(dir, entry.name));
6225
+ walk(path32.join(dir, entry.name));
6154
6226
  } else if (entry.isFile()) {
6155
6227
  const name = entry.name;
6156
6228
  if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
6157
- const ext = path31.extname(name);
6229
+ const ext = path32.extname(name);
6158
6230
  if (!extensions.has(ext)) continue;
6159
- const fullPath = path31.join(dir, name);
6231
+ const fullPath = path32.join(dir, name);
6160
6232
  try {
6161
- const stat = fs24.statSync(fullPath);
6233
+ const stat = fs25.statSync(fullPath);
6162
6234
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
6163
6235
  } catch {
6164
6236
  continue;
@@ -6185,20 +6257,20 @@ var structurePhase = {
6185
6257
  const dirs = /* @__PURE__ */ new Set();
6186
6258
  let structDone = 0;
6187
6259
  for (const filePath of context2.filePaths) {
6188
- const relativePath = path31.relative(context2.workspaceRoot, filePath);
6260
+ const relativePath = path32.relative(context2.workspaceRoot, filePath);
6189
6261
  const lang = detectLanguage(filePath);
6190
6262
  context2.graph.addNode({
6191
6263
  id: generateNodeId("file", relativePath, relativePath),
6192
6264
  kind: "file",
6193
- name: path31.basename(filePath),
6265
+ name: path32.basename(filePath),
6194
6266
  filePath: relativePath,
6195
6267
  metadata: lang ? { language: lang } : void 0
6196
6268
  });
6197
- let dir = path31.dirname(relativePath);
6269
+ let dir = path32.dirname(relativePath);
6198
6270
  while (dir && dir !== "." && dir !== "") {
6199
6271
  if (dirs.has(dir)) break;
6200
6272
  dirs.add(dir);
6201
- dir = path31.dirname(dir);
6273
+ dir = path32.dirname(dir);
6202
6274
  }
6203
6275
  structDone++;
6204
6276
  context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
@@ -6207,7 +6279,7 @@ var structurePhase = {
6207
6279
  context2.graph.addNode({
6208
6280
  id: generateNodeId("directory", dir, dir),
6209
6281
  kind: "directory",
6210
- name: path31.basename(dir),
6282
+ name: path32.basename(dir),
6211
6283
  filePath: dir
6212
6284
  });
6213
6285
  }
@@ -6318,22 +6390,22 @@ var flowPhase = {
6318
6390
  const queue = [{ nodeId: ep.id, path: [ep.id] }];
6319
6391
  const visited = /* @__PURE__ */ new Set();
6320
6392
  while (queue.length > 0 && flowCount < maxFlows) {
6321
- const { nodeId, path: path32 } = queue.shift();
6322
- if (path32.length > maxDepth) continue;
6393
+ const { nodeId, path: path33 } = queue.shift();
6394
+ if (path33.length > maxDepth) continue;
6323
6395
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
6324
- if (callEdges.length === 0 && path32.length >= 3) {
6396
+ if (callEdges.length === 0 && path33.length >= 3) {
6325
6397
  const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
6326
6398
  graph.addNode({
6327
6399
  id: flowId,
6328
6400
  kind: "flow",
6329
6401
  name: `${ep.name} flow ${flowCount}`,
6330
6402
  filePath: ep.filePath,
6331
- metadata: { steps: path32, entryPoint: ep.name }
6403
+ metadata: { steps: path33, entryPoint: ep.name }
6332
6404
  });
6333
- for (let i = 0; i < path32.length; i++) {
6405
+ for (let i = 0; i < path33.length; i++) {
6334
6406
  graph.addEdge({
6335
- id: generateEdgeId(path32[i], flowId, `step_of_${i}`),
6336
- source: path32[i],
6407
+ id: generateEdgeId(path33[i], flowId, `step_of_${i}`),
6408
+ source: path33[i],
6337
6409
  target: flowId,
6338
6410
  kind: "step_of",
6339
6411
  weight: 1,
@@ -6346,7 +6418,7 @@ var flowPhase = {
6346
6418
  for (const edge of callEdges) {
6347
6419
  if (visited.has(edge.target)) continue;
6348
6420
  visited.add(edge.target);
6349
- queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
6421
+ queue.push({ nodeId: edge.target, path: [...path33, edge.target] });
6350
6422
  }
6351
6423
  }
6352
6424
  }
@@ -6364,7 +6436,7 @@ var LLMGovernanceLogger = class {
6364
6436
  }
6365
6437
  /** Path to the JSONL log file. */
6366
6438
  getLogPath() {
6367
- return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path31.join(os13.homedir(), ".code-intel", "llm-governance.jsonl");
6439
+ return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path32.join(os13.homedir(), ".code-intel", "llm-governance.jsonl");
6368
6440
  }
6369
6441
  /**
6370
6442
  * Append an entry to the governance log.
@@ -6380,8 +6452,8 @@ var LLMGovernanceLogger = class {
6380
6452
  ...entry
6381
6453
  };
6382
6454
  const logPath = this.getLogPath();
6383
- fs24.mkdirSync(path31.dirname(logPath), { recursive: true });
6384
- fs24.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
6455
+ fs25.mkdirSync(path32.dirname(logPath), { recursive: true });
6456
+ fs25.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
6385
6457
  } catch {
6386
6458
  }
6387
6459
  }
@@ -6391,7 +6463,7 @@ var LLMGovernanceLogger = class {
6391
6463
  */
6392
6464
  readLog(limit = 100) {
6393
6465
  try {
6394
- const raw = fs24.readFileSync(this.getLogPath(), "utf-8");
6466
+ const raw = fs25.readFileSync(this.getLogPath(), "utf-8");
6395
6467
  const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
6396
6468
  return lines.map((l) => JSON.parse(l));
6397
6469
  } catch {
@@ -6401,6 +6473,9 @@ var LLMGovernanceLogger = class {
6401
6473
  };
6402
6474
  var governanceLogger = new LLMGovernanceLogger();
6403
6475
 
6476
+ // src/pipeline/phases/summarize-phase.ts
6477
+ init_logger();
6478
+
6404
6479
  // src/pipeline/workers/parse-phase-parallel.ts
6405
6480
  init_shared();
6406
6481
  init_id_generator();
@@ -6535,17 +6610,17 @@ function traceFlow(entryId, graph, maxDepth = 10, maxBranching = 4) {
6535
6610
  const queue = [{ nodeId: entryId, path: [entryId] }];
6536
6611
  const visited = /* @__PURE__ */ new Set();
6537
6612
  while (queue.length > 0 && flows.length < maxFlows) {
6538
- const { nodeId, path: path32 } = queue.shift();
6539
- if (path32.length > maxDepth) continue;
6613
+ const { nodeId, path: path33 } = queue.shift();
6614
+ if (path33.length > maxDepth) continue;
6540
6615
  const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
6541
- if (callEdges.length === 0 && path32.length >= 3) {
6542
- flows.push({ entryPointId: entryId, steps: [...path32] });
6616
+ if (callEdges.length === 0 && path33.length >= 3) {
6617
+ flows.push({ entryPointId: entryId, steps: [...path33] });
6543
6618
  continue;
6544
6619
  }
6545
6620
  for (const edge of callEdges) {
6546
6621
  if (visited.has(edge.target)) continue;
6547
6622
  visited.add(edge.target);
6548
- queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
6623
+ queue.push({ nodeId: edge.target, path: [...path33, edge.target] });
6549
6624
  }
6550
6625
  }
6551
6626
  }
@@ -6646,7 +6721,7 @@ var VectorIndex = class {
6646
6721
  this.sqlitePath = sqlitePath;
6647
6722
  }
6648
6723
  async init() {
6649
- this.db = new Database3(this.sqlitePath);
6724
+ this.db = new Database2(this.sqlitePath);
6650
6725
  this.db.pragma("journal_mode = WAL");
6651
6726
  this.db.exec(`
6652
6727
  CREATE TABLE IF NOT EXISTS ${EMBED_TABLE} (
@@ -6777,9 +6852,9 @@ function siftDown(arr, i, score) {
6777
6852
  }
6778
6853
  init_embedder();
6779
6854
  async function hybridSearch(graph, query, limit, options = {}) {
6780
- const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
6781
- const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
6782
- const hasVectorDb = Boolean(vectorDbPath && fs24.existsSync(vectorDbPath));
6855
+ const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
6856
+ const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
6857
+ const hasVectorDb = Boolean(vectorDbPath && fs25.existsSync(vectorDbPath));
6783
6858
  if (!hasVectorDb) {
6784
6859
  const bm25Results2 = await bm25Promise;
6785
6860
  return {
@@ -6828,16 +6903,282 @@ async function runVectorSearch(vectorDbPath, query, topK) {
6828
6903
  return null;
6829
6904
  }
6830
6905
  }
6906
+
6907
+ // src/search/bm25-index.ts
6908
+ init_logger();
6909
+ var K1 = 1.2;
6910
+ var B = 0.75;
6911
+ function tokenize(text) {
6912
+ return text.toLowerCase().split(/[\s\-_./\\:(){}[\]<>,"'`~!@#$%^&*+=|;?]+/).filter((t) => t.length >= 2 && t.length <= 64);
6913
+ }
6914
+ function nodeToDoc(node) {
6915
+ return [
6916
+ node.name,
6917
+ node.kind,
6918
+ node.filePath,
6919
+ (node.content ?? "").slice(0, 1e3)
6920
+ ].join(" ");
6921
+ }
6922
+ var Bm25Index = class {
6923
+ constructor(dbPath) {
6924
+ this.dbPath = dbPath;
6925
+ }
6926
+ dbPath;
6927
+ /** In-memory inverted index (populated after `load()`). */
6928
+ invertedIndex = /* @__PURE__ */ new Map();
6929
+ docLengths = /* @__PURE__ */ new Map();
6930
+ nodeMeta = /* @__PURE__ */ new Map();
6931
+ avgdl = 1;
6932
+ docCount = 0;
6933
+ _loaded = false;
6934
+ get isLoaded() {
6935
+ return this._loaded;
6936
+ }
6937
+ // ── Build ───────────────────────────────────────────────────────────────────
6938
+ /**
6939
+ * Build the inverted index from a KnowledgeGraph and persist to SQLite.
6940
+ * Called once at analysis time after the main pipeline completes.
6941
+ */
6942
+ build(graph) {
6943
+ const nodeTermFreqs = /* @__PURE__ */ new Map();
6944
+ const docLengths = /* @__PURE__ */ new Map();
6945
+ const nodeMeta = /* @__PURE__ */ new Map();
6946
+ for (const node of graph.allNodes()) {
6947
+ if (["directory", "cluster", "flow"].includes(node.kind)) continue;
6948
+ const terms = tokenize(nodeToDoc(node));
6949
+ const tf = /* @__PURE__ */ new Map();
6950
+ for (const t of terms) tf.set(t, (tf.get(t) ?? 0) + 1);
6951
+ nodeTermFreqs.set(node.id, tf);
6952
+ docLengths.set(node.id, terms.length);
6953
+ nodeMeta.set(node.id, {
6954
+ name: node.name,
6955
+ kind: node.kind,
6956
+ filePath: node.filePath,
6957
+ snippet: node.content?.slice(0, 200)
6958
+ });
6959
+ }
6960
+ const docCount = nodeTermFreqs.size;
6961
+ const totalLen = [...docLengths.values()].reduce((a, b) => a + b, 0);
6962
+ const avgdl = docCount > 0 ? totalLen / docCount : 1;
6963
+ const invertedIndex = /* @__PURE__ */ new Map();
6964
+ for (const [nodeId, tf] of nodeTermFreqs) {
6965
+ for (const [term, count] of tf) {
6966
+ let postings = invertedIndex.get(term);
6967
+ if (!postings) {
6968
+ postings = [];
6969
+ invertedIndex.set(term, postings);
6970
+ }
6971
+ postings.push({ nodeId, tf: count });
6972
+ }
6973
+ }
6974
+ fs25.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
6975
+ for (const f of [this.dbPath, `${this.dbPath}-shm`, `${this.dbPath}-wal`]) {
6976
+ try {
6977
+ if (fs25.existsSync(f)) fs25.unlinkSync(f);
6978
+ } catch {
6979
+ }
6980
+ }
6981
+ const db = new Database2(this.dbPath);
6982
+ db.pragma("journal_mode = WAL");
6983
+ db.exec(`
6984
+ CREATE TABLE bm25_index (term TEXT PRIMARY KEY, postings TEXT NOT NULL);
6985
+ CREATE TABLE bm25_doclen (node_id TEXT PRIMARY KEY, doclen INTEGER NOT NULL);
6986
+ CREATE TABLE bm25_nodemeta(node_id TEXT PRIMARY KEY, name TEXT, kind TEXT, file_path TEXT, snippet TEXT);
6987
+ CREATE TABLE bm25_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
6988
+ `);
6989
+ const insPosting = db.prepare("INSERT OR REPLACE INTO bm25_index VALUES (?, ?)");
6990
+ const insDoclen = db.prepare("INSERT OR REPLACE INTO bm25_doclen VALUES (?, ?)");
6991
+ const insNodeMeta = db.prepare("INSERT OR REPLACE INTO bm25_nodemeta VALUES (?, ?, ?, ?, ?)");
6992
+ const insMeta = db.prepare("INSERT OR REPLACE INTO bm25_meta VALUES (?, ?)");
6993
+ db.transaction(() => {
6994
+ for (const [term, postings] of invertedIndex) {
6995
+ insPosting.run(term, JSON.stringify(postings));
6996
+ }
6997
+ for (const [nodeId, len] of docLengths) {
6998
+ insDoclen.run(nodeId, len);
6999
+ }
7000
+ for (const [nodeId, meta] of nodeMeta) {
7001
+ insNodeMeta.run(nodeId, meta.name, meta.kind, meta.filePath, meta.snippet ?? null);
7002
+ }
7003
+ insMeta.run("avgdl", String(avgdl));
7004
+ insMeta.run("docCount", String(docCount));
7005
+ })();
7006
+ db.close();
7007
+ logger_default.info(` [bm25] Index built: ${invertedIndex.size} terms, ${docCount} documents`);
7008
+ }
7009
+ // ── Load into memory ────────────────────────────────────────────────────────
7010
+ /**
7011
+ * Load the full inverted index into memory.
7012
+ * Called once on `serve` startup.
7013
+ */
7014
+ load() {
7015
+ if (!fs25.existsSync(this.dbPath)) return;
7016
+ const db = new Database2(this.dbPath, { readonly: true });
7017
+ try {
7018
+ const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
7019
+ this.avgdl = parseFloat(getMeta.get("avgdl")?.value ?? "1");
7020
+ this.docCount = parseInt(getMeta.get("docCount")?.value ?? "0", 10);
7021
+ this.invertedIndex.clear();
7022
+ const postingRows = db.prepare("SELECT term, postings FROM bm25_index").all();
7023
+ for (const row of postingRows) {
7024
+ this.invertedIndex.set(row.term, JSON.parse(row.postings));
7025
+ }
7026
+ this.docLengths.clear();
7027
+ const dlRows = db.prepare("SELECT node_id, doclen FROM bm25_doclen").all();
7028
+ for (const row of dlRows) {
7029
+ this.docLengths.set(row.node_id, row.doclen);
7030
+ }
7031
+ this.nodeMeta.clear();
7032
+ const metaRows = db.prepare("SELECT node_id, name, kind, file_path, snippet FROM bm25_nodemeta").all();
7033
+ for (const row of metaRows) {
7034
+ this.nodeMeta.set(row.node_id, {
7035
+ name: row.name,
7036
+ kind: row.kind,
7037
+ filePath: row.file_path,
7038
+ snippet: row.snippet ?? void 0
7039
+ });
7040
+ }
7041
+ this._loaded = true;
7042
+ logger_default.info(` [bm25] Index loaded (${this.invertedIndex.size} terms)`);
7043
+ } finally {
7044
+ db.close();
7045
+ }
7046
+ }
7047
+ // ── Search ──────────────────────────────────────────────────────────────────
7048
+ /**
7049
+ * BM25 search. LIMIT pushdown: scores only the posting lists for query terms,
7050
+ * then partial-sorts to return only the top `limit` results.
7051
+ */
7052
+ search(query, limit) {
7053
+ if (!this._loaded || this.invertedIndex.size === 0) return [];
7054
+ const queryTerms = [...new Set(tokenize(query))];
7055
+ if (queryTerms.length === 0) return [];
7056
+ const scores = /* @__PURE__ */ new Map();
7057
+ const N = this.docCount;
7058
+ const avgdl = this.avgdl;
7059
+ for (const term of queryTerms) {
7060
+ const postings = this.invertedIndex.get(term);
7061
+ if (!postings) continue;
7062
+ const df = postings.length;
7063
+ const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
7064
+ for (const { nodeId, tf } of postings) {
7065
+ const dl = this.docLengths.get(nodeId) ?? avgdl;
7066
+ const score = idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (dl / avgdl)));
7067
+ scores.set(nodeId, (scores.get(nodeId) ?? 0) + score);
7068
+ }
7069
+ }
7070
+ const entries = [...scores.entries()];
7071
+ entries.sort((a, b) => b[1] - a[1]);
7072
+ const topK = entries.slice(0, limit);
7073
+ return topK.map(([nodeId, score]) => {
7074
+ const meta = this.nodeMeta.get(nodeId);
7075
+ return {
7076
+ nodeId,
7077
+ name: meta?.name ?? nodeId,
7078
+ kind: meta?.kind ?? "unknown",
7079
+ filePath: meta?.filePath ?? "",
7080
+ score,
7081
+ snippet: meta?.snippet
7082
+ };
7083
+ });
7084
+ }
7085
+ // ── Incremental update ──────────────────────────────────────────────────────
7086
+ /**
7087
+ * Incrementally update index for a set of changed/added nodes.
7088
+ * Only terms that overlap with the changed nodes are rewritten.
7089
+ * Works even if `load()` was not called (reads affected terms directly from DB).
7090
+ */
7091
+ updateNodes(nodes) {
7092
+ if (!fs25.existsSync(this.dbPath)) return;
7093
+ if (nodes.length === 0) return;
7094
+ const changedIds = new Set(nodes.map((n) => n.id));
7095
+ const newTermFreqs = /* @__PURE__ */ new Map();
7096
+ for (const node of nodes) {
7097
+ if (["directory", "cluster", "flow"].includes(node.kind)) continue;
7098
+ const terms = tokenize(nodeToDoc(node));
7099
+ const tf = /* @__PURE__ */ new Map();
7100
+ for (const t of terms) tf.set(t, (tf.get(t) ?? 0) + 1);
7101
+ newTermFreqs.set(node.id, tf);
7102
+ }
7103
+ const newTermSet = new Set([...newTermFreqs.values()].flatMap((m) => [...m.keys()]));
7104
+ const db = new Database2(this.dbPath);
7105
+ db.pragma("journal_mode = WAL");
7106
+ const termsToRewrite = /* @__PURE__ */ new Map();
7107
+ for (const term of newTermSet) {
7108
+ const row = db.prepare("SELECT postings FROM bm25_index WHERE term = ?").get(term);
7109
+ const existing = row ? JSON.parse(row.postings) : [];
7110
+ termsToRewrite.set(term, existing.filter((p) => !changedIds.has(p.nodeId)));
7111
+ }
7112
+ for (const nodeId of changedIds) {
7113
+ const rows = db.prepare("SELECT term, postings FROM bm25_index WHERE postings LIKE ?").all(`%${nodeId}%`);
7114
+ for (const row of rows) {
7115
+ if (termsToRewrite.has(row.term)) continue;
7116
+ const postings = JSON.parse(row.postings);
7117
+ if (postings.some((p) => changedIds.has(p.nodeId))) {
7118
+ termsToRewrite.set(row.term, postings.filter((p) => !changedIds.has(p.nodeId)));
7119
+ }
7120
+ }
7121
+ }
7122
+ for (const [nodeId, tf] of newTermFreqs) {
7123
+ for (const [term, count] of tf) {
7124
+ const postings = termsToRewrite.get(term) ?? [];
7125
+ postings.push({ nodeId, tf: count });
7126
+ termsToRewrite.set(term, postings);
7127
+ }
7128
+ }
7129
+ const upsertPosting = db.prepare("INSERT OR REPLACE INTO bm25_index VALUES (?, ?)");
7130
+ const upsertDoclen = db.prepare("INSERT OR REPLACE INTO bm25_doclen VALUES (?, ?)");
7131
+ const upsertNodeMeta = db.prepare("INSERT OR REPLACE INTO bm25_nodemeta VALUES (?, ?, ?, ?, ?)");
7132
+ db.transaction(() => {
7133
+ for (const [term, postings] of termsToRewrite) {
7134
+ if (postings.length === 0) {
7135
+ db.prepare("DELETE FROM bm25_index WHERE term = ?").run(term);
7136
+ } else {
7137
+ upsertPosting.run(term, JSON.stringify(postings));
7138
+ }
7139
+ }
7140
+ for (const node of nodes) {
7141
+ const terms = tokenize(nodeToDoc(node));
7142
+ upsertDoclen.run(node.id, terms.length);
7143
+ upsertNodeMeta.run(node.id, node.name, node.kind, node.filePath, node.content?.slice(0, 200) ?? null);
7144
+ }
7145
+ })();
7146
+ db.close();
7147
+ if (this._loaded) {
7148
+ for (const [term, postings] of termsToRewrite) {
7149
+ if (postings.length === 0) this.invertedIndex.delete(term);
7150
+ else this.invertedIndex.set(term, postings);
7151
+ }
7152
+ for (const node of nodes) {
7153
+ const terms = tokenize(nodeToDoc(node));
7154
+ this.docLengths.set(node.id, terms.length);
7155
+ this.nodeMeta.set(node.id, {
7156
+ name: node.name,
7157
+ kind: node.kind,
7158
+ filePath: node.filePath,
7159
+ snippet: node.content?.slice(0, 200)
7160
+ });
7161
+ }
7162
+ }
7163
+ }
7164
+ };
7165
+ function getBm25DbPath(workspaceRoot) {
7166
+ return path32.join(workspaceRoot, ".code-intel", "bm25.db");
7167
+ }
6831
7168
  var DbManager = class {
6832
7169
  db = null;
6833
7170
  conn = null;
6834
7171
  dbPath;
6835
- constructor(dbPath) {
7172
+ readOnly;
7173
+ constructor(dbPath, readOnly = false) {
6836
7174
  this.dbPath = dbPath;
7175
+ this.readOnly = readOnly;
6837
7176
  }
6838
7177
  async init() {
6839
- fs24.mkdirSync(path31.dirname(this.dbPath), { recursive: true });
6840
- this.db = new Database(this.dbPath);
7178
+ if (!this.readOnly) {
7179
+ fs25.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
7180
+ }
7181
+ this.db = new Database(this.dbPath, 0, true, this.readOnly);
6841
7182
  await this.db.init();
6842
7183
  this.conn = new Connection(this.db);
6843
7184
  await this.conn.init();
@@ -6872,61 +7213,8 @@ var DbManager = class {
6872
7213
  return this.conn !== null;
6873
7214
  }
6874
7215
  };
6875
-
6876
- // src/storage/schema.ts
6877
- var NODE_TABLE_MAP = {
6878
- file: "file_nodes",
6879
- directory: "dir_nodes",
6880
- function: "func_nodes",
6881
- class: "class_nodes",
6882
- interface: "iface_nodes",
6883
- method: "method_nodes",
6884
- constructor: "ctor_nodes",
6885
- variable: "var_nodes",
6886
- property: "prop_nodes",
6887
- struct: "struct_nodes",
6888
- enum: "enum_nodes",
6889
- trait: "trait_nodes",
6890
- namespace: "ns_nodes",
6891
- module: "mod_nodes",
6892
- type_alias: "type_nodes",
6893
- constant: "const_nodes",
6894
- route: "route_nodes",
6895
- cluster: "cluster_nodes",
6896
- flow: "flow_nodes",
6897
- vulnerability: "vuln_nodes"
6898
- };
6899
- var ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
6900
- function getCreateNodeTableDDL(tableName) {
6901
- return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
6902
- id STRING,
6903
- name STRING,
6904
- file_path STRING,
6905
- start_line INT64,
6906
- end_line INT64,
6907
- exported BOOLEAN,
6908
- content STRING,
6909
- metadata STRING,
6910
- PRIMARY KEY (id)
6911
- )`;
6912
- }
6913
- function getCreateEdgeTableDDL() {
6914
- const uniqueTables = ALL_NODE_TABLES;
6915
- const fromToPairs = [];
6916
- for (const from of uniqueTables) {
6917
- for (const to of uniqueTables) {
6918
- fromToPairs.push(`FROM ${from} TO ${to}`);
6919
- }
6920
- }
6921
- return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
6922
- ${fromToPairs.join(",\n ")},
6923
- kind STRING,
6924
- weight DOUBLE,
6925
- label STRING
6926
- )`];
6927
- }
6928
7216
  function writeNodeCSVs(graph, outputDir) {
6929
- fs24.mkdirSync(outputDir, { recursive: true });
7217
+ fs25.mkdirSync(outputDir, { recursive: true });
6930
7218
  const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
6931
7219
  const tableBuffers = /* @__PURE__ */ new Map();
6932
7220
  const tableFilePaths = /* @__PURE__ */ new Map();
@@ -6934,7 +7222,7 @@ function writeNodeCSVs(graph, outputDir) {
6934
7222
  const table = NODE_TABLE_MAP[node.kind];
6935
7223
  if (!tableBuffers.has(table)) {
6936
7224
  tableBuffers.set(table, [header]);
6937
- tableFilePaths.set(table, path31.join(outputDir, `${table}.csv`));
7225
+ tableFilePaths.set(table, path32.join(outputDir, `${table}.csv`));
6938
7226
  }
6939
7227
  tableBuffers.get(table).push(
6940
7228
  csvRow([
@@ -6954,12 +7242,12 @@ function writeNodeCSVs(graph, outputDir) {
6954
7242
  );
6955
7243
  }
6956
7244
  for (const [table, lines] of tableBuffers) {
6957
- fs24.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
7245
+ fs25.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
6958
7246
  }
6959
7247
  return tableFilePaths;
6960
7248
  }
6961
7249
  function writeEdgeCSV(graph, outputDir) {
6962
- fs24.mkdirSync(outputDir, { recursive: true });
7250
+ fs25.mkdirSync(outputDir, { recursive: true });
6963
7251
  const header = "from_id,to_id,kind,weight,label\n";
6964
7252
  const groups = /* @__PURE__ */ new Map();
6965
7253
  for (const edge of graph.allEdges()) {
@@ -6970,7 +7258,7 @@ function writeEdgeCSV(graph, outputDir) {
6970
7258
  const toTable = NODE_TABLE_MAP[targetNode.kind];
6971
7259
  const key = `${fromTable}->${toTable}`;
6972
7260
  if (!groups.has(key)) {
6973
- const filePath = path31.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
7261
+ const filePath = path32.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
6974
7262
  groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
6975
7263
  }
6976
7264
  groups.get(key).lines.push(
@@ -6985,7 +7273,7 @@ function writeEdgeCSV(graph, outputDir) {
6985
7273
  }
6986
7274
  const result = [];
6987
7275
  for (const group of groups.values()) {
6988
- fs24.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
7276
+ fs25.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
6989
7277
  result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
6990
7278
  }
6991
7279
  return result;
@@ -7013,7 +7301,7 @@ async function loadGraphToDB(graph, dbManager) {
7013
7301
  } catch {
7014
7302
  }
7015
7303
  }
7016
- const tmpDir = fs24.mkdtempSync(path31.join(os13.tmpdir(), "code-intel-csv-"));
7304
+ const tmpDir = fs25.mkdtempSync(path32.join(os13.tmpdir(), "code-intel-csv-"));
7017
7305
  try {
7018
7306
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
7019
7307
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -7032,8 +7320,8 @@ async function loadGraphToDB(graph, dbManager) {
7032
7320
  }
7033
7321
  let nodeCount = 0;
7034
7322
  for (const [table, csvPath] of nodeTableFiles) {
7035
- if (!fs24.existsSync(csvPath)) continue;
7036
- const stat = fs24.statSync(csvPath);
7323
+ if (!fs25.existsSync(csvPath)) continue;
7324
+ const stat = fs25.statSync(csvPath);
7037
7325
  if (stat.size < 50) continue;
7038
7326
  try {
7039
7327
  await dbManager.execute(
@@ -7046,8 +7334,8 @@ async function loadGraphToDB(graph, dbManager) {
7046
7334
  }
7047
7335
  let edgeCount = 0;
7048
7336
  for (const group of edgeGroups) {
7049
- if (!fs24.existsSync(group.filePath)) continue;
7050
- const stat = fs24.statSync(group.filePath);
7337
+ if (!fs25.existsSync(group.filePath)) continue;
7338
+ const stat = fs25.statSync(group.filePath);
7051
7339
  if (stat.size < 50) continue;
7052
7340
  try {
7053
7341
  await dbManager.execute(
@@ -7061,7 +7349,7 @@ async function loadGraphToDB(graph, dbManager) {
7061
7349
  return { nodeCount, edgeCount };
7062
7350
  } finally {
7063
7351
  try {
7064
- fs24.rmSync(tmpDir, { recursive: true, force: true });
7352
+ fs25.rmSync(tmpDir, { recursive: true, force: true });
7065
7353
  } catch {
7066
7354
  }
7067
7355
  }
@@ -7113,19 +7401,19 @@ function buildNodeProps(node) {
7113
7401
  function escCypher(s) {
7114
7402
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
7115
7403
  }
7116
- var GLOBAL_DIR = path31.join(os13.homedir(), ".code-intel");
7117
- var REPOS_FILE = path31.join(GLOBAL_DIR, "repos.json");
7404
+ var GLOBAL_DIR = path32.join(os13.homedir(), ".code-intel");
7405
+ var REPOS_FILE = path32.join(GLOBAL_DIR, "repos.json");
7118
7406
  function loadRegistry() {
7119
7407
  try {
7120
- const data = fs24.readFileSync(REPOS_FILE, "utf-8");
7408
+ const data = fs25.readFileSync(REPOS_FILE, "utf-8");
7121
7409
  return JSON.parse(data);
7122
7410
  } catch {
7123
7411
  return [];
7124
7412
  }
7125
7413
  }
7126
7414
  function saveRegistry(entries) {
7127
- fs24.mkdirSync(GLOBAL_DIR, { recursive: true });
7128
- fs24.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
7415
+ fs25.mkdirSync(GLOBAL_DIR, { recursive: true });
7416
+ fs25.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
7129
7417
  }
7130
7418
  function upsertRepo(entry) {
7131
7419
  const entries = loadRegistry();
@@ -7142,30 +7430,30 @@ function removeRepo(repoPath) {
7142
7430
  saveRegistry(entries);
7143
7431
  }
7144
7432
  function saveMetadata(repoDir, metadata) {
7145
- const metaDir = path31.join(repoDir, ".code-intel");
7146
- fs24.mkdirSync(metaDir, { recursive: true });
7147
- fs24.writeFileSync(path31.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
7433
+ const metaDir = path32.join(repoDir, ".code-intel");
7434
+ fs25.mkdirSync(metaDir, { recursive: true });
7435
+ fs25.writeFileSync(path32.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
7148
7436
  }
7149
7437
  function loadMetadata(repoDir) {
7150
7438
  try {
7151
- const data = fs24.readFileSync(path31.join(repoDir, ".code-intel", "meta.json"), "utf-8");
7439
+ const data = fs25.readFileSync(path32.join(repoDir, ".code-intel", "meta.json"), "utf-8");
7152
7440
  return JSON.parse(data);
7153
7441
  } catch {
7154
7442
  return null;
7155
7443
  }
7156
7444
  }
7157
7445
  function getDbPath(repoDir) {
7158
- return path31.join(repoDir, ".code-intel", "graph.db");
7446
+ return path32.join(repoDir, ".code-intel", "graph.db");
7159
7447
  }
7160
7448
  function getVectorDbPath(repoDir) {
7161
- return path31.join(repoDir, ".code-intel", "vector.db");
7449
+ return path32.join(repoDir, ".code-intel", "vector.db");
7162
7450
  }
7163
7451
 
7164
7452
  // src/mcp-server/server.ts
7165
7453
  init_group_registry();
7166
7454
 
7167
7455
  // src/multi-repo/graph-from-db.ts
7168
- var TABLE_TO_KIND = Object.fromEntries(
7456
+ var TABLE_TO_KIND2 = Object.fromEntries(
7169
7457
  Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
7170
7458
  );
7171
7459
  function parseRow(row, kind) {
@@ -7189,7 +7477,7 @@ function parseRow(row, kind) {
7189
7477
  }
7190
7478
  async function loadGraphFromDB(graph, db) {
7191
7479
  for (const table of ALL_NODE_TABLES) {
7192
- const kind = TABLE_TO_KIND[table];
7480
+ const kind = TABLE_TO_KIND2[table];
7193
7481
  if (!kind) continue;
7194
7482
  let rows = [];
7195
7483
  try {
@@ -7242,12 +7530,12 @@ function scanForFiles(root, matcher, maxDepth = 2) {
7242
7530
  if (depth > maxDepth) return;
7243
7531
  let entries;
7244
7532
  try {
7245
- entries = fs24.readdirSync(dir, { withFileTypes: true });
7533
+ entries = fs25.readdirSync(dir, { withFileTypes: true });
7246
7534
  } catch {
7247
7535
  return;
7248
7536
  }
7249
7537
  for (const entry of entries) {
7250
- const full = path31.join(dir, entry.name);
7538
+ const full = path32.join(dir, entry.name);
7251
7539
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
7252
7540
  walk(full, depth + 1);
7253
7541
  } else if (entry.isFile() && matcher(entry.name)) {
@@ -7270,8 +7558,8 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
7270
7558
  ]);
7271
7559
  var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
7272
7560
  function tryParseFile(filePath) {
7273
- const ext = path31.extname(filePath).toLowerCase();
7274
- const content = fs24.readFileSync(filePath, "utf-8");
7561
+ const ext = path32.extname(filePath).toLowerCase();
7562
+ const content = fs25.readFileSync(filePath, "utf-8");
7275
7563
  if (ext === ".json") {
7276
7564
  try {
7277
7565
  return JSON.parse(content);
@@ -7325,7 +7613,7 @@ async function parseGraphQLContracts(repoRoot) {
7325
7613
  const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
7326
7614
  const contracts = [];
7327
7615
  for (const filePath of files) {
7328
- const content = fs24.readFileSync(filePath, "utf-8");
7616
+ const content = fs25.readFileSync(filePath, "utf-8");
7329
7617
  const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
7330
7618
  let match;
7331
7619
  while ((match = typeRegex.exec(content)) !== null) {
@@ -7356,7 +7644,7 @@ async function parseProtoContracts(repoRoot) {
7356
7644
  const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
7357
7645
  const contracts = [];
7358
7646
  for (const filePath of files) {
7359
- const content = fs24.readFileSync(filePath, "utf-8");
7647
+ const content = fs25.readFileSync(filePath, "utf-8");
7360
7648
  const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
7361
7649
  let serviceMatch;
7362
7650
  while ((serviceMatch = serviceRegex.exec(content)) !== null) {
@@ -7561,13 +7849,13 @@ async function syncGroup(group) {
7561
7849
  logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
7562
7850
  continue;
7563
7851
  }
7564
- const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
7565
- if (!fs24.existsSync(dbPath)) {
7852
+ const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
7853
+ if (!fs25.existsSync(dbPath)) {
7566
7854
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
7567
7855
  continue;
7568
7856
  }
7569
7857
  const graph = createKnowledgeGraph();
7570
- const db = new DbManager(dbPath);
7858
+ const db = new DbManager(dbPath, true);
7571
7859
  try {
7572
7860
  await db.init();
7573
7861
  await loadGraphFromDB(graph, db);
@@ -7635,10 +7923,10 @@ async function queryGroup(group, query, limit = 20) {
7635
7923
  for (const member of group.members) {
7636
7924
  const regEntry = registry.find((r) => r.name === member.registryName);
7637
7925
  if (!regEntry) continue;
7638
- const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
7639
- if (!fs24.existsSync(dbPath)) continue;
7926
+ const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
7927
+ if (!fs25.existsSync(dbPath)) continue;
7640
7928
  const graph = createKnowledgeGraph();
7641
- const db = new DbManager(dbPath);
7929
+ const db = new DbManager(dbPath, true);
7642
7930
  try {
7643
7931
  await db.init();
7644
7932
  await loadGraphFromDB(graph, db);
@@ -8136,22 +8424,22 @@ function suggestTests(graph, symbolName) {
8136
8424
  const callPaths = [];
8137
8425
  const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
8138
8426
  while (pathQueue.length > 0 && callPaths.length < 5) {
8139
- const { id, path: path32, depth } = pathQueue.shift();
8427
+ const { id, path: path33, depth } = pathQueue.shift();
8140
8428
  let hasCallers2 = false;
8141
8429
  for (const edge of graph.findEdgesTo(id)) {
8142
8430
  if (edge.kind !== "calls") continue;
8143
8431
  const callerNode = graph.getNode(edge.source);
8144
8432
  if (!callerNode) continue;
8145
8433
  hasCallers2 = true;
8146
- const newPath = [callerNode.name, ...path32];
8434
+ const newPath = [callerNode.name, ...path33];
8147
8435
  if (depth + 1 >= 3 || callPaths.length >= 5) {
8148
8436
  if (callPaths.length < 5) callPaths.push(newPath);
8149
8437
  continue;
8150
8438
  }
8151
8439
  pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
8152
8440
  }
8153
- if (!hasCallers2 && path32.length > 1) {
8154
- callPaths.push(path32);
8441
+ if (!hasCallers2 && path33.length > 1) {
8442
+ callPaths.push(path33);
8155
8443
  }
8156
8444
  }
8157
8445
  if (callPaths.length === 0) {
@@ -8708,24 +8996,44 @@ function createMcpServer(graph, repoName, workspaceRoot) {
8708
8996
  }
8709
8997
  const startMs = Date.now();
8710
8998
  const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot);
8999
+ const MCP_TIMEOUT_MS = parseInt(process.env["CODE_INTEL_MCP_TIMEOUT_MS"] ?? "30000", 10);
9000
+ let timeoutHandle = null;
9001
+ let timedOut = false;
9002
+ const timeoutPromise = new Promise((_, reject) => {
9003
+ timeoutHandle = setTimeout(() => {
9004
+ timedOut = true;
9005
+ reject(new Error(`MCP tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`));
9006
+ }, MCP_TIMEOUT_MS);
9007
+ });
8711
9008
  let result;
8712
9009
  let status = "success";
8713
9010
  try {
8714
9011
  if (isTracingEnabled()) {
8715
- result = await withSpan(
8716
- `mcp.tool.${name}`,
8717
- sanitizeAttrs({ "mcp.tool": name, "mcp.repo": repoName }),
8718
- dispatch
8719
- );
9012
+ result = await Promise.race([
9013
+ withSpan(
9014
+ `mcp.tool.${name}`,
9015
+ sanitizeAttrs({ "mcp.tool": name, "mcp.repo": repoName }),
9016
+ dispatch
9017
+ ),
9018
+ timeoutPromise
9019
+ ]);
8720
9020
  } else {
8721
- result = await dispatch();
9021
+ result = await Promise.race([dispatch(), timeoutPromise]);
8722
9022
  }
8723
9023
  if (result.isError) status = "error";
8724
9024
  } catch (err) {
8725
9025
  status = "error";
8726
9026
  mcpToolCallsTotal.inc({ tool: name, status });
8727
9027
  mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
9028
+ if (timedOut) {
9029
+ return {
9030
+ content: [{ type: "text", text: JSON.stringify({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
9031
+ isError: false
9032
+ };
9033
+ }
8728
9034
  throw err;
9035
+ } finally {
9036
+ if (timeoutHandle) clearTimeout(timeoutHandle);
8729
9037
  }
8730
9038
  mcpToolCallsTotal.inc({ tool: name, status });
8731
9039
  mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
@@ -9113,7 +9421,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
9113
9421
  for (const { filePath: changedFile, changedLines } of changedFiles) {
9114
9422
  for (const node of graph.allNodes()) {
9115
9423
  if (!node.filePath) continue;
9116
- const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path31.sep, "");
9424
+ const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path32.sep, "");
9117
9425
  const normChanged = changedFile.replace(/^a\/|^b\//, "");
9118
9426
  if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
9119
9427
  if (node.startLine !== void 0 && node.endLine !== void 0) {
@@ -9536,8 +9844,8 @@ var STUCK_THRESHOLD_MINUTES = 30;
9536
9844
  var JobsDB = class {
9537
9845
  db;
9538
9846
  constructor(dbPath) {
9539
- fs24.mkdirSync(path31.dirname(dbPath), { recursive: true });
9540
- this.db = new Database3(dbPath);
9847
+ fs25.mkdirSync(path32.dirname(dbPath), { recursive: true });
9848
+ this.db = new Database2(dbPath);
9541
9849
  this.db.pragma("journal_mode = WAL");
9542
9850
  this.db.pragma("foreign_keys = ON");
9543
9851
  this.createTables();
@@ -9678,7 +9986,7 @@ var JobsDB = class {
9678
9986
  }
9679
9987
  };
9680
9988
  function getJobsDBPath() {
9681
- return path31.join(os13.homedir(), ".code-intel", "jobs.db");
9989
+ return path32.join(os13.homedir(), ".code-intel", "jobs.db");
9682
9990
  }
9683
9991
  var _jobsDB = null;
9684
9992
  function getOrCreateJobsDB() {
@@ -9770,7 +10078,7 @@ var BACKUP_VERSION = "1.0";
9770
10078
  var ALGORITHM = "aes-256-gcm";
9771
10079
  var IV_LENGTH = 16;
9772
10080
  function getBackupDir() {
9773
- return path31.join(os13.homedir(), ".code-intel", "backups");
10081
+ return path32.join(os13.homedir(), ".code-intel", "backups");
9774
10082
  }
9775
10083
  function getBackupKey() {
9776
10084
  const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
@@ -9801,30 +10109,30 @@ var BackupService = class {
9801
10109
  constructor(backupDir) {
9802
10110
  this.backupDir = backupDir ?? getBackupDir();
9803
10111
  this.key = getBackupKey();
9804
- fs24.mkdirSync(this.backupDir, { recursive: true });
10112
+ fs25.mkdirSync(this.backupDir, { recursive: true });
9805
10113
  }
9806
10114
  /**
9807
10115
  * Create a backup for a repository.
9808
10116
  * Returns the backup entry.
9809
10117
  */
9810
10118
  createBackup(repoPath) {
9811
- const codeIntelDir = path31.join(repoPath, ".code-intel");
10119
+ const codeIntelDir = path32.join(repoPath, ".code-intel");
9812
10120
  const id = v4();
9813
10121
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
9814
10122
  const filesToBackup = [];
9815
10123
  const candidates = ["graph.db", "vector.db", "meta.json"];
9816
10124
  for (const f of candidates) {
9817
- const fp = path31.join(codeIntelDir, f);
9818
- if (fs24.existsSync(fp)) {
10125
+ const fp = path32.join(codeIntelDir, f);
10126
+ if (fs25.existsSync(fp)) {
9819
10127
  filesToBackup.push({ name: f, localPath: fp });
9820
10128
  }
9821
10129
  }
9822
- const registryPath = path31.join(os13.homedir(), ".code-intel", "registry.json");
9823
- if (fs24.existsSync(registryPath)) {
10130
+ const registryPath = path32.join(os13.homedir(), ".code-intel", "registry.json");
10131
+ if (fs25.existsSync(registryPath)) {
9824
10132
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
9825
10133
  }
9826
- const usersDbPath = path31.join(os13.homedir(), ".code-intel", "users.db");
9827
- if (fs24.existsSync(usersDbPath)) {
10134
+ const usersDbPath = path32.join(os13.homedir(), ".code-intel", "users.db");
10135
+ if (fs25.existsSync(usersDbPath)) {
9828
10136
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
9829
10137
  }
9830
10138
  if (filesToBackup.length === 0) {
@@ -9835,7 +10143,7 @@ var BackupService = class {
9835
10143
  createdAt,
9836
10144
  version: BACKUP_VERSION,
9837
10145
  files: filesToBackup.map((f) => {
9838
- const data = fs24.readFileSync(f.localPath);
10146
+ const data = fs25.readFileSync(f.localPath);
9839
10147
  return {
9840
10148
  name: f.name,
9841
10149
  sha256: crypto5.createHash("sha256").update(data).digest("hex"),
@@ -9849,7 +10157,7 @@ var BackupService = class {
9849
10157
  manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
9850
10158
  parts.push(manifestLenBuf, manifestBuf);
9851
10159
  for (const f of filesToBackup) {
9852
- const data = fs24.readFileSync(f.localPath);
10160
+ const data = fs25.readFileSync(f.localPath);
9853
10161
  const nameBuf = Buffer.from(f.name, "utf-8");
9854
10162
  const nameLenBuf = Buffer.alloc(2);
9855
10163
  nameLenBuf.writeUInt16BE(nameBuf.length, 0);
@@ -9860,8 +10168,8 @@ var BackupService = class {
9860
10168
  const plaintext = Buffer.concat(parts);
9861
10169
  const encrypted = encryptBuffer(plaintext, this.key);
9862
10170
  const backupFileName = `backup-${id}.cib`;
9863
- const backupPath = path31.join(this.backupDir, backupFileName);
9864
- fs24.writeFileSync(backupPath, encrypted);
10171
+ const backupPath = path32.join(this.backupDir, backupFileName);
10172
+ fs25.writeFileSync(backupPath, encrypted);
9865
10173
  const entry = {
9866
10174
  id,
9867
10175
  createdAt,
@@ -9888,9 +10196,9 @@ var BackupService = class {
9888
10196
  async uploadToS3(entry) {
9889
10197
  const cfg = getS3Config();
9890
10198
  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.");
9891
- const fileName = path31.basename(entry.path);
10199
+ const fileName = path32.basename(entry.path);
9892
10200
  const s3Key = `${cfg.prefix}${fileName}`;
9893
- const body = fs24.readFileSync(entry.path);
10201
+ const body = fs25.readFileSync(entry.path);
9894
10202
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
9895
10203
  if (result.statusCode < 200 || result.statusCode >= 300) {
9896
10204
  throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
@@ -9907,8 +10215,8 @@ var BackupService = class {
9907
10215
  if (result.statusCode < 200 || result.statusCode >= 300) {
9908
10216
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
9909
10217
  }
9910
- fs24.mkdirSync(path31.dirname(destPath), { recursive: true });
9911
- fs24.writeFileSync(destPath, Buffer.from(result.body, "binary"));
10218
+ fs25.mkdirSync(path32.dirname(destPath), { recursive: true });
10219
+ fs25.writeFileSync(destPath, Buffer.from(result.body, "binary"));
9912
10220
  }
9913
10221
  /**
9914
10222
  * List backup objects in S3 with the configured prefix.
@@ -9954,10 +10262,10 @@ var BackupService = class {
9954
10262
  if (!entry) {
9955
10263
  throw new Error(`Backup "${backupId}" not found.`);
9956
10264
  }
9957
- if (!fs24.existsSync(entry.path)) {
10265
+ if (!fs25.existsSync(entry.path)) {
9958
10266
  throw new Error(`Backup file not found at: ${entry.path}`);
9959
10267
  }
9960
- const encrypted = fs24.readFileSync(entry.path);
10268
+ const encrypted = fs25.readFileSync(entry.path);
9961
10269
  let plaintext;
9962
10270
  try {
9963
10271
  plaintext = decryptBuffer(encrypted, this.key);
@@ -9971,8 +10279,8 @@ var BackupService = class {
9971
10279
  offset += manifestLen;
9972
10280
  const manifest = JSON.parse(manifestStr);
9973
10281
  const restoreBase = targetRepoPath ?? entry.repoPath;
9974
- const codeIntelDir = path31.join(restoreBase, ".code-intel");
9975
- fs24.mkdirSync(codeIntelDir, { recursive: true });
10282
+ const codeIntelDir = path32.join(restoreBase, ".code-intel");
10283
+ fs25.mkdirSync(codeIntelDir, { recursive: true });
9976
10284
  for (const fileEntry of manifest.files) {
9977
10285
  const nameLen = plaintext.readUInt16BE(offset);
9978
10286
  offset += 2;
@@ -9989,18 +10297,18 @@ var BackupService = class {
9989
10297
  }
9990
10298
  let destPath;
9991
10299
  if (name === "registry.json" || name === "users.db") {
9992
- destPath = path31.join(os13.homedir(), ".code-intel", name);
10300
+ destPath = path32.join(os13.homedir(), ".code-intel", name);
9993
10301
  } else {
9994
- destPath = path31.join(codeIntelDir, name);
10302
+ destPath = path32.join(codeIntelDir, name);
9995
10303
  }
9996
- fs24.writeFileSync(destPath, data);
10304
+ fs25.writeFileSync(destPath, data);
9997
10305
  }
9998
10306
  }
9999
10307
  /**
10000
10308
  * Apply retention policy: keep N daily, M weekly, L monthly backups.
10001
10309
  */
10002
10310
  applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
10003
- const entries = this._loadIndex().filter((e) => fs24.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
10311
+ const entries = this._loadIndex().filter((e) => fs25.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
10004
10312
  const keep = /* @__PURE__ */ new Set();
10005
10313
  const now = /* @__PURE__ */ new Date();
10006
10314
  const dailyCutoff = new Date(now);
@@ -10030,7 +10338,7 @@ var BackupService = class {
10030
10338
  for (const e of entries) {
10031
10339
  if (!keep.has(e.id)) {
10032
10340
  try {
10033
- fs24.unlinkSync(e.path);
10341
+ fs25.unlinkSync(e.path);
10034
10342
  deleted++;
10035
10343
  } catch {
10036
10344
  }
@@ -10042,17 +10350,17 @@ var BackupService = class {
10042
10350
  }
10043
10351
  // ── Index helpers ──────────────────────────────────────────────────────────
10044
10352
  _indexPath() {
10045
- return path31.join(this.backupDir, "index.json");
10353
+ return path32.join(this.backupDir, "index.json");
10046
10354
  }
10047
10355
  _loadIndex() {
10048
10356
  try {
10049
- return JSON.parse(fs24.readFileSync(this._indexPath(), "utf-8"));
10357
+ return JSON.parse(fs25.readFileSync(this._indexPath(), "utf-8"));
10050
10358
  } catch {
10051
10359
  return [];
10052
10360
  }
10053
10361
  }
10054
10362
  _saveIndex(entries) {
10055
- fs24.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
10363
+ fs25.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
10056
10364
  }
10057
10365
  _appendIndex(entry) {
10058
10366
  const entries = this._loadIndex();
@@ -10845,11 +11153,11 @@ var openApiSpec = {
10845
11153
  };
10846
11154
 
10847
11155
  // src/http/app.ts
10848
- var __dirname$1 = path31.dirname(fileURLToPath(import.meta.url));
11156
+ var __dirname$1 = path32.dirname(fileURLToPath(import.meta.url));
10849
11157
  var WEB_DIST = (() => {
10850
- const bundled = path31.resolve(__dirname$1, "..", "web");
10851
- if (fs24.existsSync(bundled)) return bundled;
10852
- return path31.resolve(__dirname$1, "..", "..", "..", "web", "dist");
11158
+ const bundled = path32.resolve(__dirname$1, "..", "web");
11159
+ if (fs25.existsSync(bundled)) return bundled;
11160
+ return path32.resolve(__dirname$1, "..", "..", "..", "web", "dist");
10853
11161
  })();
10854
11162
  function getAllowedOrigins() {
10855
11163
  const env = process.env["CODE_INTEL_CORS_ORIGINS"];
@@ -10858,13 +11166,17 @@ function getAllowedOrigins() {
10858
11166
  }
10859
11167
  function createDefaultLimiter() {
10860
11168
  const max = parseInt(process.env["CODE_INTEL_RATE_LIMIT_MAX"] ?? "100", 10);
10861
- const windowMs = parseInt(process.env["CODE_INTEL_RATE_LIMIT_WINDOW_MS"] ?? `${15 * 60 * 1e3}`, 10);
11169
+ const windowMs = parseInt(process.env["CODE_INTEL_RATE_LIMIT_WINDOW_MS"] ?? `${60 * 1e3}`, 10);
10862
11170
  return rateLimit({
10863
11171
  windowMs,
10864
11172
  max,
10865
11173
  standardHeaders: true,
10866
11174
  legacyHeaders: false,
10867
- skip: (req) => req.path.startsWith("/health") || req.path === "/metrics",
11175
+ // Skip health checks, metrics, and read-only listing/pagination endpoints.
11176
+ // The node pagination and group/repo listing endpoints are hit many times
11177
+ // when loading a large graph — rate-limiting them only hurts the user's own
11178
+ // session without providing meaningful abuse protection.
11179
+ skip: (req) => req.path.startsWith("/health") || req.path === "/metrics" || req.path === "/api/v1/repos" || req.path === "/api/v1/groups" || req.method === "GET" && req.path.startsWith("/api/v1/groups/") || /^\/api\/v1\/graph\/[^/]+\/nodes$/.test(req.path),
10868
11180
  message: {
10869
11181
  error: {
10870
11182
  code: ErrorCodes.RATE_LIMIT_EXCEEDED,
@@ -10923,12 +11235,31 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10923
11235
  });
10924
11236
  app.use(requestIdMiddleware);
10925
11237
  app.use(authMiddleware);
11238
+ let dbUnavailableSince = null;
10926
11239
  app.use((_req, res, next) => {
10927
11240
  if (workspaceRoot) {
11241
+ const metaFilePath = path32.join(workspaceRoot, ".code-intel", "meta.json");
11242
+ let metaOk = false;
10928
11243
  try {
10929
- const meta = loadMetadata(workspaceRoot);
10930
- if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
10931
- } catch {
11244
+ if (fs25.existsSync(metaFilePath)) {
11245
+ const raw = fs25.readFileSync(metaFilePath, "utf-8");
11246
+ const meta = JSON.parse(raw);
11247
+ if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
11248
+ }
11249
+ metaOk = true;
11250
+ if (dbUnavailableSince !== null) {
11251
+ dbUnavailableSince = null;
11252
+ logger_default.info("[serve] DB back online \u2014 cleared stale flag");
11253
+ }
11254
+ } catch (err) {
11255
+ if (dbUnavailableSince === null) {
11256
+ dbUnavailableSince = (/* @__PURE__ */ new Date()).toISOString();
11257
+ logger_default.warn(`[serve] DB unavailable since ${dbUnavailableSince}: ${err instanceof Error ? err.message : String(err)}`);
11258
+ }
11259
+ }
11260
+ if (!metaOk) {
11261
+ res.setHeader("X-Stale", "true");
11262
+ res.setHeader("X-Stale-Since", dbUnavailableSince);
10932
11263
  }
10933
11264
  }
10934
11265
  next();
@@ -10984,6 +11315,21 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
10984
11315
  let vectorIndex = null;
10985
11316
  let vectorIndexBuilding = false;
10986
11317
  let vectorIndexReady = false;
11318
+ let bm25Index = null;
11319
+ function ensureBm25Index() {
11320
+ if (bm25Index) return bm25Index;
11321
+ if (!workspaceRoot) return null;
11322
+ const idx = new Bm25Index(getBm25DbPath(workspaceRoot));
11323
+ idx.load();
11324
+ if (idx.isLoaded) {
11325
+ bm25Index = idx;
11326
+ return idx;
11327
+ }
11328
+ return null;
11329
+ }
11330
+ if (workspaceRoot && process.env["NODE_ENV"] !== "test") {
11331
+ setImmediate(() => ensureBm25Index());
11332
+ }
10987
11333
  async function ensureVectorIndex() {
10988
11334
  if (vectorIndexReady && vectorIndex) return vectorIndex;
10989
11335
  if (!workspaceRoot || vectorIndexBuilding) return null;
@@ -11380,10 +11726,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11380
11726
  const registry = loadRegistry();
11381
11727
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
11382
11728
  if (!entry) return null;
11383
- const dbPath = path31.join(entry.path, ".code-intel", "graph.db");
11384
- if (!fs24.existsSync(dbPath)) return null;
11729
+ const dbPath = path32.join(entry.path, ".code-intel", "graph.db");
11730
+ if (!fs25.existsSync(dbPath)) return null;
11385
11731
  const repoGraph = createKnowledgeGraph();
11386
- const db = new DbManager(dbPath);
11732
+ const db = new DbManager(dbPath, true);
11387
11733
  try {
11388
11734
  await db.init();
11389
11735
  await loadGraphFromDB(repoGraph, db);
@@ -11394,10 +11740,33 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11394
11740
  return null;
11395
11741
  }
11396
11742
  }
11743
+ async function loadGroupGraph(groupName) {
11744
+ const group = loadGroup(groupName);
11745
+ if (!group) return null;
11746
+ const registry = loadRegistry();
11747
+ const mergedGraph = createKnowledgeGraph();
11748
+ for (const member of group.members) {
11749
+ const regEntry = registry.find((r) => r.name === member.registryName);
11750
+ if (!regEntry) continue;
11751
+ const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
11752
+ if (!fs25.existsSync(dbPath)) continue;
11753
+ const db = new DbManager(dbPath, true);
11754
+ try {
11755
+ await db.init();
11756
+ await loadGraphFromDB(mergedGraph, db);
11757
+ db.close();
11758
+ } catch {
11759
+ db.close();
11760
+ }
11761
+ }
11762
+ return mergedGraph.size.nodes > 0 ? mergedGraph : null;
11763
+ }
11397
11764
  async function getGraphForRepo(requestedRepo) {
11398
11765
  if (!requestedRepo || requestedRepo === repoName) return graph;
11399
11766
  const g = await loadRepoGraph(requestedRepo);
11400
- return g ?? graph;
11767
+ if (g) return g;
11768
+ const gg = await loadGroupGraph(requestedRepo);
11769
+ return gg ?? graph;
11401
11770
  }
11402
11771
  app.get("/api/v1/graph/:repo", requireRepoAccess((req) => {
11403
11772
  const p = req.params["repo"];
@@ -11421,11 +11790,56 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11421
11790
  }
11422
11791
  res.json({ nodes: [...g.allNodes()], edges: [...g.allEdges()] });
11423
11792
  });
11793
+ app.get("/api/v1/graph/:repo/nodes", requireRepoAccess((req) => {
11794
+ const p = req.params["repo"];
11795
+ const repo = Array.isArray(p) ? p[0] : p;
11796
+ return repo ? decodeURIComponent(repo) : void 0;
11797
+ }), async (req, res) => {
11798
+ const rawRepo = req.params["repo"];
11799
+ const requestedRepo = decodeURIComponent(Array.isArray(rawRepo) ? rawRepo[0] ?? "" : rawRepo ?? "");
11800
+ const limit = Math.min(parseInt(req.query["limit"] ?? "200", 10), 1e3);
11801
+ const offset = Math.max(parseInt(req.query["offset"] ?? "0", 10), 0);
11802
+ const g = requestedRepo === repoName ? graph : await loadRepoGraph(requestedRepo);
11803
+ if (!g) {
11804
+ res.status(404).json({
11805
+ error: {
11806
+ code: ErrorCodes.NOT_FOUND,
11807
+ message: `Repo "${requestedRepo}" not found or not indexed`,
11808
+ hint: `Run: code-intel analyze <path>`,
11809
+ requestId: req.requestId,
11810
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11811
+ }
11812
+ });
11813
+ return;
11814
+ }
11815
+ let nodes;
11816
+ if (isLazyGraph(g)) {
11817
+ nodes = await g.getNodePage(offset, limit);
11818
+ } else {
11819
+ const eager = g;
11820
+ if (!eager._nodeArray) {
11821
+ eager._nodeArray = [...g.allNodes()];
11822
+ }
11823
+ nodes = eager._nodeArray.slice(offset, offset + limit);
11824
+ }
11825
+ res.json({
11826
+ nodes,
11827
+ offset,
11828
+ limit,
11829
+ total: g.size.nodes,
11830
+ hasMore: offset + nodes.length < g.size.nodes
11831
+ });
11832
+ });
11424
11833
  app.post("/api/v1/search", requireToolScope("search"), async (req, res) => {
11425
11834
  const { query, limit, repo } = req.body;
11426
11835
  const g = await getGraphForRepo(repo);
11427
11836
  const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
11428
- const { results, searchMode } = await hybridSearch(g, query ?? "", limit ?? 20, { vectorDbPath: vdbPath });
11837
+ const bm25 = !repo || repo === repoName ? ensureBm25Index() : null;
11838
+ const bm25Results = bm25 ? bm25.search(query ?? "", (limit ?? 20) * 3) : null;
11839
+ const { results, searchMode } = await hybridSearch(g, query ?? "", limit ?? 20, {
11840
+ vectorDbPath: vdbPath,
11841
+ bm25Results: bm25Results ?? void 0
11842
+ });
11429
11843
  res.json({ results, searchMode });
11430
11844
  });
11431
11845
  app.post("/api/v1/vector-search", async (req, res) => {
@@ -11468,7 +11882,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11468
11882
  return;
11469
11883
  }
11470
11884
  try {
11471
- const content = fs24.readFileSync(file_path, "utf-8");
11885
+ const content = fs25.readFileSync(file_path, "utf-8");
11472
11886
  res.json({ content });
11473
11887
  } catch {
11474
11888
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
@@ -11505,7 +11919,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11505
11919
  if (workspaceRoot) {
11506
11920
  try {
11507
11921
  const dbPath = getDbPath(workspaceRoot);
11508
- const dbm = new DbManager(dbPath);
11922
+ const dbm = new DbManager(dbPath, true);
11509
11923
  await dbm.init();
11510
11924
  const rows = await dbm.query(q);
11511
11925
  dbm.close();
@@ -11546,33 +11960,53 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11546
11960
  app.get("/api/v1/nodes/:id", async (req, res) => {
11547
11961
  const nodeId = decodeURIComponent(req.params.id);
11548
11962
  const g = await getGraphForRepo(req.query["repo"]);
11549
- const node = g.getNode(nodeId);
11963
+ const node = isLazyGraph(g) ? await g.getNodeAsync(nodeId) : g.getNode(nodeId);
11550
11964
  if (!node) {
11551
11965
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Node not found", requestId: req.requestId } });
11552
11966
  return;
11553
11967
  }
11554
11968
  const incoming = [...g.findEdgesTo(nodeId)];
11555
11969
  const outgoing = [...g.findEdgesFrom(nodeId)];
11970
+ const resolveName = isLazyGraph(g) ? async (id) => {
11971
+ const n = g.getNode(id) ?? await g.getNodeAsync(id);
11972
+ return n?.name;
11973
+ } : (id) => Promise.resolve(g.getNode(id)?.name);
11974
+ const resolveKind = isLazyGraph(g) ? async (id) => {
11975
+ const n = g.getNode(id) ?? await g.getNodeAsync(id);
11976
+ return n?.kind;
11977
+ } : (id) => Promise.resolve(g.getNode(id)?.kind);
11556
11978
  res.json({
11557
11979
  node,
11558
- callers: incoming.filter((e) => e.kind === "calls").map((e) => ({ id: e.source, name: g.getNode(e.source)?.name, weight: e.weight })),
11559
- callees: outgoing.filter((e) => e.kind === "calls").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name, weight: e.weight })),
11560
- imports: outgoing.filter((e) => e.kind === "imports").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name })),
11561
- importedBy: incoming.filter((e) => e.kind === "imports").map((e) => ({ id: e.source, name: g.getNode(e.source)?.name })),
11562
- extends: outgoing.filter((e) => e.kind === "extends").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name })),
11563
- implementsEdges: outgoing.filter((e) => e.kind === "implements").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name })),
11564
- members: outgoing.filter((e) => e.kind === "has_member").map((e) => ({ id: e.target, name: g.getNode(e.target)?.name, kind: g.getNode(e.target)?.kind })),
11565
- cluster: incoming.filter((e) => e.kind === "belongs_to").map((e) => g.getNode(e.target)?.name)[0]
11980
+ callers: await Promise.all(incoming.filter((e) => e.kind === "calls").map(async (e) => ({ id: e.source, name: await resolveName(e.source), weight: e.weight }))),
11981
+ callees: await Promise.all(outgoing.filter((e) => e.kind === "calls").map(async (e) => ({ id: e.target, name: await resolveName(e.target), weight: e.weight }))),
11982
+ imports: await Promise.all(outgoing.filter((e) => e.kind === "imports").map(async (e) => ({ id: e.target, name: await resolveName(e.target) }))),
11983
+ importedBy: await Promise.all(incoming.filter((e) => e.kind === "imports").map(async (e) => ({ id: e.source, name: await resolveName(e.source) }))),
11984
+ extends: await Promise.all(outgoing.filter((e) => e.kind === "extends").map(async (e) => ({ id: e.target, name: await resolveName(e.target) }))),
11985
+ implementsEdges: await Promise.all(outgoing.filter((e) => e.kind === "implements").map(async (e) => ({ id: e.target, name: await resolveName(e.target) }))),
11986
+ members: await Promise.all(outgoing.filter((e) => e.kind === "has_member").map(async (e) => ({ id: e.target, name: await resolveName(e.target), kind: await resolveKind(e.target) }))),
11987
+ cluster: (await Promise.all(incoming.filter((e) => e.kind === "belongs_to").map(async (e) => resolveName(e.target))))[0]
11566
11988
  });
11567
11989
  });
11568
11990
  app.post("/api/v1/blast-radius", async (req, res) => {
11569
11991
  const { target, direction = "both", max_hops = 5, repo } = req.body;
11570
11992
  const g = await getGraphForRepo(repo);
11571
11993
  let targetNode = null;
11572
- for (const node of g.allNodes()) {
11573
- if (node.name === target || node.id === target) {
11574
- targetNode = node;
11575
- break;
11994
+ if (isLazyGraph(g) && target) {
11995
+ targetNode = g.getNode(target) ?? await g.getNodeAsync(target) ?? null;
11996
+ if (!targetNode) {
11997
+ for await (const node of g.allNodesAsync()) {
11998
+ if (node.name === target || node.id === target) {
11999
+ targetNode = node;
12000
+ break;
12001
+ }
12002
+ }
12003
+ }
12004
+ } else {
12005
+ for (const node of g.allNodes()) {
12006
+ if (node.name === target || node.id === target) {
12007
+ targetNode = node;
12008
+ break;
12009
+ }
11576
12010
  }
11577
12011
  }
11578
12012
  if (!targetNode) {
@@ -11726,9 +12160,9 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11726
12160
  for (const member of group.members) {
11727
12161
  const regEntry = registry.find((r) => r.name === member.registryName);
11728
12162
  if (!regEntry) continue;
11729
- const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
11730
- if (!fs24.existsSync(dbPath)) continue;
11731
- const db = new DbManager(dbPath);
12163
+ const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
12164
+ if (!fs25.existsSync(dbPath)) continue;
12165
+ const db = new DbManager(dbPath, true);
11732
12166
  try {
11733
12167
  await db.init();
11734
12168
  await loadGraphFromDB(mergedGraph, db);
@@ -11753,10 +12187,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11753
12187
  let nodeCount = 0;
11754
12188
  let edgeCount = 0;
11755
12189
  if (regEntry) {
11756
- const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
11757
- if (fs24.existsSync(dbPath)) {
12190
+ const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
12191
+ if (fs25.existsSync(dbPath)) {
11758
12192
  try {
11759
- const db = new DbManager(dbPath);
12193
+ const db = new DbManager(dbPath, true);
11760
12194
  await db.init();
11761
12195
  const g = createKnowledgeGraph();
11762
12196
  await loadGraphFromDB(g, db);
@@ -11779,7 +12213,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11779
12213
  res.json({ repos, edges });
11780
12214
  });
11781
12215
  app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
11782
- const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
12216
+ const { file, startLine: startLineStr, endLine: endLineStr, repo } = req.query;
11783
12217
  if (!file) {
11784
12218
  res.status(400).json({
11785
12219
  error: {
@@ -11803,14 +12237,36 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11803
12237
  });
11804
12238
  return;
11805
12239
  }
11806
- let rawResolved = path31.normalize(file);
11807
- if (!path31.isAbsolute(rawResolved) && workspaceRoot) {
11808
- rawResolved = path31.join(workspaceRoot, rawResolved);
12240
+ let baseDir = workspaceRoot;
12241
+ if (repo && repo !== repoName) {
12242
+ const registry = loadRegistry();
12243
+ const entry = registry.find((r) => r.name === repo || r.path === repo);
12244
+ if (entry) {
12245
+ baseDir = entry.path;
12246
+ } else {
12247
+ const group = loadGroup(repo);
12248
+ if (group) {
12249
+ const normalizedFile = path32.normalize(file);
12250
+ for (const member of group.members) {
12251
+ const regEntry = registry.find((r) => r.name === member.registryName);
12252
+ if (!regEntry) continue;
12253
+ const candidate = path32.resolve(path32.join(regEntry.path, normalizedFile));
12254
+ if (fs25.existsSync(candidate)) {
12255
+ baseDir = regEntry.path;
12256
+ break;
12257
+ }
12258
+ }
12259
+ }
12260
+ }
12261
+ }
12262
+ let rawResolved = path32.normalize(file);
12263
+ if (!path32.isAbsolute(rawResolved) && baseDir) {
12264
+ rawResolved = path32.join(baseDir, rawResolved);
11809
12265
  }
11810
- const resolvedFile = path31.resolve(rawResolved);
12266
+ const resolvedFile = path32.resolve(rawResolved);
11811
12267
  function isInsideDir(fileAbs, dir) {
11812
- const rel = path31.relative(path31.resolve(dir), fileAbs);
11813
- return !rel.startsWith("..") && !path31.isAbsolute(rel);
12268
+ const rel = path32.relative(path32.resolve(dir), fileAbs);
12269
+ return !rel.startsWith("..") && !path32.isAbsolute(rel);
11814
12270
  }
11815
12271
  if (workspaceRoot) {
11816
12272
  if (!isInsideDir(resolvedFile, workspaceRoot)) {
@@ -11847,7 +12303,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11847
12303
  }
11848
12304
  let fileContent;
11849
12305
  try {
11850
- fileContent = fs24.readFileSync(resolvedFile, "utf-8");
12306
+ fileContent = fs25.readFileSync(resolvedFile, "utf-8");
11851
12307
  } catch {
11852
12308
  res.status(404).json({
11853
12309
  error: {
@@ -11878,7 +12334,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11878
12334
  const contextStart = Math.max(1, startLine - 20);
11879
12335
  const contextEnd = Math.min(lines.length, endLine + 20);
11880
12336
  const content = lines.slice(contextStart - 1, contextEnd).join("\n");
11881
- const ext = path31.extname(resolvedFile).toLowerCase();
12337
+ const ext = path32.extname(resolvedFile).toLowerCase();
11882
12338
  const languageMap = {
11883
12339
  ".ts": "typescript",
11884
12340
  ".tsx": "typescript",
@@ -12013,10 +12469,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12013
12469
  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() } });
12014
12470
  }
12015
12471
  });
12016
- if (fs24.existsSync(WEB_DIST)) {
12472
+ if (fs25.existsSync(WEB_DIST)) {
12017
12473
  app.use(express.static(WEB_DIST));
12018
12474
  app.get("/{*path}", (_req, res) => {
12019
- res.sendFile(path31.join(WEB_DIST, "index.html"));
12475
+ res.sendFile(path32.join(WEB_DIST, "index.html"));
12020
12476
  });
12021
12477
  }
12022
12478
  app.use("/admin", requireRole("admin"));
@@ -12176,6 +12632,6 @@ function mergeSearchResults(...perRepoResults) {
12176
12632
  return reciprocalRankFusion(...perRepoResults);
12177
12633
  }
12178
12634
 
12179
- export { AstCache, BindingTracker, DbManager, addBinding, addClustersToGraph, addMember, buildCallEdges, buildHeritageEdges, classifyCall, clusterPhase, computeMRO, createApp, createKnowledgeGraph, createMcpServer, createScope, deleteGroup, detectCommunities, detectOverrides, findEntryPoints, flowPhase, generateEdgeId, generateNodeId, getAllLanguageModules, getDbPath, getLanguage, getLanguageModule, getParser, groupExists, initParser, isTreeSitterAvailable, listGroups, loadGraphToDB, loadGroup, loadMetadata, loadRegistry, loadSyncResult, mergeSearchResults, parsePhase, parseSource, queryGroup, reciprocalRankFusion, removeMember, removeRepo, resolveBinding, resolveImports, resolvePhase, runPipeline, runQuery, runQueryMatches, saveGroup, saveMetadata, saveSyncResult, scanPhase, startHttpServer, startMcpStdio, structurePhase, syncGroup, textSearch, topologicalSort, traceFlow, upsertRepo, validateDAG };
12635
+ export { AstCache, BindingTracker, Bm25Index, DbManager, addBinding, addClustersToGraph, addMember, buildCallEdges, buildHeritageEdges, classifyCall, clusterPhase, computeMRO, createApp, createKnowledgeGraph, createMcpServer, createScope, deleteGroup, detectCommunities, detectOverrides, findEntryPoints, flowPhase, generateEdgeId, generateNodeId, getAllLanguageModules, getBm25DbPath, getDbPath, getLanguage, getLanguageModule, getParser, groupExists, initParser, isTreeSitterAvailable, listGroups, loadGraphToDB, loadGroup, loadMetadata, loadRegistry, loadSyncResult, mergeSearchResults, parsePhase, parseSource, queryGroup, reciprocalRankFusion, removeMember, removeRepo, resolveBinding, resolveImports, resolvePhase, runPipeline, runQuery, runQueryMatches, saveGroup, saveMetadata, saveSyncResult, scanPhase, startHttpServer, startMcpStdio, structurePhase, syncGroup, textSearch, topologicalSort, traceFlow, upsertRepo, validateDAG };
12180
12636
  //# sourceMappingURL=index.js.map
12181
12637
  //# sourceMappingURL=index.js.map