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