@vohongtho.infotech/code-intel 0.7.0 → 0.8.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 +6 -3
- package/dist/cli/main.js +1408 -378
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +851 -186
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-yDTUrrnL.js → es-J7AmFCht.js} +1 -1
- package/dist/web/assets/{index-B4bH2ZP8.js → index-upRm-kxQ.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
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 fs34, { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import path35, { 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';
|
|
@@ -326,7 +326,7 @@ var init_logger = __esm({
|
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
/** Global log directory: ~/.code-intel/logs */
|
|
329
|
-
static LOG_DIR =
|
|
329
|
+
static LOG_DIR = path35.join(os12.homedir(), ".code-intel", "logs");
|
|
330
330
|
static getLogger() {
|
|
331
331
|
if (!_Logger.instance) {
|
|
332
332
|
const isProduction = process.env.NODE_ENV === "production";
|
|
@@ -335,12 +335,12 @@ var init_logger = __esm({
|
|
|
335
335
|
transports.push(new winston.transports.Console());
|
|
336
336
|
if (!isProduction) {
|
|
337
337
|
try {
|
|
338
|
-
if (!
|
|
339
|
-
|
|
338
|
+
if (!fs34.existsSync(_Logger.LOG_DIR)) {
|
|
339
|
+
fs34.mkdirSync(_Logger.LOG_DIR, { recursive: true });
|
|
340
340
|
}
|
|
341
341
|
transports.push(
|
|
342
342
|
new DailyRotateFile({
|
|
343
|
-
filename:
|
|
343
|
+
filename: path35.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
|
|
344
344
|
datePattern: "YYYY-MM-DD",
|
|
345
345
|
maxSize: "20m",
|
|
346
346
|
maxFiles: "14d"
|
|
@@ -540,25 +540,25 @@ function validateDAG(phases) {
|
|
|
540
540
|
const visiting = /* @__PURE__ */ new Set();
|
|
541
541
|
const visited = /* @__PURE__ */ new Set();
|
|
542
542
|
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
543
|
-
function dfs(name,
|
|
543
|
+
function dfs(name, path36) {
|
|
544
544
|
if (visiting.has(name)) {
|
|
545
|
-
const cycleStart =
|
|
546
|
-
const cycle =
|
|
545
|
+
const cycleStart = path36.indexOf(name);
|
|
546
|
+
const cycle = path36.slice(cycleStart).concat(name);
|
|
547
547
|
errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
548
548
|
return true;
|
|
549
549
|
}
|
|
550
550
|
if (visited.has(name)) return false;
|
|
551
551
|
visiting.add(name);
|
|
552
|
-
|
|
552
|
+
path36.push(name);
|
|
553
553
|
const phase = phaseMap.get(name);
|
|
554
554
|
if (phase) {
|
|
555
555
|
for (const dep of phase.dependencies) {
|
|
556
|
-
if (dfs(dep,
|
|
556
|
+
if (dfs(dep, path36)) return true;
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
559
|
visiting.delete(name);
|
|
560
560
|
visited.add(name);
|
|
561
|
-
|
|
561
|
+
path36.pop();
|
|
562
562
|
return false;
|
|
563
563
|
}
|
|
564
564
|
for (const phase of phases) {
|
|
@@ -818,11 +818,11 @@ var init_id_generator = __esm({
|
|
|
818
818
|
}
|
|
819
819
|
});
|
|
820
820
|
function findBundledWasmDir() {
|
|
821
|
-
const fileDir =
|
|
821
|
+
const fileDir = path35.dirname(fileURLToPath(import.meta.url));
|
|
822
822
|
const candidates = [
|
|
823
|
-
|
|
823
|
+
path35.join(fileDir, "wasm"),
|
|
824
824
|
// dist/index.js → dist/wasm/
|
|
825
|
-
|
|
825
|
+
path35.join(fileDir, "../wasm")
|
|
826
826
|
// dist/cli/main.js → dist/wasm/
|
|
827
827
|
];
|
|
828
828
|
for (const candidate of candidates) {
|
|
@@ -863,7 +863,7 @@ function wasmPath(lang) {
|
|
|
863
863
|
}
|
|
864
864
|
const bundled = BUNDLED_WASM_MAP[lang];
|
|
865
865
|
if (bundled) {
|
|
866
|
-
const bundledPath =
|
|
866
|
+
const bundledPath = path35.join(_bundledWasmDir, bundled);
|
|
867
867
|
if (existsSync(bundledPath)) return bundledPath;
|
|
868
868
|
}
|
|
869
869
|
return null;
|
|
@@ -876,14 +876,14 @@ async function initParser() {
|
|
|
876
876
|
}
|
|
877
877
|
async function getLanguage(lang) {
|
|
878
878
|
if (languageCache.has(lang)) return languageCache.get(lang);
|
|
879
|
-
const
|
|
880
|
-
if (!
|
|
879
|
+
const path36 = wasmPath(lang);
|
|
880
|
+
if (!path36) {
|
|
881
881
|
languageCache.set(lang, null);
|
|
882
882
|
return null;
|
|
883
883
|
}
|
|
884
884
|
try {
|
|
885
885
|
await initParser();
|
|
886
|
-
const language = await Language.load(
|
|
886
|
+
const language = await Language.load(path36);
|
|
887
887
|
languageCache.set(lang, language);
|
|
888
888
|
return language;
|
|
889
889
|
} catch {
|
|
@@ -2308,7 +2308,7 @@ var init_parse_phase = __esm({
|
|
|
2308
2308
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
2309
2309
|
await Promise.all(batch.map(async (filePath) => {
|
|
2310
2310
|
try {
|
|
2311
|
-
const source = await
|
|
2311
|
+
const source = await fs34.promises.readFile(filePath, "utf-8");
|
|
2312
2312
|
context2.fileCache.set(filePath, source);
|
|
2313
2313
|
} catch {
|
|
2314
2314
|
}
|
|
@@ -2321,14 +2321,14 @@ var init_parse_phase = __esm({
|
|
|
2321
2321
|
const lang = detectLanguage(filePath);
|
|
2322
2322
|
if (!lang) {
|
|
2323
2323
|
if (context2.verbose) {
|
|
2324
|
-
const relativePath2 =
|
|
2324
|
+
const relativePath2 = path35.relative(context2.workspaceRoot, filePath);
|
|
2325
2325
|
logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
|
|
2326
2326
|
}
|
|
2327
2327
|
continue;
|
|
2328
2328
|
}
|
|
2329
2329
|
const source = context2.fileCache.get(filePath);
|
|
2330
2330
|
if (!source) continue;
|
|
2331
|
-
const relativePath =
|
|
2331
|
+
const relativePath = path35.relative(context2.workspaceRoot, filePath);
|
|
2332
2332
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
2333
2333
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
2334
2334
|
if (fileNode) {
|
|
@@ -2574,11 +2574,11 @@ var init_resolve_phase = __esm({
|
|
|
2574
2574
|
let heritageEdges = 0;
|
|
2575
2575
|
const fileIndex = /* @__PURE__ */ new Map();
|
|
2576
2576
|
for (const fp of filePaths) {
|
|
2577
|
-
const rel =
|
|
2577
|
+
const rel = path35.relative(workspaceRoot, fp);
|
|
2578
2578
|
fileIndex.set(rel, fp);
|
|
2579
2579
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
2580
2580
|
if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
|
|
2581
|
-
const base =
|
|
2581
|
+
const base = path35.basename(rel, path35.extname(rel));
|
|
2582
2582
|
if (!fileIndex.has(base)) fileIndex.set(base, fp);
|
|
2583
2583
|
}
|
|
2584
2584
|
const symbolIndex = /* @__PURE__ */ new Map();
|
|
@@ -2609,7 +2609,7 @@ var init_resolve_phase = __esm({
|
|
|
2609
2609
|
for (const filePath of filePaths) {
|
|
2610
2610
|
const lang = detectLanguage(filePath);
|
|
2611
2611
|
if (!lang) continue;
|
|
2612
|
-
const relativePath =
|
|
2612
|
+
const relativePath = path35.relative(workspaceRoot, filePath);
|
|
2613
2613
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
2614
2614
|
const source = fileCache.get(filePath);
|
|
2615
2615
|
if (!source) continue;
|
|
@@ -2622,13 +2622,13 @@ var init_resolve_phase = __esm({
|
|
|
2622
2622
|
let resolvedRelPath = null;
|
|
2623
2623
|
if (cleaned.startsWith(".")) {
|
|
2624
2624
|
const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
|
|
2625
|
-
const fromDir =
|
|
2625
|
+
const fromDir = path35.dirname(relativePath);
|
|
2626
2626
|
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
|
|
2627
|
-
const candidate =
|
|
2628
|
-
const normalized =
|
|
2627
|
+
const candidate = path35.join(fromDir, cleanedNoJs + ext);
|
|
2628
|
+
const normalized = path35.normalize(candidate);
|
|
2629
2629
|
if (fileIndex.has(normalized)) {
|
|
2630
2630
|
const absPath = fileIndex.get(normalized);
|
|
2631
|
-
resolvedRelPath =
|
|
2631
|
+
resolvedRelPath = path35.relative(workspaceRoot, absPath);
|
|
2632
2632
|
break;
|
|
2633
2633
|
}
|
|
2634
2634
|
}
|
|
@@ -3093,7 +3093,7 @@ var init_db_manager = __esm({
|
|
|
3093
3093
|
this.dbPath = dbPath;
|
|
3094
3094
|
}
|
|
3095
3095
|
async init() {
|
|
3096
|
-
|
|
3096
|
+
fs34.mkdirSync(path35.dirname(this.dbPath), { recursive: true });
|
|
3097
3097
|
this.db = new Database$1(this.dbPath);
|
|
3098
3098
|
await this.db.init();
|
|
3099
3099
|
this.conn = new Connection(this.db);
|
|
@@ -3190,7 +3190,7 @@ var init_schema = __esm({
|
|
|
3190
3190
|
}
|
|
3191
3191
|
});
|
|
3192
3192
|
function writeNodeCSVs(graph, outputDir) {
|
|
3193
|
-
|
|
3193
|
+
fs34.mkdirSync(outputDir, { recursive: true });
|
|
3194
3194
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
3195
3195
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
3196
3196
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -3198,7 +3198,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
3198
3198
|
const table = NODE_TABLE_MAP[node.kind];
|
|
3199
3199
|
if (!tableBuffers.has(table)) {
|
|
3200
3200
|
tableBuffers.set(table, [header]);
|
|
3201
|
-
tableFilePaths.set(table,
|
|
3201
|
+
tableFilePaths.set(table, path35.join(outputDir, `${table}.csv`));
|
|
3202
3202
|
}
|
|
3203
3203
|
tableBuffers.get(table).push(
|
|
3204
3204
|
csvRow([
|
|
@@ -3218,12 +3218,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
3218
3218
|
);
|
|
3219
3219
|
}
|
|
3220
3220
|
for (const [table, lines] of tableBuffers) {
|
|
3221
|
-
|
|
3221
|
+
fs34.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
3222
3222
|
}
|
|
3223
3223
|
return tableFilePaths;
|
|
3224
3224
|
}
|
|
3225
3225
|
function writeEdgeCSV(graph, outputDir) {
|
|
3226
|
-
|
|
3226
|
+
fs34.mkdirSync(outputDir, { recursive: true });
|
|
3227
3227
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
3228
3228
|
const groups = /* @__PURE__ */ new Map();
|
|
3229
3229
|
for (const edge of graph.allEdges()) {
|
|
@@ -3234,7 +3234,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
3234
3234
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
3235
3235
|
const key = `${fromTable}->${toTable}`;
|
|
3236
3236
|
if (!groups.has(key)) {
|
|
3237
|
-
const filePath =
|
|
3237
|
+
const filePath = path35.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
3238
3238
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
3239
3239
|
}
|
|
3240
3240
|
groups.get(key).lines.push(
|
|
@@ -3249,7 +3249,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
3249
3249
|
}
|
|
3250
3250
|
const result = [];
|
|
3251
3251
|
for (const group of groups.values()) {
|
|
3252
|
-
|
|
3252
|
+
fs34.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
3253
3253
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
3254
3254
|
}
|
|
3255
3255
|
return result;
|
|
@@ -3292,7 +3292,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3292
3292
|
} catch {
|
|
3293
3293
|
}
|
|
3294
3294
|
}
|
|
3295
|
-
const tmpDir =
|
|
3295
|
+
const tmpDir = fs34.mkdtempSync(path35.join(os12.tmpdir(), "code-intel-csv-"));
|
|
3296
3296
|
try {
|
|
3297
3297
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
3298
3298
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -3311,8 +3311,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3311
3311
|
}
|
|
3312
3312
|
let nodeCount = 0;
|
|
3313
3313
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
3314
|
-
if (!
|
|
3315
|
-
const stat =
|
|
3314
|
+
if (!fs34.existsSync(csvPath)) continue;
|
|
3315
|
+
const stat = fs34.statSync(csvPath);
|
|
3316
3316
|
if (stat.size < 50) continue;
|
|
3317
3317
|
try {
|
|
3318
3318
|
await dbManager.execute(
|
|
@@ -3325,8 +3325,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3325
3325
|
}
|
|
3326
3326
|
let edgeCount = 0;
|
|
3327
3327
|
for (const group of edgeGroups) {
|
|
3328
|
-
if (!
|
|
3329
|
-
const stat =
|
|
3328
|
+
if (!fs34.existsSync(group.filePath)) continue;
|
|
3329
|
+
const stat = fs34.statSync(group.filePath);
|
|
3330
3330
|
if (stat.size < 50) continue;
|
|
3331
3331
|
try {
|
|
3332
3332
|
await dbManager.execute(
|
|
@@ -3340,7 +3340,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
3340
3340
|
return { nodeCount, edgeCount };
|
|
3341
3341
|
} finally {
|
|
3342
3342
|
try {
|
|
3343
|
-
|
|
3343
|
+
fs34.rmSync(tmpDir, { recursive: true, force: true });
|
|
3344
3344
|
} catch {
|
|
3345
3345
|
}
|
|
3346
3346
|
}
|
|
@@ -3442,15 +3442,15 @@ var init_graph_loader = __esm({
|
|
|
3442
3442
|
});
|
|
3443
3443
|
function loadRegistry() {
|
|
3444
3444
|
try {
|
|
3445
|
-
const data =
|
|
3445
|
+
const data = fs34.readFileSync(REPOS_FILE, "utf-8");
|
|
3446
3446
|
return JSON.parse(data);
|
|
3447
3447
|
} catch {
|
|
3448
3448
|
return [];
|
|
3449
3449
|
}
|
|
3450
3450
|
}
|
|
3451
3451
|
function saveRegistry(entries) {
|
|
3452
|
-
|
|
3453
|
-
|
|
3452
|
+
fs34.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
3453
|
+
fs34.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
3454
3454
|
}
|
|
3455
3455
|
function upsertRepo(entry) {
|
|
3456
3456
|
const entries = loadRegistry();
|
|
@@ -3469,28 +3469,28 @@ function removeRepo(repoPath) {
|
|
|
3469
3469
|
var GLOBAL_DIR, REPOS_FILE;
|
|
3470
3470
|
var init_repo_registry = __esm({
|
|
3471
3471
|
"src/storage/repo-registry.ts"() {
|
|
3472
|
-
GLOBAL_DIR =
|
|
3473
|
-
REPOS_FILE =
|
|
3472
|
+
GLOBAL_DIR = path35.join(os12.homedir(), ".code-intel");
|
|
3473
|
+
REPOS_FILE = path35.join(GLOBAL_DIR, "repos.json");
|
|
3474
3474
|
}
|
|
3475
3475
|
});
|
|
3476
3476
|
function saveMetadata(repoDir, metadata) {
|
|
3477
|
-
const metaDir =
|
|
3478
|
-
|
|
3479
|
-
|
|
3477
|
+
const metaDir = path35.join(repoDir, ".code-intel");
|
|
3478
|
+
fs34.mkdirSync(metaDir, { recursive: true });
|
|
3479
|
+
fs34.writeFileSync(path35.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
3480
3480
|
}
|
|
3481
3481
|
function loadMetadata(repoDir) {
|
|
3482
3482
|
try {
|
|
3483
|
-
const data =
|
|
3483
|
+
const data = fs34.readFileSync(path35.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
3484
3484
|
return JSON.parse(data);
|
|
3485
3485
|
} catch {
|
|
3486
3486
|
return null;
|
|
3487
3487
|
}
|
|
3488
3488
|
}
|
|
3489
3489
|
function getDbPath(repoDir) {
|
|
3490
|
-
return
|
|
3490
|
+
return path35.join(repoDir, ".code-intel", "graph.db");
|
|
3491
3491
|
}
|
|
3492
3492
|
function getVectorDbPath(repoDir) {
|
|
3493
|
-
return
|
|
3493
|
+
return path35.join(repoDir, ".code-intel", "vector.db");
|
|
3494
3494
|
}
|
|
3495
3495
|
var init_metadata = __esm({
|
|
3496
3496
|
"src/storage/metadata.ts"() {
|
|
@@ -3546,27 +3546,27 @@ __export(group_registry_exports, {
|
|
|
3546
3546
|
saveSyncResult: () => saveSyncResult
|
|
3547
3547
|
});
|
|
3548
3548
|
function groupFile(name) {
|
|
3549
|
-
return
|
|
3549
|
+
return path35.join(GROUPS_DIR, `${name}.json`);
|
|
3550
3550
|
}
|
|
3551
3551
|
function loadGroup(name) {
|
|
3552
3552
|
try {
|
|
3553
|
-
return JSON.parse(
|
|
3553
|
+
return JSON.parse(fs34.readFileSync(groupFile(name), "utf-8"));
|
|
3554
3554
|
} catch {
|
|
3555
3555
|
return null;
|
|
3556
3556
|
}
|
|
3557
3557
|
}
|
|
3558
3558
|
function saveGroup(group) {
|
|
3559
|
-
|
|
3560
|
-
|
|
3559
|
+
fs34.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
3560
|
+
fs34.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
3561
3561
|
}
|
|
3562
3562
|
function listGroups() {
|
|
3563
3563
|
const groups = [];
|
|
3564
3564
|
try {
|
|
3565
|
-
for (const file of
|
|
3565
|
+
for (const file of fs34.readdirSync(GROUPS_DIR)) {
|
|
3566
3566
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
3567
3567
|
try {
|
|
3568
3568
|
const g = JSON.parse(
|
|
3569
|
-
|
|
3569
|
+
fs34.readFileSync(path35.join(GROUPS_DIR, file), "utf-8")
|
|
3570
3570
|
);
|
|
3571
3571
|
groups.push(g);
|
|
3572
3572
|
} catch {
|
|
@@ -3578,16 +3578,16 @@ function listGroups() {
|
|
|
3578
3578
|
}
|
|
3579
3579
|
function deleteGroup(name) {
|
|
3580
3580
|
try {
|
|
3581
|
-
|
|
3581
|
+
fs34.unlinkSync(groupFile(name));
|
|
3582
3582
|
} catch {
|
|
3583
3583
|
}
|
|
3584
3584
|
try {
|
|
3585
|
-
|
|
3585
|
+
fs34.unlinkSync(path35.join(GROUPS_DIR, `${name}.sync.json`));
|
|
3586
3586
|
} catch {
|
|
3587
3587
|
}
|
|
3588
3588
|
}
|
|
3589
3589
|
function groupExists(name) {
|
|
3590
|
-
return
|
|
3590
|
+
return fs34.existsSync(groupFile(name));
|
|
3591
3591
|
}
|
|
3592
3592
|
function addMember(groupName, member) {
|
|
3593
3593
|
const group = loadGroup(groupName);
|
|
@@ -3613,16 +3613,16 @@ function removeMember(groupName, groupPath) {
|
|
|
3613
3613
|
return group;
|
|
3614
3614
|
}
|
|
3615
3615
|
function saveSyncResult(result) {
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3616
|
+
fs34.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
3617
|
+
fs34.writeFileSync(
|
|
3618
|
+
path35.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
3619
3619
|
JSON.stringify(result, null, 2) + "\n"
|
|
3620
3620
|
);
|
|
3621
3621
|
}
|
|
3622
3622
|
function loadSyncResult(groupName) {
|
|
3623
3623
|
try {
|
|
3624
3624
|
return JSON.parse(
|
|
3625
|
-
|
|
3625
|
+
fs34.readFileSync(path35.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
3626
3626
|
);
|
|
3627
3627
|
} catch {
|
|
3628
3628
|
return null;
|
|
@@ -3631,7 +3631,7 @@ function loadSyncResult(groupName) {
|
|
|
3631
3631
|
var GROUPS_DIR;
|
|
3632
3632
|
var init_group_registry = __esm({
|
|
3633
3633
|
"src/multi-repo/group-registry.ts"() {
|
|
3634
|
-
GROUPS_DIR =
|
|
3634
|
+
GROUPS_DIR = path35.join(os12.homedir(), ".code-intel", "groups");
|
|
3635
3635
|
}
|
|
3636
3636
|
});
|
|
3637
3637
|
|
|
@@ -3716,12 +3716,12 @@ function scanForFiles(root, matcher, maxDepth = 2) {
|
|
|
3716
3716
|
if (depth > maxDepth) return;
|
|
3717
3717
|
let entries;
|
|
3718
3718
|
try {
|
|
3719
|
-
entries =
|
|
3719
|
+
entries = fs34.readdirSync(dir, { withFileTypes: true });
|
|
3720
3720
|
} catch {
|
|
3721
3721
|
return;
|
|
3722
3722
|
}
|
|
3723
3723
|
for (const entry of entries) {
|
|
3724
|
-
const full =
|
|
3724
|
+
const full = path35.join(dir, entry.name);
|
|
3725
3725
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
3726
3726
|
walk2(full, depth + 1);
|
|
3727
3727
|
} else if (entry.isFile() && matcher(entry.name)) {
|
|
@@ -3737,8 +3737,8 @@ var init_file_scanner = __esm({
|
|
|
3737
3737
|
}
|
|
3738
3738
|
});
|
|
3739
3739
|
function tryParseFile(filePath) {
|
|
3740
|
-
const ext =
|
|
3741
|
-
const content =
|
|
3740
|
+
const ext = path35.extname(filePath).toLowerCase();
|
|
3741
|
+
const content = fs34.readFileSync(filePath, "utf-8");
|
|
3742
3742
|
if (ext === ".json") {
|
|
3743
3743
|
try {
|
|
3744
3744
|
return JSON.parse(content);
|
|
@@ -3807,7 +3807,7 @@ async function parseGraphQLContracts(repoRoot) {
|
|
|
3807
3807
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
3808
3808
|
const contracts = [];
|
|
3809
3809
|
for (const filePath of files) {
|
|
3810
|
-
const content =
|
|
3810
|
+
const content = fs34.readFileSync(filePath, "utf-8");
|
|
3811
3811
|
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
3812
3812
|
let match;
|
|
3813
3813
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
@@ -3843,7 +3843,7 @@ async function parseProtoContracts(repoRoot) {
|
|
|
3843
3843
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
3844
3844
|
const contracts = [];
|
|
3845
3845
|
for (const filePath of files) {
|
|
3846
|
-
const content =
|
|
3846
|
+
const content = fs34.readFileSync(filePath, "utf-8");
|
|
3847
3847
|
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
3848
3848
|
let serviceMatch;
|
|
3849
3849
|
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
@@ -4061,8 +4061,8 @@ async function syncGroup(group) {
|
|
|
4061
4061
|
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
4062
4062
|
continue;
|
|
4063
4063
|
}
|
|
4064
|
-
const dbPath =
|
|
4065
|
-
if (!
|
|
4064
|
+
const dbPath = path35.join(regEntry.path, ".code-intel", "graph.db");
|
|
4065
|
+
if (!fs34.existsSync(dbPath)) {
|
|
4066
4066
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
4067
4067
|
continue;
|
|
4068
4068
|
}
|
|
@@ -4175,10 +4175,10 @@ var init_codes = __esm({
|
|
|
4175
4175
|
}
|
|
4176
4176
|
});
|
|
4177
4177
|
function secureMkdir(dir) {
|
|
4178
|
-
|
|
4178
|
+
fs34.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
4179
4179
|
if (process.platform !== "win32") {
|
|
4180
4180
|
try {
|
|
4181
|
-
|
|
4181
|
+
fs34.chmodSync(dir, SECURE_DIR_MODE);
|
|
4182
4182
|
} catch {
|
|
4183
4183
|
}
|
|
4184
4184
|
}
|
|
@@ -4186,22 +4186,22 @@ function secureMkdir(dir) {
|
|
|
4186
4186
|
function secureChmodFile(file) {
|
|
4187
4187
|
if (process.platform === "win32") return;
|
|
4188
4188
|
try {
|
|
4189
|
-
|
|
4189
|
+
fs34.chmodSync(file, SECURE_FILE_MODE);
|
|
4190
4190
|
} catch {
|
|
4191
4191
|
}
|
|
4192
4192
|
}
|
|
4193
4193
|
function secureWriteFile(file, data) {
|
|
4194
|
-
secureMkdir(
|
|
4195
|
-
|
|
4194
|
+
secureMkdir(path35.dirname(file));
|
|
4195
|
+
fs34.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
|
|
4196
4196
|
secureChmodFile(file);
|
|
4197
4197
|
}
|
|
4198
4198
|
function tightenDbFiles(dir) {
|
|
4199
4199
|
if (process.platform === "win32") return;
|
|
4200
|
-
if (!
|
|
4201
|
-
for (const name of
|
|
4200
|
+
if (!fs34.existsSync(dir)) return;
|
|
4201
|
+
for (const name of fs34.readdirSync(dir)) {
|
|
4202
4202
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
4203
4203
|
try {
|
|
4204
|
-
|
|
4204
|
+
fs34.chmodSync(path35.join(dir, name), SECURE_FILE_MODE);
|
|
4205
4205
|
} catch {
|
|
4206
4206
|
}
|
|
4207
4207
|
}
|
|
@@ -4215,7 +4215,7 @@ var init_fs_secure = __esm({
|
|
|
4215
4215
|
}
|
|
4216
4216
|
});
|
|
4217
4217
|
function getUsersDBPath() {
|
|
4218
|
-
return process.env["CODE_INTEL_USERS_DB_PATH"] ??
|
|
4218
|
+
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path35.join(os12.homedir(), ".code-intel", "users.db");
|
|
4219
4219
|
}
|
|
4220
4220
|
function getOrCreateUsersDB() {
|
|
4221
4221
|
if (!_usersDB) {
|
|
@@ -4231,7 +4231,7 @@ var init_users_db = __esm({
|
|
|
4231
4231
|
UsersDB = class {
|
|
4232
4232
|
db;
|
|
4233
4233
|
constructor(dbPath) {
|
|
4234
|
-
const dir =
|
|
4234
|
+
const dir = path35.dirname(dbPath);
|
|
4235
4235
|
secureMkdir(dir);
|
|
4236
4236
|
this.db = new Database(dbPath);
|
|
4237
4237
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -4508,7 +4508,7 @@ function getScryptN() {
|
|
|
4508
4508
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
4509
4509
|
}
|
|
4510
4510
|
function getSecretsPath() {
|
|
4511
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ??
|
|
4511
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path35.join(os12.homedir(), ".code-intel", ".secrets");
|
|
4512
4512
|
}
|
|
4513
4513
|
function getMasterPassword() {
|
|
4514
4514
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
@@ -4550,12 +4550,12 @@ function decryptSecrets(encrypted) {
|
|
|
4550
4550
|
return JSON.parse(plaintext.toString("utf8"));
|
|
4551
4551
|
}
|
|
4552
4552
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
4553
|
-
if (!
|
|
4554
|
-
const blob =
|
|
4553
|
+
if (!fs34.existsSync(secretsPath)) return {};
|
|
4554
|
+
const blob = fs34.readFileSync(secretsPath);
|
|
4555
4555
|
return decryptSecrets(blob);
|
|
4556
4556
|
}
|
|
4557
4557
|
function saveSecrets(blob, secretsPath = getSecretsPath()) {
|
|
4558
|
-
secureMkdir(
|
|
4558
|
+
secureMkdir(path35.dirname(secretsPath));
|
|
4559
4559
|
const encrypted = encryptSecrets(blob);
|
|
4560
4560
|
secureWriteFile(secretsPath, encrypted);
|
|
4561
4561
|
secureChmodFile(secretsPath);
|
|
@@ -6039,14 +6039,14 @@ function detectDeadCode(graph) {
|
|
|
6039
6039
|
if (meta?.deprecated === true) continue;
|
|
6040
6040
|
if (ENTRY_POINT_NAME_RE.test(node.name)) continue;
|
|
6041
6041
|
if (entryPointIds.has(node.id)) continue;
|
|
6042
|
-
let
|
|
6042
|
+
let hasCallers2 = false;
|
|
6043
6043
|
for (const edge of graph.findEdgesTo(node.id)) {
|
|
6044
6044
|
if (edge.kind === "calls") {
|
|
6045
|
-
|
|
6045
|
+
hasCallers2 = true;
|
|
6046
6046
|
break;
|
|
6047
6047
|
}
|
|
6048
6048
|
}
|
|
6049
|
-
if (
|
|
6049
|
+
if (hasCallers2) continue;
|
|
6050
6050
|
let hasImporters = false;
|
|
6051
6051
|
for (const edge of graph.findEdgesTo(node.id)) {
|
|
6052
6052
|
if (edge.kind === "imports") {
|
|
@@ -6298,6 +6298,550 @@ var init_health_score = __esm({
|
|
|
6298
6298
|
}
|
|
6299
6299
|
});
|
|
6300
6300
|
|
|
6301
|
+
// src/analysis/deprecated-detector.ts
|
|
6302
|
+
var deprecated_detector_exports = {};
|
|
6303
|
+
__export(deprecated_detector_exports, {
|
|
6304
|
+
DeprecatedDetector: () => DeprecatedDetector
|
|
6305
|
+
});
|
|
6306
|
+
var BUILTIN_DEPRECATED, DeprecatedDetector;
|
|
6307
|
+
var init_deprecated_detector = __esm({
|
|
6308
|
+
"src/analysis/deprecated-detector.ts"() {
|
|
6309
|
+
BUILTIN_DEPRECATED = [
|
|
6310
|
+
{ pattern: "url.parse", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
|
|
6311
|
+
{ pattern: "url.resolve", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
|
|
6312
|
+
{ pattern: "url.format", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
|
|
6313
|
+
{ pattern: "fs.exists", message: "deprecated \u2014 use fs.access instead" },
|
|
6314
|
+
{ pattern: "crypto.createCipher", message: "deprecated \u2014 use crypto.createCipheriv instead" },
|
|
6315
|
+
{ pattern: "crypto.createDecipher", message: "deprecated \u2014 use crypto.createDecipheriv instead" },
|
|
6316
|
+
{ pattern: "new Buffer()", message: "deprecated \u2014 use Buffer.from() instead" },
|
|
6317
|
+
{ pattern: "domain.create", message: "deprecated \u2014 the domain module is discouraged" },
|
|
6318
|
+
{ pattern: "process.binding", message: "deprecated internal API" }
|
|
6319
|
+
];
|
|
6320
|
+
DeprecatedDetector = class {
|
|
6321
|
+
tagDeprecated(graph) {
|
|
6322
|
+
for (const node of graph.allNodes()) {
|
|
6323
|
+
if (!node.metadata) node.metadata = {};
|
|
6324
|
+
if (node.metadata["deprecated"] === true) continue;
|
|
6325
|
+
let message;
|
|
6326
|
+
const jsdoc = node.metadata["jsdoc"];
|
|
6327
|
+
const comment = node.metadata["comment"];
|
|
6328
|
+
if (jsdoc?.includes("@deprecated") || comment?.includes("@deprecated")) {
|
|
6329
|
+
const src = jsdoc ?? comment ?? "";
|
|
6330
|
+
const match = src.match(/@deprecated\s+(.*)/);
|
|
6331
|
+
message = match?.[1]?.trim() || "deprecated";
|
|
6332
|
+
}
|
|
6333
|
+
if (!message && node.metadata["deprecated"] === true) {
|
|
6334
|
+
message = node.metadata["deprecationMessage"] ?? "deprecated";
|
|
6335
|
+
}
|
|
6336
|
+
if (!message) {
|
|
6337
|
+
const annotations = node.metadata["annotations"];
|
|
6338
|
+
if (Array.isArray(annotations) && annotations.includes("Deprecated")) {
|
|
6339
|
+
message = "marked @Deprecated";
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
if (!message) {
|
|
6343
|
+
const attributes = node.metadata["attributes"];
|
|
6344
|
+
if (Array.isArray(attributes) && attributes.includes("deprecated")) {
|
|
6345
|
+
message = "marked #[deprecated]";
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
if (!message) {
|
|
6349
|
+
for (const entry of BUILTIN_DEPRECATED) {
|
|
6350
|
+
if (node.name === entry.pattern || node.name.includes(entry.pattern)) {
|
|
6351
|
+
message = entry.message;
|
|
6352
|
+
break;
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
if (message) {
|
|
6357
|
+
node.metadata["deprecated"] = true;
|
|
6358
|
+
node.metadata["deprecationMessage"] = message;
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
detect(graph, scope) {
|
|
6363
|
+
const findings = [];
|
|
6364
|
+
for (const node of graph.allNodes()) {
|
|
6365
|
+
if (!node.metadata?.["deprecated"]) continue;
|
|
6366
|
+
const callers = [];
|
|
6367
|
+
for (const edge of graph.findEdgesTo(node.id)) {
|
|
6368
|
+
if (edge.kind !== "calls" && edge.kind !== "deprecated_use") continue;
|
|
6369
|
+
const caller = graph.getNode(edge.source);
|
|
6370
|
+
if (!caller) continue;
|
|
6371
|
+
if (scope && !caller.filePath.includes(scope)) continue;
|
|
6372
|
+
callers.push({ name: caller.name, filePath: caller.filePath });
|
|
6373
|
+
const edgeId = `dep_use_${edge.source}_${node.id}`;
|
|
6374
|
+
if (!graph.getEdge(edgeId)) {
|
|
6375
|
+
graph.addEdge({ id: edgeId, source: edge.source, target: node.id, kind: "deprecated_use" });
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
findings.push({
|
|
6379
|
+
symbol: node.name,
|
|
6380
|
+
filePath: node.filePath,
|
|
6381
|
+
deprecationMessage: node.metadata?.["deprecationMessage"] ?? "deprecated",
|
|
6382
|
+
callers
|
|
6383
|
+
});
|
|
6384
|
+
}
|
|
6385
|
+
return findings;
|
|
6386
|
+
}
|
|
6387
|
+
};
|
|
6388
|
+
}
|
|
6389
|
+
});
|
|
6390
|
+
|
|
6391
|
+
// src/analysis/complexity.ts
|
|
6392
|
+
var complexity_exports = {};
|
|
6393
|
+
__export(complexity_exports, {
|
|
6394
|
+
computeComplexity: () => computeComplexity
|
|
6395
|
+
});
|
|
6396
|
+
function getSeverity(cyclomatic) {
|
|
6397
|
+
if (cyclomatic <= 5) return "LOW";
|
|
6398
|
+
if (cyclomatic <= 10) return "MEDIUM";
|
|
6399
|
+
if (cyclomatic <= 20) return "HIGH";
|
|
6400
|
+
return "CRITICAL";
|
|
6401
|
+
}
|
|
6402
|
+
function computeComplexity(graph, scope) {
|
|
6403
|
+
const results = [];
|
|
6404
|
+
for (const node of graph.allNodes()) {
|
|
6405
|
+
if (node.kind !== "function" && node.kind !== "method") continue;
|
|
6406
|
+
if (scope && !node.filePath.startsWith(scope)) continue;
|
|
6407
|
+
let outgoingCalls = 0;
|
|
6408
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
6409
|
+
if (edge.kind === "calls") outgoingCalls++;
|
|
6410
|
+
}
|
|
6411
|
+
let cyclomatic;
|
|
6412
|
+
const meta = node.metadata;
|
|
6413
|
+
const metaComplexity = meta?.complexity;
|
|
6414
|
+
if (typeof metaComplexity?.cyclomatic === "number") {
|
|
6415
|
+
cyclomatic = metaComplexity.cyclomatic;
|
|
6416
|
+
} else {
|
|
6417
|
+
cyclomatic = 1 + Math.floor(outgoingCalls / 2);
|
|
6418
|
+
}
|
|
6419
|
+
cyclomatic = Math.min(cyclomatic, 50);
|
|
6420
|
+
let cognitive;
|
|
6421
|
+
if (typeof metaComplexity?.cognitive === "number") {
|
|
6422
|
+
cognitive = metaComplexity.cognitive;
|
|
6423
|
+
} else {
|
|
6424
|
+
cognitive = Math.ceil(cyclomatic * 1.3);
|
|
6425
|
+
}
|
|
6426
|
+
results.push({
|
|
6427
|
+
nodeId: node.id,
|
|
6428
|
+
name: node.name,
|
|
6429
|
+
filePath: node.filePath,
|
|
6430
|
+
cyclomatic,
|
|
6431
|
+
cognitive,
|
|
6432
|
+
severity: getSeverity(cyclomatic)
|
|
6433
|
+
});
|
|
6434
|
+
}
|
|
6435
|
+
return results.sort((a, b) => b.cyclomatic - a.cyclomatic);
|
|
6436
|
+
}
|
|
6437
|
+
var init_complexity = __esm({
|
|
6438
|
+
"src/analysis/complexity.ts"() {
|
|
6439
|
+
}
|
|
6440
|
+
});
|
|
6441
|
+
|
|
6442
|
+
// src/analysis/test-coverage.ts
|
|
6443
|
+
var test_coverage_exports = {};
|
|
6444
|
+
__export(test_coverage_exports, {
|
|
6445
|
+
computeCoverage: () => computeCoverage
|
|
6446
|
+
});
|
|
6447
|
+
function isTestFile(filePath) {
|
|
6448
|
+
if (filePath.includes(".test.") || filePath.includes(".spec.")) return true;
|
|
6449
|
+
if (filePath.includes("_test.") || filePath.endsWith("_test.go")) return true;
|
|
6450
|
+
if (filePath.includes("__tests__")) return true;
|
|
6451
|
+
const base = path35.basename(filePath);
|
|
6452
|
+
if (base.startsWith("Test") && filePath.endsWith(".java")) return true;
|
|
6453
|
+
return false;
|
|
6454
|
+
}
|
|
6455
|
+
function computeBlastRadius(graph, nodeId) {
|
|
6456
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6457
|
+
const queue = [{ id: nodeId, depth: 0 }];
|
|
6458
|
+
while (queue.length > 0) {
|
|
6459
|
+
const { id, depth } = queue.shift();
|
|
6460
|
+
if (visited.has(id)) continue;
|
|
6461
|
+
visited.add(id);
|
|
6462
|
+
if (depth >= 3) continue;
|
|
6463
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
6464
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
6465
|
+
if (!visited.has(edge.source)) {
|
|
6466
|
+
queue.push({ id: edge.source, depth: depth + 1 });
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
return Math.max(0, visited.size - 1);
|
|
6472
|
+
}
|
|
6473
|
+
function getRisk(blastRadius) {
|
|
6474
|
+
if (blastRadius > 20) return "HIGH";
|
|
6475
|
+
if (blastRadius >= 5) return "MEDIUM";
|
|
6476
|
+
return "LOW";
|
|
6477
|
+
}
|
|
6478
|
+
function computeCoverage(graph, scope) {
|
|
6479
|
+
const testFilePaths = /* @__PURE__ */ new Set();
|
|
6480
|
+
for (const node of graph.allNodes()) {
|
|
6481
|
+
if (isTestFile(node.filePath)) testFilePaths.add(node.filePath);
|
|
6482
|
+
}
|
|
6483
|
+
const nodesImportedByTests = /* @__PURE__ */ new Set();
|
|
6484
|
+
for (const edge of graph.findEdgesByKind("imports")) {
|
|
6485
|
+
const sourceNode = graph.getNode(edge.source);
|
|
6486
|
+
if (sourceNode && isTestFile(sourceNode.filePath)) {
|
|
6487
|
+
nodesImportedByTests.add(edge.target);
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
const nodesWithTestedBy = /* @__PURE__ */ new Set();
|
|
6491
|
+
for (const edge of graph.findEdgesByKind("tested_by")) {
|
|
6492
|
+
nodesWithTestedBy.add(edge.source);
|
|
6493
|
+
}
|
|
6494
|
+
const baseNameToTestFiles = /* @__PURE__ */ new Map();
|
|
6495
|
+
for (const testPath of testFilePaths) {
|
|
6496
|
+
const base = path35.basename(testPath);
|
|
6497
|
+
const stripped = base.replace(/\.test\.[^.]+$/, "").replace(/\.spec\.[^.]+$/, "").replace(/_test\.[^.]+$/, "").replace(/_test$/, "");
|
|
6498
|
+
const existing = baseNameToTestFiles.get(stripped) ?? [];
|
|
6499
|
+
existing.push(testPath);
|
|
6500
|
+
baseNameToTestFiles.set(stripped, existing);
|
|
6501
|
+
}
|
|
6502
|
+
const exportedKinds = /* @__PURE__ */ new Set(["function", "method", "class"]);
|
|
6503
|
+
const results = [];
|
|
6504
|
+
for (const node of graph.allNodes()) {
|
|
6505
|
+
if (!exportedKinds.has(node.kind)) continue;
|
|
6506
|
+
if (node.exported !== true) continue;
|
|
6507
|
+
if (scope && !node.filePath.startsWith(scope)) continue;
|
|
6508
|
+
const testFiles = [];
|
|
6509
|
+
if (nodesWithTestedBy.has(node.id)) {
|
|
6510
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
6511
|
+
if (edge.kind === "tested_by") {
|
|
6512
|
+
const testNode = graph.getNode(edge.target);
|
|
6513
|
+
if (testNode && !testFiles.includes(testNode.filePath)) {
|
|
6514
|
+
testFiles.push(testNode.filePath);
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
}
|
|
6518
|
+
}
|
|
6519
|
+
if (nodesImportedByTests.has(node.id)) {
|
|
6520
|
+
for (const edge of graph.findEdgesTo(node.id)) {
|
|
6521
|
+
if (edge.kind === "imports") {
|
|
6522
|
+
const sourceNode = graph.getNode(edge.source);
|
|
6523
|
+
if (sourceNode && isTestFile(sourceNode.filePath) && !testFiles.includes(sourceNode.filePath)) {
|
|
6524
|
+
testFiles.push(sourceNode.filePath);
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
const nodeBase = path35.basename(node.filePath).replace(/\.[^.]+$/, "");
|
|
6530
|
+
const matchingTestFiles = baseNameToTestFiles.get(nodeBase) ?? [];
|
|
6531
|
+
for (const tf of matchingTestFiles) {
|
|
6532
|
+
if (!testFiles.includes(tf)) testFiles.push(tf);
|
|
6533
|
+
}
|
|
6534
|
+
const tested = testFiles.length > 0;
|
|
6535
|
+
const blastRadius = computeBlastRadius(graph, node.id);
|
|
6536
|
+
const risk = getRisk(blastRadius);
|
|
6537
|
+
results.push({
|
|
6538
|
+
nodeId: node.id,
|
|
6539
|
+
name: node.name,
|
|
6540
|
+
filePath: node.filePath,
|
|
6541
|
+
exported: true,
|
|
6542
|
+
tested,
|
|
6543
|
+
testFiles,
|
|
6544
|
+
blastRadius,
|
|
6545
|
+
risk
|
|
6546
|
+
});
|
|
6547
|
+
}
|
|
6548
|
+
const totalExported = results.length;
|
|
6549
|
+
const testedExported = results.filter((r) => r.tested).length;
|
|
6550
|
+
const coveragePct = totalExported === 0 ? 100 : Math.round(testedExported / totalExported * 100);
|
|
6551
|
+
const untestedByRisk = results.filter((r) => !r.tested).sort((a, b) => b.blastRadius - a.blastRadius);
|
|
6552
|
+
return { totalExported, testedExported, coveragePct, untestedByRisk };
|
|
6553
|
+
}
|
|
6554
|
+
var init_test_coverage = __esm({
|
|
6555
|
+
"src/analysis/test-coverage.ts"() {
|
|
6556
|
+
}
|
|
6557
|
+
});
|
|
6558
|
+
|
|
6559
|
+
// src/security/secret-scanner.ts
|
|
6560
|
+
var secret_scanner_exports = {};
|
|
6561
|
+
__export(secret_scanner_exports, {
|
|
6562
|
+
SecretScanner: () => SecretScanner
|
|
6563
|
+
});
|
|
6564
|
+
function shannonEntropy(s) {
|
|
6565
|
+
const freq = /* @__PURE__ */ new Map();
|
|
6566
|
+
for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
|
|
6567
|
+
let entropy = 0;
|
|
6568
|
+
for (const count of freq.values()) {
|
|
6569
|
+
const p = count / s.length;
|
|
6570
|
+
entropy -= p * Math.log2(p);
|
|
6571
|
+
}
|
|
6572
|
+
return entropy;
|
|
6573
|
+
}
|
|
6574
|
+
function isTestFile2(filePath) {
|
|
6575
|
+
return filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("fixtures/") || filePath.includes("mocks/");
|
|
6576
|
+
}
|
|
6577
|
+
var ENV_VAR_RE, SENSITIVE_NAME_RE, VALUE_PATTERNS, SecretScanner;
|
|
6578
|
+
var init_secret_scanner = __esm({
|
|
6579
|
+
"src/security/secret-scanner.ts"() {
|
|
6580
|
+
ENV_VAR_RE = /^process\.env\./;
|
|
6581
|
+
SENSITIVE_NAME_RE = /_SECRET$|_PASSWORD$|_TOKEN$|_KEY$|_API_KEY$/i;
|
|
6582
|
+
VALUE_PATTERNS = [
|
|
6583
|
+
[/sk-[A-Za-z0-9]{6,}/, "openai-api-key", "HIGH"],
|
|
6584
|
+
[/pk_live_[A-Za-z0-9]{20,}/, "stripe-key", "HIGH"],
|
|
6585
|
+
[/AKIA[0-9A-Z]{16}|aws.access.key/i, "aws-access-key", "HIGH"],
|
|
6586
|
+
[/xoxb-[0-9]{11}-[0-9]{11}-[A-Za-z0-9]{24}/, "slack-token", "HIGH"],
|
|
6587
|
+
[/postgres:\/\/[^@]+:[^@]+@/, "db-url-with-credentials", "HIGH"],
|
|
6588
|
+
[/mysql:\/\/[^@]+:[^@]+@/, "db-url-with-credentials", "HIGH"],
|
|
6589
|
+
[/-----BEGIN RSA PRIVATE KEY-----/, "rsa-private-key", "HIGH"]
|
|
6590
|
+
];
|
|
6591
|
+
SecretScanner = class {
|
|
6592
|
+
scan(graph, options) {
|
|
6593
|
+
const findings = [];
|
|
6594
|
+
const includeTests = options?.includeTestFiles ?? false;
|
|
6595
|
+
const scope = options?.scope;
|
|
6596
|
+
const ignorePatterns = [...options?.ignorePatterns ?? []];
|
|
6597
|
+
if (options?.workspaceRoot) {
|
|
6598
|
+
try {
|
|
6599
|
+
const raw = fs34.readFileSync(path35.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
|
|
6600
|
+
for (const line of raw.split("\n")) {
|
|
6601
|
+
const trimmed = line.trim();
|
|
6602
|
+
if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
|
|
6603
|
+
}
|
|
6604
|
+
} catch {
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
for (const node of graph.allNodes()) {
|
|
6608
|
+
const filePath = node.filePath;
|
|
6609
|
+
if (scope && !filePath.startsWith(scope)) continue;
|
|
6610
|
+
if (!includeTests && isTestFile2(filePath)) continue;
|
|
6611
|
+
if (ignorePatterns.length > 0 && ignorePatterns.some((p) => filePath.includes(p))) continue;
|
|
6612
|
+
const meta = node.metadata;
|
|
6613
|
+
const rawValue = meta?.value ?? meta?.literalValue;
|
|
6614
|
+
if (typeof rawValue !== "string" || rawValue.trim() === "") continue;
|
|
6615
|
+
const value = rawValue.trim();
|
|
6616
|
+
if (ENV_VAR_RE.test(value)) continue;
|
|
6617
|
+
let matched = false;
|
|
6618
|
+
for (const [re, label, severity] of VALUE_PATTERNS) {
|
|
6619
|
+
if (re.test(value)) {
|
|
6620
|
+
node.metadata = {
|
|
6621
|
+
...node.metadata ?? {},
|
|
6622
|
+
security: { secretRisk: true, secretPattern: label }
|
|
6623
|
+
};
|
|
6624
|
+
findings.push({
|
|
6625
|
+
file: filePath,
|
|
6626
|
+
line: node.startLine,
|
|
6627
|
+
symbol: node.name,
|
|
6628
|
+
pattern: label,
|
|
6629
|
+
severity
|
|
6630
|
+
});
|
|
6631
|
+
matched = true;
|
|
6632
|
+
break;
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
if (matched) continue;
|
|
6636
|
+
if (SENSITIVE_NAME_RE.test(node.name)) {
|
|
6637
|
+
node.metadata = {
|
|
6638
|
+
...node.metadata ?? {},
|
|
6639
|
+
security: { secretRisk: true, secretPattern: "sensitive-name-with-value" }
|
|
6640
|
+
};
|
|
6641
|
+
findings.push({
|
|
6642
|
+
file: filePath,
|
|
6643
|
+
line: node.startLine,
|
|
6644
|
+
symbol: node.name,
|
|
6645
|
+
pattern: "sensitive-name-with-value",
|
|
6646
|
+
severity: "MEDIUM"
|
|
6647
|
+
});
|
|
6648
|
+
continue;
|
|
6649
|
+
}
|
|
6650
|
+
if (SENSITIVE_NAME_RE.test(node.name) && value.length > 20 && shannonEntropy(value) > 4.5) {
|
|
6651
|
+
node.metadata = {
|
|
6652
|
+
...node.metadata ?? {},
|
|
6653
|
+
security: { secretRisk: true, secretPattern: "high-entropy-string" }
|
|
6654
|
+
};
|
|
6655
|
+
findings.push({
|
|
6656
|
+
file: filePath,
|
|
6657
|
+
line: node.startLine,
|
|
6658
|
+
symbol: node.name,
|
|
6659
|
+
pattern: "high-entropy-string",
|
|
6660
|
+
severity: "MEDIUM"
|
|
6661
|
+
});
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
6664
|
+
return findings;
|
|
6665
|
+
}
|
|
6666
|
+
};
|
|
6667
|
+
}
|
|
6668
|
+
});
|
|
6669
|
+
|
|
6670
|
+
// src/security/vulnerability-detector.ts
|
|
6671
|
+
var vulnerability_detector_exports = {};
|
|
6672
|
+
__export(vulnerability_detector_exports, {
|
|
6673
|
+
VulnerabilityDetector: () => VulnerabilityDetector
|
|
6674
|
+
});
|
|
6675
|
+
function hasCallers(graph, nodeId) {
|
|
6676
|
+
for (const edge of graph.findEdgesTo(nodeId)) {
|
|
6677
|
+
if (edge.kind === "calls") return true;
|
|
6678
|
+
}
|
|
6679
|
+
return false;
|
|
6680
|
+
}
|
|
6681
|
+
var CWE, SQL_PATTERN, XSS_PATTERN, SSRF_PATTERN, PATH_PATTERN, CMD_PATTERN, VulnerabilityDetector;
|
|
6682
|
+
var init_vulnerability_detector = __esm({
|
|
6683
|
+
"src/security/vulnerability-detector.ts"() {
|
|
6684
|
+
CWE = {
|
|
6685
|
+
SQL_INJECTION: "CWE-89",
|
|
6686
|
+
XSS: "CWE-79",
|
|
6687
|
+
SSRF: "CWE-918",
|
|
6688
|
+
PATH_TRAVERSAL: "CWE-22",
|
|
6689
|
+
COMMAND_INJECTION: "CWE-78"
|
|
6690
|
+
};
|
|
6691
|
+
SQL_PATTERN = /(db|database|connection|knex|sequelize|pool)\.(query|execute|raw)/i;
|
|
6692
|
+
XSS_PATTERN = /innerHTML|outerHTML|document\.write|insertAdjacentHTML/i;
|
|
6693
|
+
SSRF_PATTERN = /^(fetch|axios|http\.request|got)$/i;
|
|
6694
|
+
PATH_PATTERN = /^(fs\.readFile|fs\.writeFile|path\.join|createReadStream)$/i;
|
|
6695
|
+
CMD_PATTERN = /^(exec|execSync|spawn|eval)$/i;
|
|
6696
|
+
VulnerabilityDetector = class {
|
|
6697
|
+
detect(graph, options) {
|
|
6698
|
+
const findings = [];
|
|
6699
|
+
const scope = options?.scope;
|
|
6700
|
+
const types = options?.types ? new Set(options.types) : null;
|
|
6701
|
+
const want = (t) => !types || types.has(t);
|
|
6702
|
+
const nodes = [...graph.allNodes()];
|
|
6703
|
+
for (const node of nodes) {
|
|
6704
|
+
if (node.kind === "vulnerability") continue;
|
|
6705
|
+
const filePath = node.filePath;
|
|
6706
|
+
if (scope && !filePath.startsWith(scope)) continue;
|
|
6707
|
+
const nodeName = node.name;
|
|
6708
|
+
const meta = node.metadata;
|
|
6709
|
+
if (want("SQL_INJECTION") && SQL_PATTERN.test(nodeName)) {
|
|
6710
|
+
const hasDynamic = meta?.hasStringConcatenation === true || hasCallers(graph, node.id);
|
|
6711
|
+
if (hasDynamic) {
|
|
6712
|
+
findings.push({
|
|
6713
|
+
type: "SQL_INJECTION",
|
|
6714
|
+
severity: "MEDIUM",
|
|
6715
|
+
file: filePath,
|
|
6716
|
+
line: node.startLine,
|
|
6717
|
+
symbol: nodeName,
|
|
6718
|
+
description: `Potential SQL injection: ${nodeName} may execute unsanitized user input`,
|
|
6719
|
+
cweId: CWE["SQL_INJECTION"]
|
|
6720
|
+
});
|
|
6721
|
+
this._tagNode(graph, node.id, "SQL_INJECTION");
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
if (want("XSS") && XSS_PATTERN.test(nodeName)) {
|
|
6725
|
+
findings.push({
|
|
6726
|
+
type: "XSS",
|
|
6727
|
+
severity: "HIGH",
|
|
6728
|
+
file: filePath,
|
|
6729
|
+
line: node.startLine,
|
|
6730
|
+
symbol: nodeName,
|
|
6731
|
+
description: `Potential XSS: ${nodeName} writes to DOM sink`,
|
|
6732
|
+
cweId: CWE["XSS"]
|
|
6733
|
+
});
|
|
6734
|
+
this._tagNode(graph, node.id, "XSS");
|
|
6735
|
+
}
|
|
6736
|
+
if (want("XSS")) {
|
|
6737
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
6738
|
+
if (edge.kind === "calls") {
|
|
6739
|
+
const callee = graph.getNode(edge.target);
|
|
6740
|
+
if (callee && XSS_PATTERN.test(callee.name)) {
|
|
6741
|
+
findings.push({
|
|
6742
|
+
type: "XSS",
|
|
6743
|
+
severity: "HIGH",
|
|
6744
|
+
file: filePath,
|
|
6745
|
+
line: node.startLine,
|
|
6746
|
+
symbol: node.name,
|
|
6747
|
+
description: `Potential XSS: ${node.name} calls ${callee.name}`,
|
|
6748
|
+
cweId: CWE["XSS"]
|
|
6749
|
+
});
|
|
6750
|
+
this._tagNode(graph, node.id, "XSS");
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
if (want("SSRF") && SSRF_PATTERN.test(nodeName)) {
|
|
6756
|
+
const isDynamic = meta?.dynamicUrl === true;
|
|
6757
|
+
if (isDynamic) {
|
|
6758
|
+
findings.push({
|
|
6759
|
+
type: "SSRF",
|
|
6760
|
+
severity: "HIGH",
|
|
6761
|
+
file: filePath,
|
|
6762
|
+
line: node.startLine,
|
|
6763
|
+
symbol: nodeName,
|
|
6764
|
+
description: `Potential SSRF: ${nodeName} uses a dynamic URL`,
|
|
6765
|
+
cweId: CWE["SSRF"]
|
|
6766
|
+
});
|
|
6767
|
+
this._tagNode(graph, node.id, "SSRF");
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
if (want("PATH_TRAVERSAL") && PATH_PATTERN.test(nodeName)) {
|
|
6771
|
+
const isDynamic = meta?.dynamicPath === true;
|
|
6772
|
+
if (isDynamic) {
|
|
6773
|
+
findings.push({
|
|
6774
|
+
type: "PATH_TRAVERSAL",
|
|
6775
|
+
severity: "HIGH",
|
|
6776
|
+
file: filePath,
|
|
6777
|
+
line: node.startLine,
|
|
6778
|
+
symbol: nodeName,
|
|
6779
|
+
description: `Potential path traversal: ${nodeName} uses a dynamic path`,
|
|
6780
|
+
cweId: CWE["PATH_TRAVERSAL"]
|
|
6781
|
+
});
|
|
6782
|
+
this._tagNode(graph, node.id, "PATH_TRAVERSAL");
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6785
|
+
if (want("COMMAND_INJECTION") && CMD_PATTERN.test(nodeName)) {
|
|
6786
|
+
const isDynamic = meta?.dynamicArgs === true;
|
|
6787
|
+
if (isDynamic) {
|
|
6788
|
+
findings.push({
|
|
6789
|
+
type: "COMMAND_INJECTION",
|
|
6790
|
+
severity: "HIGH",
|
|
6791
|
+
file: filePath,
|
|
6792
|
+
line: node.startLine,
|
|
6793
|
+
symbol: nodeName,
|
|
6794
|
+
description: `Potential command injection: ${nodeName} uses dynamic arguments`,
|
|
6795
|
+
cweId: CWE["COMMAND_INJECTION"]
|
|
6796
|
+
});
|
|
6797
|
+
this._tagNode(graph, node.id, "COMMAND_INJECTION");
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6802
|
+
return findings.filter((f) => {
|
|
6803
|
+
const key = `${f.type}:${f.file}:${f.line}:${f.symbol}`;
|
|
6804
|
+
if (seen.has(key)) return false;
|
|
6805
|
+
seen.add(key);
|
|
6806
|
+
return true;
|
|
6807
|
+
});
|
|
6808
|
+
}
|
|
6809
|
+
_tagNode(graph, nodeId, vulnType) {
|
|
6810
|
+
const node = graph.getNode(nodeId);
|
|
6811
|
+
if (!node) return;
|
|
6812
|
+
const meta = node.metadata ?? {};
|
|
6813
|
+
node.metadata = {
|
|
6814
|
+
...meta,
|
|
6815
|
+
security: {
|
|
6816
|
+
...meta["security"] ?? {},
|
|
6817
|
+
vulnerability: vulnType
|
|
6818
|
+
}
|
|
6819
|
+
};
|
|
6820
|
+
const vulnNodeId = `vuln:${vulnType}:${nodeId}`;
|
|
6821
|
+
if (!graph.getNode(vulnNodeId)) {
|
|
6822
|
+
graph.addNode({
|
|
6823
|
+
id: vulnNodeId,
|
|
6824
|
+
kind: "vulnerability",
|
|
6825
|
+
name: `${vulnType}:${node.name}`,
|
|
6826
|
+
filePath: node.filePath,
|
|
6827
|
+
startLine: node.startLine,
|
|
6828
|
+
metadata: { cweId: CWE[vulnType], vulnType }
|
|
6829
|
+
});
|
|
6830
|
+
}
|
|
6831
|
+
const edgeId = `has_vulnerability:${nodeId}:${vulnNodeId}`;
|
|
6832
|
+
if (!graph.getEdge(edgeId)) {
|
|
6833
|
+
graph.addEdge({
|
|
6834
|
+
id: edgeId,
|
|
6835
|
+
source: nodeId,
|
|
6836
|
+
target: vulnNodeId,
|
|
6837
|
+
kind: "has_vulnerability"
|
|
6838
|
+
});
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
};
|
|
6842
|
+
}
|
|
6843
|
+
});
|
|
6844
|
+
|
|
6301
6845
|
// src/pipeline/file-watcher.ts
|
|
6302
6846
|
var file_watcher_exports = {};
|
|
6303
6847
|
__export(file_watcher_exports, {
|
|
@@ -6376,10 +6920,10 @@ var init_file_watcher = __esm({
|
|
|
6376
6920
|
}
|
|
6377
6921
|
// ── private ─────────────────────────────────────────────────────────────────
|
|
6378
6922
|
readCodeIntelIgnore() {
|
|
6379
|
-
const ignoreFile =
|
|
6923
|
+
const ignoreFile = path35.join(this.workspaceRoot, ".codeintelignore");
|
|
6380
6924
|
try {
|
|
6381
|
-
if (!
|
|
6382
|
-
return
|
|
6925
|
+
if (!fs34.existsSync(ignoreFile)) return [];
|
|
6926
|
+
return fs34.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
6383
6927
|
} catch {
|
|
6384
6928
|
return [];
|
|
6385
6929
|
}
|
|
@@ -6431,7 +6975,7 @@ var init_incremental_indexer = __esm({
|
|
|
6431
6975
|
}
|
|
6432
6976
|
const nodeIdsToRemove = /* @__PURE__ */ new Set();
|
|
6433
6977
|
for (const absPath of changedFiles) {
|
|
6434
|
-
const relPath2 =
|
|
6978
|
+
const relPath2 = path35.relative(workspaceRoot, absPath);
|
|
6435
6979
|
for (const id of nodesByFilePath.get(relPath2) ?? []) nodeIdsToRemove.add(id);
|
|
6436
6980
|
for (const id of nodesByFilePath.get(absPath) ?? []) nodeIdsToRemove.add(id);
|
|
6437
6981
|
}
|
|
@@ -6439,12 +6983,12 @@ var init_incremental_indexer = __esm({
|
|
|
6439
6983
|
graph.removeNodeCascade(id);
|
|
6440
6984
|
nodesRemoved++;
|
|
6441
6985
|
}
|
|
6442
|
-
if (
|
|
6986
|
+
if (fs34.existsSync(dbPath)) {
|
|
6443
6987
|
try {
|
|
6444
6988
|
const db = new DbManager(dbPath);
|
|
6445
6989
|
await db.init();
|
|
6446
6990
|
for (const absPath of changedFiles) {
|
|
6447
|
-
const relPath2 =
|
|
6991
|
+
const relPath2 = path35.relative(workspaceRoot, absPath);
|
|
6448
6992
|
await removeNodesForFile(relPath2, db);
|
|
6449
6993
|
}
|
|
6450
6994
|
db.close();
|
|
@@ -6454,7 +6998,7 @@ var init_incremental_indexer = __esm({
|
|
|
6454
6998
|
}
|
|
6455
6999
|
const existingFiles = changedFiles.filter((f) => {
|
|
6456
7000
|
try {
|
|
6457
|
-
return
|
|
7001
|
+
return fs34.statSync(f).isFile();
|
|
6458
7002
|
} catch {
|
|
6459
7003
|
return false;
|
|
6460
7004
|
}
|
|
@@ -6476,13 +7020,13 @@ var init_incremental_indexer = __esm({
|
|
|
6476
7020
|
await runPipeline([noopScan, parsePhase, resolvePhase], context2);
|
|
6477
7021
|
}
|
|
6478
7022
|
const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
|
|
6479
|
-
if (
|
|
7023
|
+
if (fs34.existsSync(dbPath) && existingFiles.length > 0) {
|
|
6480
7024
|
try {
|
|
6481
7025
|
const db = new DbManager(dbPath);
|
|
6482
7026
|
await db.init();
|
|
6483
|
-
const changedRelPaths = new Set(changedFiles.map((f) =>
|
|
7027
|
+
const changedRelPaths = new Set(changedFiles.map((f) => path35.relative(workspaceRoot, f)));
|
|
6484
7028
|
const nodesToUpsert = [...graph.allNodes()].filter(
|
|
6485
|
-
(n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(
|
|
7029
|
+
(n) => changedRelPaths.has(n.filePath) || changedRelPaths.has(path35.relative(workspaceRoot, n.filePath))
|
|
6486
7030
|
);
|
|
6487
7031
|
await upsertNodes(nodesToUpsert, db);
|
|
6488
7032
|
db.close();
|
|
@@ -6513,35 +7057,35 @@ __export(saved_queries_exports, {
|
|
|
6513
7057
|
saveQuery: () => saveQuery
|
|
6514
7058
|
});
|
|
6515
7059
|
function getQueriesDir(workspaceRoot) {
|
|
6516
|
-
return
|
|
7060
|
+
return path35.join(workspaceRoot, ".code-intel", "queries");
|
|
6517
7061
|
}
|
|
6518
7062
|
function ensureQueriesDir(workspaceRoot) {
|
|
6519
7063
|
const dir = getQueriesDir(workspaceRoot);
|
|
6520
|
-
if (!
|
|
6521
|
-
|
|
7064
|
+
if (!fs34.existsSync(dir)) {
|
|
7065
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
6522
7066
|
}
|
|
6523
7067
|
return dir;
|
|
6524
7068
|
}
|
|
6525
7069
|
function saveQuery(workspaceRoot, name, gql) {
|
|
6526
7070
|
const dir = ensureQueriesDir(workspaceRoot);
|
|
6527
|
-
const filePath =
|
|
6528
|
-
|
|
7071
|
+
const filePath = path35.join(dir, `${name}.gql`);
|
|
7072
|
+
fs34.writeFileSync(filePath, gql, "utf-8");
|
|
6529
7073
|
}
|
|
6530
7074
|
function loadQuery(workspaceRoot, name) {
|
|
6531
7075
|
const dir = getQueriesDir(workspaceRoot);
|
|
6532
|
-
const filePath =
|
|
6533
|
-
if (!
|
|
6534
|
-
return
|
|
7076
|
+
const filePath = path35.join(dir, `${name}.gql`);
|
|
7077
|
+
if (!fs34.existsSync(filePath)) return null;
|
|
7078
|
+
return fs34.readFileSync(filePath, "utf-8");
|
|
6535
7079
|
}
|
|
6536
7080
|
function listQueries(workspaceRoot) {
|
|
6537
7081
|
const dir = getQueriesDir(workspaceRoot);
|
|
6538
|
-
if (!
|
|
6539
|
-
const files =
|
|
7082
|
+
if (!fs34.existsSync(dir)) return [];
|
|
7083
|
+
const files = fs34.readdirSync(dir).filter((f) => f.endsWith(".gql"));
|
|
6540
7084
|
return files.map((f) => {
|
|
6541
|
-
const filePath =
|
|
7085
|
+
const filePath = path35.join(dir, f);
|
|
6542
7086
|
const name = f.replace(/\.gql$/, "");
|
|
6543
|
-
const content =
|
|
6544
|
-
const stat =
|
|
7087
|
+
const content = fs34.readFileSync(filePath, "utf-8");
|
|
7088
|
+
const stat = fs34.statSync(filePath);
|
|
6545
7089
|
return {
|
|
6546
7090
|
name,
|
|
6547
7091
|
content,
|
|
@@ -6552,15 +7096,15 @@ function listQueries(workspaceRoot) {
|
|
|
6552
7096
|
}
|
|
6553
7097
|
function deleteQuery(workspaceRoot, name) {
|
|
6554
7098
|
const dir = getQueriesDir(workspaceRoot);
|
|
6555
|
-
const filePath =
|
|
6556
|
-
if (!
|
|
6557
|
-
|
|
7099
|
+
const filePath = path35.join(dir, `${name}.gql`);
|
|
7100
|
+
if (!fs34.existsSync(filePath)) return false;
|
|
7101
|
+
fs34.unlinkSync(filePath);
|
|
6558
7102
|
return true;
|
|
6559
7103
|
}
|
|
6560
7104
|
function queryExists(workspaceRoot, name) {
|
|
6561
7105
|
const dir = getQueriesDir(workspaceRoot);
|
|
6562
|
-
const filePath =
|
|
6563
|
-
return
|
|
7106
|
+
const filePath = path35.join(dir, `${name}.gql`);
|
|
7107
|
+
return fs34.existsSync(filePath);
|
|
6564
7108
|
}
|
|
6565
7109
|
var init_saved_queries = __esm({
|
|
6566
7110
|
"src/query/saved-queries.ts"() {
|
|
@@ -6669,7 +7213,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
6669
7213
|
]);
|
|
6670
7214
|
function loadIgnorePatterns(workspaceRoot) {
|
|
6671
7215
|
try {
|
|
6672
|
-
const raw =
|
|
7216
|
+
const raw = fs34.readFileSync(path35.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
6673
7217
|
const extras = /* @__PURE__ */ new Set();
|
|
6674
7218
|
for (const line of raw.split("\n")) {
|
|
6675
7219
|
const trimmed = line.trim();
|
|
@@ -6693,7 +7237,7 @@ var scanPhase = {
|
|
|
6693
7237
|
function walk2(dir) {
|
|
6694
7238
|
let entries;
|
|
6695
7239
|
try {
|
|
6696
|
-
entries =
|
|
7240
|
+
entries = fs34.readdirSync(dir, { withFileTypes: true });
|
|
6697
7241
|
} catch {
|
|
6698
7242
|
return;
|
|
6699
7243
|
}
|
|
@@ -6702,15 +7246,15 @@ var scanPhase = {
|
|
|
6702
7246
|
if (entry.name.startsWith(".")) continue;
|
|
6703
7247
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6704
7248
|
if (extraIgnore.has(entry.name)) continue;
|
|
6705
|
-
walk2(
|
|
7249
|
+
walk2(path35.join(dir, entry.name));
|
|
6706
7250
|
} else if (entry.isFile()) {
|
|
6707
7251
|
const name = entry.name;
|
|
6708
7252
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
6709
|
-
const ext =
|
|
7253
|
+
const ext = path35.extname(name);
|
|
6710
7254
|
if (!extensions.has(ext)) continue;
|
|
6711
|
-
const fullPath =
|
|
7255
|
+
const fullPath = path35.join(dir, name);
|
|
6712
7256
|
try {
|
|
6713
|
-
const stat =
|
|
7257
|
+
const stat = fs34.statSync(fullPath);
|
|
6714
7258
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
6715
7259
|
} catch {
|
|
6716
7260
|
continue;
|
|
@@ -6737,20 +7281,20 @@ var structurePhase = {
|
|
|
6737
7281
|
const dirs = /* @__PURE__ */ new Set();
|
|
6738
7282
|
let structDone = 0;
|
|
6739
7283
|
for (const filePath of context2.filePaths) {
|
|
6740
|
-
const relativePath =
|
|
7284
|
+
const relativePath = path35.relative(context2.workspaceRoot, filePath);
|
|
6741
7285
|
const lang = detectLanguage(filePath);
|
|
6742
7286
|
context2.graph.addNode({
|
|
6743
7287
|
id: generateNodeId("file", relativePath, relativePath),
|
|
6744
7288
|
kind: "file",
|
|
6745
|
-
name:
|
|
7289
|
+
name: path35.basename(filePath),
|
|
6746
7290
|
filePath: relativePath,
|
|
6747
7291
|
metadata: lang ? { language: lang } : void 0
|
|
6748
7292
|
});
|
|
6749
|
-
let dir =
|
|
7293
|
+
let dir = path35.dirname(relativePath);
|
|
6750
7294
|
while (dir && dir !== "." && dir !== "") {
|
|
6751
7295
|
if (dirs.has(dir)) break;
|
|
6752
7296
|
dirs.add(dir);
|
|
6753
|
-
dir =
|
|
7297
|
+
dir = path35.dirname(dir);
|
|
6754
7298
|
}
|
|
6755
7299
|
structDone++;
|
|
6756
7300
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -6759,7 +7303,7 @@ var structurePhase = {
|
|
|
6759
7303
|
context2.graph.addNode({
|
|
6760
7304
|
id: generateNodeId("directory", dir, dir),
|
|
6761
7305
|
kind: "directory",
|
|
6762
|
-
name:
|
|
7306
|
+
name: path35.basename(dir),
|
|
6763
7307
|
filePath: dir
|
|
6764
7308
|
});
|
|
6765
7309
|
}
|
|
@@ -6846,9 +7390,9 @@ var flowPhase = {
|
|
|
6846
7390
|
for (const node of graph.allNodes()) {
|
|
6847
7391
|
if (!["function", "method"].includes(node.kind)) continue;
|
|
6848
7392
|
let score = 0;
|
|
6849
|
-
const
|
|
7393
|
+
const hasCallers2 = calledNodes.has(node.id);
|
|
6850
7394
|
const outCalls = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "calls");
|
|
6851
|
-
if (!
|
|
7395
|
+
if (!hasCallers2 && outCalls.length > 0) score += 10;
|
|
6852
7396
|
if (node.exported) score += 5;
|
|
6853
7397
|
if (/^(main|handle|init|start|run|execute|process|serve|listen|bootstrap)/.test(node.name)) score += 3;
|
|
6854
7398
|
if (node.filePath.includes("test") || node.filePath.includes("spec") || node.filePath.includes("__test")) score -= 20;
|
|
@@ -6870,22 +7414,22 @@ var flowPhase = {
|
|
|
6870
7414
|
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
6871
7415
|
const visited = /* @__PURE__ */ new Set();
|
|
6872
7416
|
while (queue.length > 0 && flowCount < maxFlows) {
|
|
6873
|
-
const { nodeId, path:
|
|
6874
|
-
if (
|
|
7417
|
+
const { nodeId, path: path36 } = queue.shift();
|
|
7418
|
+
if (path36.length > maxDepth) continue;
|
|
6875
7419
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
6876
|
-
if (callEdges.length === 0 &&
|
|
7420
|
+
if (callEdges.length === 0 && path36.length >= 3) {
|
|
6877
7421
|
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
6878
7422
|
graph.addNode({
|
|
6879
7423
|
id: flowId,
|
|
6880
7424
|
kind: "flow",
|
|
6881
7425
|
name: `${ep.name} flow ${flowCount}`,
|
|
6882
7426
|
filePath: ep.filePath,
|
|
6883
|
-
metadata: { steps:
|
|
7427
|
+
metadata: { steps: path36, entryPoint: ep.name }
|
|
6884
7428
|
});
|
|
6885
|
-
for (let i = 0; i <
|
|
7429
|
+
for (let i = 0; i < path36.length; i++) {
|
|
6886
7430
|
graph.addEdge({
|
|
6887
|
-
id: generateEdgeId(
|
|
6888
|
-
source:
|
|
7431
|
+
id: generateEdgeId(path36[i], flowId, `step_of_${i}`),
|
|
7432
|
+
source: path36[i],
|
|
6889
7433
|
target: flowId,
|
|
6890
7434
|
kind: "step_of",
|
|
6891
7435
|
weight: 1,
|
|
@@ -6898,7 +7442,7 @@ var flowPhase = {
|
|
|
6898
7442
|
for (const edge of callEdges) {
|
|
6899
7443
|
if (visited.has(edge.target)) continue;
|
|
6900
7444
|
visited.add(edge.target);
|
|
6901
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
7445
|
+
queue.push({ nodeId: edge.target, path: [...path36, edge.target] });
|
|
6902
7446
|
}
|
|
6903
7447
|
}
|
|
6904
7448
|
}
|
|
@@ -6916,7 +7460,7 @@ var LLMGovernanceLogger = class {
|
|
|
6916
7460
|
}
|
|
6917
7461
|
/** Path to the JSONL log file. */
|
|
6918
7462
|
getLogPath() {
|
|
6919
|
-
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ??
|
|
7463
|
+
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path35.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
|
|
6920
7464
|
}
|
|
6921
7465
|
/**
|
|
6922
7466
|
* Append an entry to the governance log.
|
|
@@ -6932,8 +7476,8 @@ var LLMGovernanceLogger = class {
|
|
|
6932
7476
|
...entry
|
|
6933
7477
|
};
|
|
6934
7478
|
const logPath = this.getLogPath();
|
|
6935
|
-
|
|
6936
|
-
|
|
7479
|
+
fs34.mkdirSync(path35.dirname(logPath), { recursive: true });
|
|
7480
|
+
fs34.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
6937
7481
|
} catch {
|
|
6938
7482
|
}
|
|
6939
7483
|
}
|
|
@@ -6943,7 +7487,7 @@ var LLMGovernanceLogger = class {
|
|
|
6943
7487
|
*/
|
|
6944
7488
|
readLog(limit = 100) {
|
|
6945
7489
|
try {
|
|
6946
|
-
const raw =
|
|
7490
|
+
const raw = fs34.readFileSync(this.getLogPath(), "utf-8");
|
|
6947
7491
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
6948
7492
|
return lines.map((l) => JSON.parse(l));
|
|
6949
7493
|
} catch {
|
|
@@ -7257,7 +7801,7 @@ var LANG_QUERIES2 = {
|
|
|
7257
7801
|
};
|
|
7258
7802
|
function workerScriptPath() {
|
|
7259
7803
|
const thisFile = fileURLToPath(import.meta.url);
|
|
7260
|
-
return
|
|
7804
|
+
return path35.join(path35.dirname(thisFile), "parse-worker.js");
|
|
7261
7805
|
}
|
|
7262
7806
|
var parsePhaseParallel = {
|
|
7263
7807
|
name: "parse",
|
|
@@ -7273,14 +7817,14 @@ var parsePhaseParallel = {
|
|
|
7273
7817
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
7274
7818
|
await Promise.all(batch.map(async (filePath) => {
|
|
7275
7819
|
try {
|
|
7276
|
-
const source = await
|
|
7820
|
+
const source = await fs34.promises.readFile(filePath, "utf-8");
|
|
7277
7821
|
context2.fileCache.set(filePath, source);
|
|
7278
7822
|
} catch {
|
|
7279
7823
|
}
|
|
7280
7824
|
}));
|
|
7281
7825
|
}
|
|
7282
7826
|
const workerScript = workerScriptPath();
|
|
7283
|
-
const workerScriptExists =
|
|
7827
|
+
const workerScriptExists = fs34.existsSync(workerScript);
|
|
7284
7828
|
if (!workerScriptExists || workerCount === 1) {
|
|
7285
7829
|
logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
|
|
7286
7830
|
const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
|
|
@@ -7292,7 +7836,7 @@ var parsePhaseParallel = {
|
|
|
7292
7836
|
if (!lang) continue;
|
|
7293
7837
|
const source = context2.fileCache.get(filePath);
|
|
7294
7838
|
if (!source) continue;
|
|
7295
|
-
const relativePath =
|
|
7839
|
+
const relativePath = path35.relative(context2.workspaceRoot, filePath);
|
|
7296
7840
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
7297
7841
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
7298
7842
|
if (fileNode) fileNode.content = source.slice(0, 2e3);
|
|
@@ -7335,7 +7879,7 @@ var parsePhaseParallel = {
|
|
|
7335
7879
|
symbolCount += res.nodes.length;
|
|
7336
7880
|
if (res.usedTreeSitter) treeSitterCount++;
|
|
7337
7881
|
else regexCount++;
|
|
7338
|
-
const relativePath =
|
|
7882
|
+
const relativePath = path35.relative(context2.workspaceRoot, res.taskId);
|
|
7339
7883
|
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);
|
|
7340
7884
|
if (funcs.length > 0) context2.fileFunctionIndex.set(relativePath, funcs);
|
|
7341
7885
|
parseDone++;
|
|
@@ -7362,7 +7906,7 @@ init_id_generator();
|
|
|
7362
7906
|
init_logger();
|
|
7363
7907
|
function workerScriptPath2() {
|
|
7364
7908
|
const thisFile = fileURLToPath(import.meta.url);
|
|
7365
|
-
return
|
|
7909
|
+
return path35.join(path35.dirname(thisFile), "resolve-worker.js");
|
|
7366
7910
|
}
|
|
7367
7911
|
var resolvePhaseParallel = {
|
|
7368
7912
|
name: "resolve",
|
|
@@ -7374,11 +7918,11 @@ var resolvePhaseParallel = {
|
|
|
7374
7918
|
const fileFunctionIndex = context2.fileFunctionIndex ?? /* @__PURE__ */ new Map();
|
|
7375
7919
|
const fileIndex = {};
|
|
7376
7920
|
for (const fp of filePaths) {
|
|
7377
|
-
const rel =
|
|
7921
|
+
const rel = path35.relative(workspaceRoot, fp);
|
|
7378
7922
|
fileIndex[rel] = fp;
|
|
7379
7923
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
7380
7924
|
if (!fileIndex[noExt]) fileIndex[noExt] = fp;
|
|
7381
|
-
const base =
|
|
7925
|
+
const base = path35.basename(rel, path35.extname(rel));
|
|
7382
7926
|
if (!fileIndex[base]) fileIndex[base] = fp;
|
|
7383
7927
|
}
|
|
7384
7928
|
const symbolIndex = {};
|
|
@@ -7392,7 +7936,7 @@ var resolvePhaseParallel = {
|
|
|
7392
7936
|
}
|
|
7393
7937
|
const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
|
|
7394
7938
|
const workerScript = workerScriptPath2();
|
|
7395
|
-
const workerScriptExists =
|
|
7939
|
+
const workerScriptExists = fs34.existsSync(workerScript);
|
|
7396
7940
|
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os12.cpus().length - 1);
|
|
7397
7941
|
if (!workerScriptExists || workerCount === 1) {
|
|
7398
7942
|
logger_default.info(`[resolve-parallel] falling back to sequential`);
|
|
@@ -7405,7 +7949,7 @@ var resolvePhaseParallel = {
|
|
|
7405
7949
|
if (!lang) continue;
|
|
7406
7950
|
const source = fileCache.get(filePath);
|
|
7407
7951
|
if (!source) continue;
|
|
7408
|
-
const relativePath =
|
|
7952
|
+
const relativePath = path35.relative(workspaceRoot, filePath);
|
|
7409
7953
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
7410
7954
|
const funcList = fileFunctionIndex.get(relativePath) ?? [];
|
|
7411
7955
|
tasks.push({ taskId: filePath, filePath, relativePath, fileNodeId, source, funcList });
|
|
@@ -7528,7 +8072,7 @@ init_embedder();
|
|
|
7528
8072
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
7529
8073
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
7530
8074
|
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
7531
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
8075
|
+
const hasVectorDb = Boolean(vectorDbPath && fs34.existsSync(vectorDbPath));
|
|
7532
8076
|
if (!hasVectorDb) {
|
|
7533
8077
|
const bm25Results2 = await bm25Promise;
|
|
7534
8078
|
return {
|
|
@@ -7595,8 +8139,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7595
8139
|
for (const member of group.members) {
|
|
7596
8140
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7597
8141
|
if (!regEntry) continue;
|
|
7598
|
-
const dbPath =
|
|
7599
|
-
if (!
|
|
8142
|
+
const dbPath = path35.join(regEntry.path, ".code-intel", "graph.db");
|
|
8143
|
+
if (!fs34.existsSync(dbPath)) continue;
|
|
7600
8144
|
const graph = createKnowledgeGraph();
|
|
7601
8145
|
const db = new DbManager(dbPath);
|
|
7602
8146
|
try {
|
|
@@ -7638,7 +8182,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
7638
8182
|
var JobsDB = class {
|
|
7639
8183
|
db;
|
|
7640
8184
|
constructor(dbPath) {
|
|
7641
|
-
|
|
8185
|
+
fs34.mkdirSync(path35.dirname(dbPath), { recursive: true });
|
|
7642
8186
|
this.db = new Database(dbPath);
|
|
7643
8187
|
this.db.pragma("journal_mode = WAL");
|
|
7644
8188
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -7780,7 +8324,7 @@ var JobsDB = class {
|
|
|
7780
8324
|
}
|
|
7781
8325
|
};
|
|
7782
8326
|
function getJobsDBPath() {
|
|
7783
|
-
return
|
|
8327
|
+
return path35.join(os12.homedir(), ".code-intel", "jobs.db");
|
|
7784
8328
|
}
|
|
7785
8329
|
var _jobsDB = null;
|
|
7786
8330
|
function getOrCreateJobsDB() {
|
|
@@ -7872,7 +8416,7 @@ var BACKUP_VERSION = "1.0";
|
|
|
7872
8416
|
var ALGORITHM = "aes-256-gcm";
|
|
7873
8417
|
var IV_LENGTH = 16;
|
|
7874
8418
|
function getBackupDir() {
|
|
7875
|
-
return
|
|
8419
|
+
return path35.join(os12.homedir(), ".code-intel", "backups");
|
|
7876
8420
|
}
|
|
7877
8421
|
function getBackupKey() {
|
|
7878
8422
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
@@ -7903,30 +8447,30 @@ var BackupService = class {
|
|
|
7903
8447
|
constructor(backupDir) {
|
|
7904
8448
|
this.backupDir = backupDir ?? getBackupDir();
|
|
7905
8449
|
this.key = getBackupKey();
|
|
7906
|
-
|
|
8450
|
+
fs34.mkdirSync(this.backupDir, { recursive: true });
|
|
7907
8451
|
}
|
|
7908
8452
|
/**
|
|
7909
8453
|
* Create a backup for a repository.
|
|
7910
8454
|
* Returns the backup entry.
|
|
7911
8455
|
*/
|
|
7912
8456
|
createBackup(repoPath) {
|
|
7913
|
-
const codeIntelDir =
|
|
8457
|
+
const codeIntelDir = path35.join(repoPath, ".code-intel");
|
|
7914
8458
|
const id = v4();
|
|
7915
8459
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7916
8460
|
const filesToBackup = [];
|
|
7917
8461
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
7918
8462
|
for (const f of candidates) {
|
|
7919
|
-
const fp =
|
|
7920
|
-
if (
|
|
8463
|
+
const fp = path35.join(codeIntelDir, f);
|
|
8464
|
+
if (fs34.existsSync(fp)) {
|
|
7921
8465
|
filesToBackup.push({ name: f, localPath: fp });
|
|
7922
8466
|
}
|
|
7923
8467
|
}
|
|
7924
|
-
const registryPath =
|
|
7925
|
-
if (
|
|
8468
|
+
const registryPath = path35.join(os12.homedir(), ".code-intel", "registry.json");
|
|
8469
|
+
if (fs34.existsSync(registryPath)) {
|
|
7926
8470
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
7927
8471
|
}
|
|
7928
|
-
const usersDbPath =
|
|
7929
|
-
if (
|
|
8472
|
+
const usersDbPath = path35.join(os12.homedir(), ".code-intel", "users.db");
|
|
8473
|
+
if (fs34.existsSync(usersDbPath)) {
|
|
7930
8474
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
7931
8475
|
}
|
|
7932
8476
|
if (filesToBackup.length === 0) {
|
|
@@ -7937,7 +8481,7 @@ var BackupService = class {
|
|
|
7937
8481
|
createdAt,
|
|
7938
8482
|
version: BACKUP_VERSION,
|
|
7939
8483
|
files: filesToBackup.map((f) => {
|
|
7940
|
-
const data =
|
|
8484
|
+
const data = fs34.readFileSync(f.localPath);
|
|
7941
8485
|
return {
|
|
7942
8486
|
name: f.name,
|
|
7943
8487
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -7951,7 +8495,7 @@ var BackupService = class {
|
|
|
7951
8495
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
7952
8496
|
parts.push(manifestLenBuf, manifestBuf);
|
|
7953
8497
|
for (const f of filesToBackup) {
|
|
7954
|
-
const data =
|
|
8498
|
+
const data = fs34.readFileSync(f.localPath);
|
|
7955
8499
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
7956
8500
|
const nameLenBuf = Buffer.alloc(2);
|
|
7957
8501
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -7962,8 +8506,8 @@ var BackupService = class {
|
|
|
7962
8506
|
const plaintext = Buffer.concat(parts);
|
|
7963
8507
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
7964
8508
|
const backupFileName = `backup-${id}.cib`;
|
|
7965
|
-
const backupPath =
|
|
7966
|
-
|
|
8509
|
+
const backupPath = path35.join(this.backupDir, backupFileName);
|
|
8510
|
+
fs34.writeFileSync(backupPath, encrypted);
|
|
7967
8511
|
const entry = {
|
|
7968
8512
|
id,
|
|
7969
8513
|
createdAt,
|
|
@@ -7990,9 +8534,9 @@ var BackupService = class {
|
|
|
7990
8534
|
async uploadToS3(entry) {
|
|
7991
8535
|
const cfg = getS3Config();
|
|
7992
8536
|
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.");
|
|
7993
|
-
const fileName =
|
|
8537
|
+
const fileName = path35.basename(entry.path);
|
|
7994
8538
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
7995
|
-
const body =
|
|
8539
|
+
const body = fs34.readFileSync(entry.path);
|
|
7996
8540
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
7997
8541
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
7998
8542
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -8009,8 +8553,8 @@ var BackupService = class {
|
|
|
8009
8553
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
8010
8554
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
8011
8555
|
}
|
|
8012
|
-
|
|
8013
|
-
|
|
8556
|
+
fs34.mkdirSync(path35.dirname(destPath), { recursive: true });
|
|
8557
|
+
fs34.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
8014
8558
|
}
|
|
8015
8559
|
/**
|
|
8016
8560
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -8056,10 +8600,10 @@ var BackupService = class {
|
|
|
8056
8600
|
if (!entry) {
|
|
8057
8601
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
8058
8602
|
}
|
|
8059
|
-
if (!
|
|
8603
|
+
if (!fs34.existsSync(entry.path)) {
|
|
8060
8604
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
8061
8605
|
}
|
|
8062
|
-
const encrypted =
|
|
8606
|
+
const encrypted = fs34.readFileSync(entry.path);
|
|
8063
8607
|
let plaintext;
|
|
8064
8608
|
try {
|
|
8065
8609
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -8073,8 +8617,8 @@ var BackupService = class {
|
|
|
8073
8617
|
offset += manifestLen;
|
|
8074
8618
|
const manifest = JSON.parse(manifestStr);
|
|
8075
8619
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
8076
|
-
const codeIntelDir =
|
|
8077
|
-
|
|
8620
|
+
const codeIntelDir = path35.join(restoreBase, ".code-intel");
|
|
8621
|
+
fs34.mkdirSync(codeIntelDir, { recursive: true });
|
|
8078
8622
|
for (const fileEntry of manifest.files) {
|
|
8079
8623
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
8080
8624
|
offset += 2;
|
|
@@ -8091,18 +8635,18 @@ var BackupService = class {
|
|
|
8091
8635
|
}
|
|
8092
8636
|
let destPath;
|
|
8093
8637
|
if (name === "registry.json" || name === "users.db") {
|
|
8094
|
-
destPath =
|
|
8638
|
+
destPath = path35.join(os12.homedir(), ".code-intel", name);
|
|
8095
8639
|
} else {
|
|
8096
|
-
destPath =
|
|
8640
|
+
destPath = path35.join(codeIntelDir, name);
|
|
8097
8641
|
}
|
|
8098
|
-
|
|
8642
|
+
fs34.writeFileSync(destPath, data);
|
|
8099
8643
|
}
|
|
8100
8644
|
}
|
|
8101
8645
|
/**
|
|
8102
8646
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
8103
8647
|
*/
|
|
8104
8648
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
8105
|
-
const entries = this._loadIndex().filter((e) =>
|
|
8649
|
+
const entries = this._loadIndex().filter((e) => fs34.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
8106
8650
|
const keep = /* @__PURE__ */ new Set();
|
|
8107
8651
|
const now = /* @__PURE__ */ new Date();
|
|
8108
8652
|
const dailyCutoff = new Date(now);
|
|
@@ -8132,7 +8676,7 @@ var BackupService = class {
|
|
|
8132
8676
|
for (const e of entries) {
|
|
8133
8677
|
if (!keep.has(e.id)) {
|
|
8134
8678
|
try {
|
|
8135
|
-
|
|
8679
|
+
fs34.unlinkSync(e.path);
|
|
8136
8680
|
deleted++;
|
|
8137
8681
|
} catch {
|
|
8138
8682
|
}
|
|
@@ -8144,17 +8688,17 @@ var BackupService = class {
|
|
|
8144
8688
|
}
|
|
8145
8689
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
8146
8690
|
_indexPath() {
|
|
8147
|
-
return
|
|
8691
|
+
return path35.join(this.backupDir, "index.json");
|
|
8148
8692
|
}
|
|
8149
8693
|
_loadIndex() {
|
|
8150
8694
|
try {
|
|
8151
|
-
return JSON.parse(
|
|
8695
|
+
return JSON.parse(fs34.readFileSync(this._indexPath(), "utf-8"));
|
|
8152
8696
|
} catch {
|
|
8153
8697
|
return [];
|
|
8154
8698
|
}
|
|
8155
8699
|
}
|
|
8156
8700
|
_saveIndex(entries) {
|
|
8157
|
-
|
|
8701
|
+
fs34.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
8158
8702
|
}
|
|
8159
8703
|
_appendIndex(entry) {
|
|
8160
8704
|
const entries = this._loadIndex();
|
|
@@ -8795,11 +9339,11 @@ var openApiSpec = {
|
|
|
8795
9339
|
};
|
|
8796
9340
|
|
|
8797
9341
|
// src/http/app.ts
|
|
8798
|
-
var __dirname$1 =
|
|
9342
|
+
var __dirname$1 = path35.dirname(fileURLToPath(import.meta.url));
|
|
8799
9343
|
var WEB_DIST = (() => {
|
|
8800
|
-
const bundled =
|
|
8801
|
-
if (
|
|
8802
|
-
return
|
|
9344
|
+
const bundled = path35.resolve(__dirname$1, "..", "web");
|
|
9345
|
+
if (fs34.existsSync(bundled)) return bundled;
|
|
9346
|
+
return path35.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
8803
9347
|
})();
|
|
8804
9348
|
function getAllowedOrigins() {
|
|
8805
9349
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -9330,8 +9874,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9330
9874
|
const registry = loadRegistry();
|
|
9331
9875
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
9332
9876
|
if (!entry) return null;
|
|
9333
|
-
const dbPath =
|
|
9334
|
-
if (!
|
|
9877
|
+
const dbPath = path35.join(entry.path, ".code-intel", "graph.db");
|
|
9878
|
+
if (!fs34.existsSync(dbPath)) return null;
|
|
9335
9879
|
const repoGraph = createKnowledgeGraph();
|
|
9336
9880
|
const db = new DbManager(dbPath);
|
|
9337
9881
|
try {
|
|
@@ -9418,7 +9962,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9418
9962
|
return;
|
|
9419
9963
|
}
|
|
9420
9964
|
try {
|
|
9421
|
-
const content =
|
|
9965
|
+
const content = fs34.readFileSync(file_path, "utf-8");
|
|
9422
9966
|
res.json({ content });
|
|
9423
9967
|
} catch {
|
|
9424
9968
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -9676,8 +10220,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9676
10220
|
for (const member of group.members) {
|
|
9677
10221
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
9678
10222
|
if (!regEntry) continue;
|
|
9679
|
-
const dbPath =
|
|
9680
|
-
if (!
|
|
10223
|
+
const dbPath = path35.join(regEntry.path, ".code-intel", "graph.db");
|
|
10224
|
+
if (!fs34.existsSync(dbPath)) continue;
|
|
9681
10225
|
const db = new DbManager(dbPath);
|
|
9682
10226
|
try {
|
|
9683
10227
|
await db.init();
|
|
@@ -9703,8 +10247,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9703
10247
|
let nodeCount = 0;
|
|
9704
10248
|
let edgeCount = 0;
|
|
9705
10249
|
if (regEntry) {
|
|
9706
|
-
const dbPath =
|
|
9707
|
-
if (
|
|
10250
|
+
const dbPath = path35.join(regEntry.path, ".code-intel", "graph.db");
|
|
10251
|
+
if (fs34.existsSync(dbPath)) {
|
|
9708
10252
|
try {
|
|
9709
10253
|
const db = new DbManager(dbPath);
|
|
9710
10254
|
await db.init();
|
|
@@ -9753,14 +10297,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9753
10297
|
});
|
|
9754
10298
|
return;
|
|
9755
10299
|
}
|
|
9756
|
-
let rawResolved =
|
|
9757
|
-
if (!
|
|
9758
|
-
rawResolved =
|
|
10300
|
+
let rawResolved = path35.normalize(file);
|
|
10301
|
+
if (!path35.isAbsolute(rawResolved) && workspaceRoot) {
|
|
10302
|
+
rawResolved = path35.join(workspaceRoot, rawResolved);
|
|
9759
10303
|
}
|
|
9760
|
-
const resolvedFile =
|
|
10304
|
+
const resolvedFile = path35.resolve(rawResolved);
|
|
9761
10305
|
function isInsideDir(fileAbs, dir) {
|
|
9762
|
-
const rel =
|
|
9763
|
-
return !rel.startsWith("..") && !
|
|
10306
|
+
const rel = path35.relative(path35.resolve(dir), fileAbs);
|
|
10307
|
+
return !rel.startsWith("..") && !path35.isAbsolute(rel);
|
|
9764
10308
|
}
|
|
9765
10309
|
if (workspaceRoot) {
|
|
9766
10310
|
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
@@ -9797,7 +10341,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9797
10341
|
}
|
|
9798
10342
|
let fileContent;
|
|
9799
10343
|
try {
|
|
9800
|
-
fileContent =
|
|
10344
|
+
fileContent = fs34.readFileSync(resolvedFile, "utf-8");
|
|
9801
10345
|
} catch {
|
|
9802
10346
|
res.status(404).json({
|
|
9803
10347
|
error: {
|
|
@@ -9828,7 +10372,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9828
10372
|
const contextStart = Math.max(1, startLine - 20);
|
|
9829
10373
|
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
9830
10374
|
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
9831
|
-
const ext =
|
|
10375
|
+
const ext = path35.extname(resolvedFile).toLowerCase();
|
|
9832
10376
|
const languageMap = {
|
|
9833
10377
|
".ts": "typescript",
|
|
9834
10378
|
".tsx": "typescript",
|
|
@@ -9963,10 +10507,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
9963
10507
|
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() } });
|
|
9964
10508
|
}
|
|
9965
10509
|
});
|
|
9966
|
-
if (
|
|
10510
|
+
if (fs34.existsSync(WEB_DIST)) {
|
|
9967
10511
|
app.use(express.static(WEB_DIST));
|
|
9968
10512
|
app.get("/{*path}", (_req, res) => {
|
|
9969
|
-
res.sendFile(
|
|
10513
|
+
res.sendFile(path35.join(WEB_DIST, "index.html"));
|
|
9970
10514
|
});
|
|
9971
10515
|
}
|
|
9972
10516
|
app.use("/admin", requireRole("admin"));
|
|
@@ -10495,22 +11039,22 @@ function suggestTests(graph, symbolName) {
|
|
|
10495
11039
|
const callPaths = [];
|
|
10496
11040
|
const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
|
|
10497
11041
|
while (pathQueue.length > 0 && callPaths.length < 5) {
|
|
10498
|
-
const { id, path:
|
|
10499
|
-
let
|
|
11042
|
+
const { id, path: path36, depth } = pathQueue.shift();
|
|
11043
|
+
let hasCallers2 = false;
|
|
10500
11044
|
for (const edge of graph.findEdgesTo(id)) {
|
|
10501
11045
|
if (edge.kind !== "calls") continue;
|
|
10502
11046
|
const callerNode = graph.getNode(edge.source);
|
|
10503
11047
|
if (!callerNode) continue;
|
|
10504
|
-
|
|
10505
|
-
const newPath = [callerNode.name, ...
|
|
11048
|
+
hasCallers2 = true;
|
|
11049
|
+
const newPath = [callerNode.name, ...path36];
|
|
10506
11050
|
if (depth + 1 >= 3 || callPaths.length >= 5) {
|
|
10507
11051
|
if (callPaths.length < 5) callPaths.push(newPath);
|
|
10508
11052
|
continue;
|
|
10509
11053
|
}
|
|
10510
11054
|
pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
|
|
10511
11055
|
}
|
|
10512
|
-
if (!
|
|
10513
|
-
callPaths.push(
|
|
11056
|
+
if (!hasCallers2 && path36.length > 1) {
|
|
11057
|
+
callPaths.push(path36);
|
|
10514
11058
|
}
|
|
10515
11059
|
}
|
|
10516
11060
|
if (callPaths.length === 0) {
|
|
@@ -10985,6 +11529,70 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
10985
11529
|
},
|
|
10986
11530
|
required: ["cluster"]
|
|
10987
11531
|
}
|
|
11532
|
+
},
|
|
11533
|
+
{
|
|
11534
|
+
name: "deprecated_usage",
|
|
11535
|
+
description: "Find usages of deprecated APIs in the codebase",
|
|
11536
|
+
inputSchema: {
|
|
11537
|
+
type: "object",
|
|
11538
|
+
properties: {
|
|
11539
|
+
scope: { type: "string", description: "Directory scope filter" },
|
|
11540
|
+
..._tokenProp
|
|
11541
|
+
}
|
|
11542
|
+
}
|
|
11543
|
+
},
|
|
11544
|
+
{
|
|
11545
|
+
name: "complexity_hotspots",
|
|
11546
|
+
description: "Ranked list of functions/methods by cyclomatic complexity. Useful for identifying refactoring candidates.",
|
|
11547
|
+
inputSchema: {
|
|
11548
|
+
type: "object",
|
|
11549
|
+
properties: {
|
|
11550
|
+
scope: { type: "string", description: "Limit to a file path prefix (optional)" },
|
|
11551
|
+
limit: { type: "number", description: "Maximum number of results (default: 20)" },
|
|
11552
|
+
..._tokenProp
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11555
|
+
},
|
|
11556
|
+
{
|
|
11557
|
+
name: "coverage_gaps",
|
|
11558
|
+
description: "Find exported symbols with no test coverage, ranked by blast radius. Useful for prioritizing test writing.",
|
|
11559
|
+
inputSchema: {
|
|
11560
|
+
type: "object",
|
|
11561
|
+
properties: {
|
|
11562
|
+
scope: { type: "string", description: "Limit to a file path prefix (optional)" },
|
|
11563
|
+
limit: { type: "number", description: "Maximum number of untested results to return (default: 20)" },
|
|
11564
|
+
..._tokenProp
|
|
11565
|
+
}
|
|
11566
|
+
}
|
|
11567
|
+
},
|
|
11568
|
+
{
|
|
11569
|
+
name: "secrets",
|
|
11570
|
+
description: "Scan the knowledge graph for hardcoded secrets: API keys, passwords, tokens, private keys, high-entropy strings",
|
|
11571
|
+
inputSchema: {
|
|
11572
|
+
type: "object",
|
|
11573
|
+
properties: {
|
|
11574
|
+
scope: { type: "string", description: "Limit scan to files under this path prefix" },
|
|
11575
|
+
includeTestFiles: { type: "boolean", description: "Include test/spec/fixture files (default: false)" },
|
|
11576
|
+
..._tokenProp
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11579
|
+
},
|
|
11580
|
+
{
|
|
11581
|
+
name: "vulnerability_scan",
|
|
11582
|
+
description: "Scan the knowledge graph for OWASP vulnerabilities: SQL injection, XSS, SSRF, path traversal, command injection",
|
|
11583
|
+
inputSchema: {
|
|
11584
|
+
type: "object",
|
|
11585
|
+
properties: {
|
|
11586
|
+
scope: { type: "string", description: "Limit scan to files under this path prefix" },
|
|
11587
|
+
types: {
|
|
11588
|
+
type: "array",
|
|
11589
|
+
items: { type: "string", enum: ["SQL_INJECTION", "XSS", "SSRF", "PATH_TRAVERSAL", "COMMAND_INJECTION"] },
|
|
11590
|
+
description: "Vulnerability types to detect (default: all)"
|
|
11591
|
+
},
|
|
11592
|
+
severity: { type: "string", description: "Minimum severity to report: HIGH|MEDIUM|LOW (default: LOW)" },
|
|
11593
|
+
..._tokenProp
|
|
11594
|
+
}
|
|
11595
|
+
}
|
|
10988
11596
|
}
|
|
10989
11597
|
]
|
|
10990
11598
|
}));
|
|
@@ -11408,7 +12016,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
11408
12016
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
11409
12017
|
for (const node of graph.allNodes()) {
|
|
11410
12018
|
if (!node.filePath) continue;
|
|
11411
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
12019
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path35.sep, "");
|
|
11412
12020
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
11413
12021
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
11414
12022
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -11681,6 +12289,63 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
11681
12289
|
const result = summarizeCluster(graph, cluster);
|
|
11682
12290
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
11683
12291
|
}
|
|
12292
|
+
case "deprecated_usage": {
|
|
12293
|
+
const scope = a.scope;
|
|
12294
|
+
const { DeprecatedDetector: DeprecatedDetector2 } = await Promise.resolve().then(() => (init_deprecated_detector(), deprecated_detector_exports));
|
|
12295
|
+
const detector = new DeprecatedDetector2();
|
|
12296
|
+
detector.tagDeprecated(graph);
|
|
12297
|
+
const findings = detector.detect(graph, scope);
|
|
12298
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
12299
|
+
}
|
|
12300
|
+
// ── complexity_hotspots ────────────────────────────────────────────────
|
|
12301
|
+
case "complexity_hotspots": {
|
|
12302
|
+
const { computeComplexity: computeComplexity2 } = await Promise.resolve().then(() => (init_complexity(), complexity_exports));
|
|
12303
|
+
const scope = a.scope;
|
|
12304
|
+
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
12305
|
+
const hotspots = computeComplexity2(graph, scope).slice(0, limit);
|
|
12306
|
+
return { content: [{ type: "text", text: JSON.stringify({ hotspots, total: hotspots.length }, null, 2) }] };
|
|
12307
|
+
}
|
|
12308
|
+
// ── coverage_gaps ──────────────────────────────────────────────────────
|
|
12309
|
+
case "coverage_gaps": {
|
|
12310
|
+
const { computeCoverage: computeCoverage2 } = await Promise.resolve().then(() => (init_test_coverage(), test_coverage_exports));
|
|
12311
|
+
const scope = a.scope;
|
|
12312
|
+
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
12313
|
+
const summary = computeCoverage2(graph, scope);
|
|
12314
|
+
const untestedByRisk = summary.untestedByRisk.slice(0, limit);
|
|
12315
|
+
return {
|
|
12316
|
+
content: [{
|
|
12317
|
+
type: "text",
|
|
12318
|
+
text: JSON.stringify({
|
|
12319
|
+
untestedByRisk,
|
|
12320
|
+
coveragePct: summary.coveragePct,
|
|
12321
|
+
totalExported: summary.totalExported,
|
|
12322
|
+
testedExported: summary.testedExported
|
|
12323
|
+
}, null, 2)
|
|
12324
|
+
}]
|
|
12325
|
+
};
|
|
12326
|
+
}
|
|
12327
|
+
// ── secrets ────────────────────────────────────────────────────────────
|
|
12328
|
+
case "secrets": {
|
|
12329
|
+
const { SecretScanner: SecretScanner2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
12330
|
+
const scanner = new SecretScanner2();
|
|
12331
|
+
const scope = a.scope;
|
|
12332
|
+
const includeTestFiles = a.includeTestFiles ?? false;
|
|
12333
|
+
const findings = scanner.scan(graph, { scope, includeTestFiles });
|
|
12334
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
12335
|
+
}
|
|
12336
|
+
// ── vulnerability_scan ─────────────────────────────────────────────────
|
|
12337
|
+
case "vulnerability_scan": {
|
|
12338
|
+
const { VulnerabilityDetector: VulnerabilityDetector2 } = await Promise.resolve().then(() => (init_vulnerability_detector(), vulnerability_detector_exports));
|
|
12339
|
+
const detector = new VulnerabilityDetector2();
|
|
12340
|
+
const scope = a.scope;
|
|
12341
|
+
const types = a.types;
|
|
12342
|
+
const minSev = (a.severity ?? "LOW").toUpperCase();
|
|
12343
|
+
const sevRank = { HIGH: 3, MEDIUM: 2, LOW: 1 };
|
|
12344
|
+
const minRank = sevRank[minSev] ?? 1;
|
|
12345
|
+
let findings = detector.detect(graph, { scope, types });
|
|
12346
|
+
findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
|
|
12347
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
12348
|
+
}
|
|
11684
12349
|
default:
|
|
11685
12350
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
11686
12351
|
}
|
|
@@ -11767,20 +12432,20 @@ function parseDiff(diffText) {
|
|
|
11767
12432
|
// src/cli/main.ts
|
|
11768
12433
|
init_metadata();
|
|
11769
12434
|
async function writeSkillFiles(graph, workspaceRoot, projectName) {
|
|
11770
|
-
const outputDir =
|
|
12435
|
+
const outputDir = path35.join(workspaceRoot, ".claude", "skills", "code-intel");
|
|
11771
12436
|
const areas = buildAreaMap(graph, workspaceRoot);
|
|
11772
12437
|
if (areas.length === 0) return { skills: [], outputDir };
|
|
11773
|
-
|
|
11774
|
-
|
|
12438
|
+
fs34.rmSync(outputDir, { recursive: true, force: true });
|
|
12439
|
+
fs34.mkdirSync(outputDir, { recursive: true });
|
|
11775
12440
|
const skills = [];
|
|
11776
12441
|
const usedNames = /* @__PURE__ */ new Set();
|
|
11777
12442
|
for (const area of areas) {
|
|
11778
12443
|
const kebab = uniqueKebab(area.label, usedNames);
|
|
11779
12444
|
usedNames.add(kebab);
|
|
11780
12445
|
const content = renderSkill(area, projectName, kebab);
|
|
11781
|
-
const dir =
|
|
11782
|
-
|
|
11783
|
-
|
|
12446
|
+
const dir = path35.join(outputDir, kebab);
|
|
12447
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
12448
|
+
fs34.writeFileSync(path35.join(dir, "SKILL.md"), content, "utf-8");
|
|
11784
12449
|
skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
|
|
11785
12450
|
}
|
|
11786
12451
|
return { skills, outputDir };
|
|
@@ -11960,11 +12625,20 @@ var BLOCK_START = "<!-- code-intel:start -->";
|
|
|
11960
12625
|
var BLOCK_END = "<!-- code-intel:end -->";
|
|
11961
12626
|
function writeContextFiles(workspaceRoot, projectName, stats, skills) {
|
|
11962
12627
|
const block = buildBlock(projectName, stats, skills);
|
|
11963
|
-
upsertFile(
|
|
11964
|
-
upsertFile(
|
|
12628
|
+
upsertFile(path35.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
|
|
12629
|
+
upsertFile(path35.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
|
|
12630
|
+
const githubDir = path35.join(workspaceRoot, ".github");
|
|
12631
|
+
if (!fs34.existsSync(githubDir)) fs34.mkdirSync(githubDir, { recursive: true });
|
|
12632
|
+
upsertFile(path35.join(githubDir, "copilot-instructions.md"), block, "copilot-instructions.md");
|
|
12633
|
+
const cursorDir = path35.join(workspaceRoot, ".cursor", "rules");
|
|
12634
|
+
if (!fs34.existsSync(cursorDir)) fs34.mkdirSync(cursorDir, { recursive: true });
|
|
12635
|
+
upsertFile(path35.join(cursorDir, "code-intel.mdc"), block, "code-intel.mdc");
|
|
12636
|
+
const kiroDir = path35.join(workspaceRoot, ".kiro", "steering");
|
|
12637
|
+
if (!fs34.existsSync(kiroDir)) fs34.mkdirSync(kiroDir, { recursive: true });
|
|
12638
|
+
upsertFile(path35.join(kiroDir, "code-intel.md"), block, "code-intel.md");
|
|
11965
12639
|
}
|
|
11966
12640
|
function buildBlock(projectName, stats, skills) {
|
|
11967
|
-
const
|
|
12641
|
+
const skillTableRows = skills.map(
|
|
11968
12642
|
(s) => `| Work in \`${s.label}\` (${s.symbolCount} symbols) | \`.claude/skills/code-intel/${s.name}/SKILL.md\` |`
|
|
11969
12643
|
).join("\n");
|
|
11970
12644
|
const skillTable = `| Task | Skill file |
|
|
@@ -11972,7 +12646,15 @@ function buildBlock(projectName, stats, skills) {
|
|
|
11972
12646
|
| Understand architecture / "How does X work?" | Load \`code-intel-exploring\` skill |
|
|
11973
12647
|
| Blast radius / "What breaks if I change X?" | Load \`code-intel-impact\` skill |
|
|
11974
12648
|
| Debugging / "Why is X failing?" | Load \`code-intel-debugging\` skill |
|
|
11975
|
-
${
|
|
12649
|
+
${skillTableRows ? skillTableRows + "\n" : ""}`;
|
|
12650
|
+
const skillLoadInstructions = skills.length > 0 ? `
|
|
12651
|
+
## When to Load a Skill
|
|
12652
|
+
|
|
12653
|
+
Before working deeply in a subsystem, **load the matching skill file** listed above.
|
|
12654
|
+
Each skill gives you symbol maps, key entry points, and safe-change guidance for that area.
|
|
12655
|
+
|
|
12656
|
+
${skills.map((s) => `- Working in **${s.label}**? \u2192 Load \`.claude/skills/code-intel/${s.name}/SKILL.md\``).join("\n")}
|
|
12657
|
+
` : "";
|
|
11976
12658
|
return `${BLOCK_START}
|
|
11977
12659
|
# Code Intelligence \u2014 ${projectName}
|
|
11978
12660
|
|
|
@@ -11983,39 +12665,114 @@ Indexed: **${stats.nodes.toLocaleString()} nodes** | **${stats.edges.toLocaleStr
|
|
|
11983
12665
|
|
|
11984
12666
|
> Index stale? Re-run: \`code-intel analyze\`
|
|
11985
12667
|
|
|
11986
|
-
##
|
|
12668
|
+
## Mandatory Rules \u2014 ALL Agents (Amp, Claude Code, Codex, Copilot, Cursor, Aider, Gemini, Kiro, Trae, Hermes, Factory, OpenCode, Pi, Antigravity, OpenClaw, and others)
|
|
11987
12669
|
|
|
11988
|
-
|
|
11989
|
-
- **Before committing**, verify scope with \`code-intel inspect <symbol>\`.
|
|
11990
|
-
- Use \`code-intel search "<concept>"\` to find related symbols instead of grepping.
|
|
11991
|
-
- Warn the user if impact shows \u2265 5 direct callers (**HIGH risk**).
|
|
12670
|
+
These rules apply to **every coding agent or AI assistant** working in this repository.
|
|
11992
12671
|
|
|
11993
|
-
|
|
12672
|
+
### Before Implementing Any Change
|
|
12673
|
+
1. Run \`code-intel search "<concept>"\` \u2014 find existing symbols related to the task.
|
|
12674
|
+
2. Run \`code-intel inspect <symbol>\` \u2014 understand callers, callees, and imports.
|
|
12675
|
+
3. Run \`code-intel impact <symbol>\` \u2014 check blast radius before touching anything.
|
|
12676
|
+
4. **If impact shows \u2265 5 direct callers \u2192 warn the user: HIGH risk.**
|
|
11994
12677
|
|
|
11995
|
-
|
|
11996
|
-
- NEVER ignore impact warnings \u2014 always report blast radius to the user.
|
|
12678
|
+
### While Implementing / Fixing a Bug
|
|
11997
12679
|
- NEVER open a file cold \u2014 always \`code-intel search\` first.
|
|
11998
12680
|
- NEVER grep for symbols \u2014 use \`code-intel search\` instead.
|
|
12681
|
+
- NEVER rename symbols with find-and-replace \u2014 use \`code-intel inspect\` to find all usages first.
|
|
12682
|
+
- Use \`code-intel inspect <symbol>\` to understand a function's callers/callees before modifying it.
|
|
12683
|
+
- Use \`code-intel query "TRAVERSE CALLS FROM '<symbol>' DEPTH 3"\` to trace execution paths.
|
|
12684
|
+
|
|
12685
|
+
### Before Committing / Code Review
|
|
12686
|
+
- Run \`code-intel impact <symbol>\` for every symbol you changed.
|
|
12687
|
+
- Run \`code-intel pr-impact --base main --head HEAD\` to see full PR blast radius.
|
|
12688
|
+
- Fail PR if HIGH risk symbols are changed without reviewer sign-off.
|
|
12689
|
+
|
|
12690
|
+
### Studying the Codebase
|
|
12691
|
+
- Use \`code-intel search "<concept>"\` to explore unfamiliar areas.
|
|
12692
|
+
- Use \`code-intel inspect <symbol>\` to see a symbol's full context.
|
|
12693
|
+
- Use \`code-intel serve\` to open the interactive Web UI for graph exploration.
|
|
12694
|
+
- Use subsystem skills (see table below) for deep-dive on a specific area.
|
|
12695
|
+
|
|
12696
|
+
## Never Do
|
|
12697
|
+
|
|
12698
|
+
- NEVER ignore impact warnings \u2014 always report blast radius to the user.
|
|
12699
|
+
- NEVER skip \`code-intel search\` before grepping or opening files.
|
|
12700
|
+
- NEVER make changes to a symbol with \u2265 5 callers without running \`code-intel impact\` first.
|
|
12701
|
+
- NEVER use find-and-replace for symbol renames.
|
|
12702
|
+
|
|
12703
|
+
## Development Workflow
|
|
12704
|
+
|
|
12705
|
+
### \u{1F527} Implement a New Feature
|
|
12706
|
+
\`\`\`
|
|
12707
|
+
1. code-intel search "<feature concept>" # find related existing symbols
|
|
12708
|
+
2. code-intel inspect <related-symbol> # understand context & callers
|
|
12709
|
+
3. Load subsystem skill (see Skills table) # deep-dive the area
|
|
12710
|
+
4. Implement changes
|
|
12711
|
+
5. code-intel impact <changed-symbol> # verify blast radius
|
|
12712
|
+
6. code-intel pr-impact --base main # full PR summary before commit
|
|
12713
|
+
\`\`\`
|
|
12714
|
+
|
|
12715
|
+
### \u{1F41B} Fix a Bug
|
|
12716
|
+
\`\`\`
|
|
12717
|
+
1. code-intel search "<buggy behavior>" # locate the symbol
|
|
12718
|
+
2. code-intel query "TRAVERSE CALLS FROM '<symbol>' DEPTH 3" # trace execution path
|
|
12719
|
+
3. code-intel inspect <symbol> # find all callers that may be affected
|
|
12720
|
+
4. Fix the bug
|
|
12721
|
+
5. code-intel impact <symbol> # confirm no unexpected side effects
|
|
12722
|
+
\`\`\`
|
|
12723
|
+
|
|
12724
|
+
### \u{1F52C} Study / Understand Code
|
|
12725
|
+
\`\`\`
|
|
12726
|
+
1. code-intel search "<concept>" # discover entry points
|
|
12727
|
+
2. code-intel inspect <symbol> # full context: callers, callees, imports
|
|
12728
|
+
3. code-intel query "TRAVERSE CALLS FROM '<symbol>' DEPTH 3" # execution call graph
|
|
12729
|
+
4. code-intel query "PATH FROM '<symbol>' TO '<target>'" # path between two symbols
|
|
12730
|
+
5. Load subsystem skill # structured deep-dive
|
|
12731
|
+
\`\`\`
|
|
12732
|
+
|
|
12733
|
+
### \u{1F440} Code Review
|
|
12734
|
+
\`\`\`
|
|
12735
|
+
1. code-intel pr-impact --base main --head HEAD # blast radius of all PR changes
|
|
12736
|
+
2. code-intel impact <each-changed-symbol> # per-symbol risk check
|
|
12737
|
+
3. Flag HIGH risk (\u2265 5 callers) for reviewer sign-off
|
|
12738
|
+
\`\`\`
|
|
12739
|
+
|
|
12740
|
+
### \u{1F504} Maintain / Refactor
|
|
12741
|
+
\`\`\`
|
|
12742
|
+
1. code-intel inspect <symbol> # find ALL usages before touching
|
|
12743
|
+
2. code-intel impact <symbol> # blast radius \u2014 plan your changes
|
|
12744
|
+
3. Make changes incrementally
|
|
12745
|
+
4. code-intel pr-impact --base main # validate scope hasn't exploded
|
|
12746
|
+
\`\`\`
|
|
11999
12747
|
|
|
12000
12748
|
## CLI Quick Reference
|
|
12001
12749
|
|
|
12002
12750
|
\`\`\`bash
|
|
12003
|
-
code-intel analyze [path]
|
|
12004
|
-
code-intel serve [path]
|
|
12005
|
-
code-intel search <query>
|
|
12006
|
-
code-intel inspect <symbol>
|
|
12007
|
-
code-intel impact <symbol>
|
|
12008
|
-
code-intel
|
|
12009
|
-
code-intel
|
|
12751
|
+
code-intel analyze [path] # Build / refresh the knowledge graph
|
|
12752
|
+
code-intel serve [path] # Start HTTP API + Web UI on :4747
|
|
12753
|
+
code-intel search <query> # Find symbols by concept/name
|
|
12754
|
+
code-intel inspect <symbol> # Callers, callees, imports, cluster
|
|
12755
|
+
code-intel impact <symbol> # Blast radius (who breaks if this changes)
|
|
12756
|
+
code-intel query "TRAVERSE CALLS FROM '<symbol>' DEPTH 3" # Trace execution call graph
|
|
12757
|
+
code-intel query "PATH FROM '<sym>' TO '<target>'" # Find path between two symbols
|
|
12758
|
+
code-intel query "FIND function WHERE name CONTAINS '<x>'" # GQL symbol search
|
|
12759
|
+
code-intel pr-impact --base main --head HEAD # Full PR blast radius report
|
|
12760
|
+
code-intel complexity [path] --top 10 # Cyclomatic complexity hotspots
|
|
12761
|
+
code-intel coverage [path] # Untested exported symbols by blast radius
|
|
12762
|
+
code-intel secrets [path] # Scan for hardcoded secrets
|
|
12763
|
+
code-intel scan [path] --severity high # OWASP vulnerability scan
|
|
12764
|
+
code-intel deprecated [path] # Find deprecated API usages
|
|
12765
|
+
code-intel status [path] # Index freshness and stats
|
|
12766
|
+
code-intel clean [path] # Remove index data
|
|
12010
12767
|
\`\`\`
|
|
12011
12768
|
|
|
12012
12769
|
## Skills
|
|
12013
|
-
|
|
12770
|
+
${skillLoadInstructions}
|
|
12014
12771
|
${skillTable}
|
|
12015
12772
|
${BLOCK_END}`;
|
|
12016
12773
|
}
|
|
12017
12774
|
function upsertFile(filePath, block, fileName) {
|
|
12018
|
-
if (!
|
|
12775
|
+
if (!fs34.existsSync(filePath)) {
|
|
12019
12776
|
const newContent = [
|
|
12020
12777
|
`# ${fileName}`,
|
|
12021
12778
|
"",
|
|
@@ -12026,17 +12783,17 @@ function upsertFile(filePath, block, fileName) {
|
|
|
12026
12783
|
"<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
|
|
12027
12784
|
""
|
|
12028
12785
|
].join("\n");
|
|
12029
|
-
|
|
12786
|
+
fs34.writeFileSync(filePath, newContent, "utf-8");
|
|
12030
12787
|
return;
|
|
12031
12788
|
}
|
|
12032
|
-
const existing =
|
|
12789
|
+
const existing = fs34.readFileSync(filePath, "utf-8");
|
|
12033
12790
|
const startIdx = findLineMarker(existing, BLOCK_START);
|
|
12034
12791
|
const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
|
|
12035
12792
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
12036
12793
|
const before = existing.slice(0, startIdx);
|
|
12037
12794
|
const after = existing.slice(endIdx + BLOCK_END.length);
|
|
12038
12795
|
const updated = (before + block + after).trimEnd() + "\n";
|
|
12039
|
-
|
|
12796
|
+
fs34.writeFileSync(filePath, updated, "utf-8");
|
|
12040
12797
|
return;
|
|
12041
12798
|
}
|
|
12042
12799
|
const appended = [
|
|
@@ -12049,7 +12806,7 @@ function upsertFile(filePath, block, fileName) {
|
|
|
12049
12806
|
block,
|
|
12050
12807
|
""
|
|
12051
12808
|
].join("\n");
|
|
12052
|
-
|
|
12809
|
+
fs34.writeFileSync(filePath, appended, "utf-8");
|
|
12053
12810
|
}
|
|
12054
12811
|
function findLineMarker(content, marker, startFrom = 0) {
|
|
12055
12812
|
let idx = content.indexOf(marker, startFrom);
|
|
@@ -12091,14 +12848,14 @@ function getChangedFilesSince(workspaceRoot, baseHash) {
|
|
|
12091
12848
|
function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
|
|
12092
12849
|
const changed = [];
|
|
12093
12850
|
for (const absPath of allFilePaths) {
|
|
12094
|
-
const rel =
|
|
12851
|
+
const rel = path35.relative(workspaceRoot, absPath);
|
|
12095
12852
|
const stored = storedMtimes[rel];
|
|
12096
12853
|
if (stored === void 0) {
|
|
12097
12854
|
changed.push(absPath);
|
|
12098
12855
|
continue;
|
|
12099
12856
|
}
|
|
12100
12857
|
try {
|
|
12101
|
-
const { mtimeMs } =
|
|
12858
|
+
const { mtimeMs } = fs34.statSync(absPath);
|
|
12102
12859
|
if (mtimeMs > stored) changed.push(absPath);
|
|
12103
12860
|
} catch {
|
|
12104
12861
|
changed.push(absPath);
|
|
@@ -12110,8 +12867,8 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
|
|
|
12110
12867
|
const snap = {};
|
|
12111
12868
|
for (const absPath of filePaths) {
|
|
12112
12869
|
try {
|
|
12113
|
-
const { mtimeMs } =
|
|
12114
|
-
snap[
|
|
12870
|
+
const { mtimeMs } = fs34.statSync(absPath);
|
|
12871
|
+
snap[path35.relative(workspaceRoot, absPath)] = mtimeMs;
|
|
12115
12872
|
} catch {
|
|
12116
12873
|
}
|
|
12117
12874
|
}
|
|
@@ -12122,8 +12879,8 @@ function decideIncremental(workspaceRoot, allFilePaths, prevCommitHash, storedMt
|
|
|
12122
12879
|
if (prevCommitHash) {
|
|
12123
12880
|
const changed = getChangedFilesSince(workspaceRoot, prevCommitHash);
|
|
12124
12881
|
if (changed !== null) {
|
|
12125
|
-
const scanSet = new Set(allFilePaths.map((p) =>
|
|
12126
|
-
const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) =>
|
|
12882
|
+
const scanSet = new Set(allFilePaths.map((p) => path35.relative(workspaceRoot, p)));
|
|
12883
|
+
const changedInScan = changed.filter((rel) => scanSet.has(rel)).map((rel) => path35.join(workspaceRoot, rel));
|
|
12127
12884
|
if (total > 0 && changedInScan.length / total > 0.2) {
|
|
12128
12885
|
return { incremental: false, fallbackReason: `changed files (${changedInScan.length}) > 20% of total (${total})` };
|
|
12129
12886
|
}
|
|
@@ -12150,11 +12907,11 @@ init_group_sync();
|
|
|
12150
12907
|
function expandGlob(root, pattern) {
|
|
12151
12908
|
const parts = pattern.replace(/\/\*\*?$/, "").split("/").filter(Boolean);
|
|
12152
12909
|
if (parts.length === 0) return [];
|
|
12153
|
-
const dir =
|
|
12154
|
-
if (!
|
|
12155
|
-
return
|
|
12910
|
+
const dir = path35.join(root, ...parts);
|
|
12911
|
+
if (!fs34.existsSync(dir)) return [];
|
|
12912
|
+
return fs34.readdirSync(dir).map((entry) => path35.join(dir, entry)).filter((p) => {
|
|
12156
12913
|
try {
|
|
12157
|
-
return
|
|
12914
|
+
return fs34.statSync(p).isDirectory();
|
|
12158
12915
|
} catch {
|
|
12159
12916
|
return false;
|
|
12160
12917
|
}
|
|
@@ -12165,11 +12922,11 @@ function resolvePackages(root, patterns) {
|
|
|
12165
12922
|
for (const pattern of patterns) {
|
|
12166
12923
|
const dirs = expandGlob(root, pattern);
|
|
12167
12924
|
for (const dir of dirs) {
|
|
12168
|
-
const pkgJsonPath =
|
|
12169
|
-
if (!
|
|
12925
|
+
const pkgJsonPath = path35.join(dir, "package.json");
|
|
12926
|
+
if (!fs34.existsSync(pkgJsonPath)) continue;
|
|
12170
12927
|
try {
|
|
12171
|
-
const pkgJson = JSON.parse(
|
|
12172
|
-
const name = pkgJson.name ??
|
|
12928
|
+
const pkgJson = JSON.parse(fs34.readFileSync(pkgJsonPath, "utf-8"));
|
|
12929
|
+
const name = pkgJson.name ?? path35.basename(dir);
|
|
12173
12930
|
packages.push({ name, path: dir });
|
|
12174
12931
|
} catch {
|
|
12175
12932
|
}
|
|
@@ -12178,13 +12935,13 @@ function resolvePackages(root, patterns) {
|
|
|
12178
12935
|
return packages;
|
|
12179
12936
|
}
|
|
12180
12937
|
async function detectWorkspace(root) {
|
|
12181
|
-
const turboJsonPath =
|
|
12182
|
-
if (
|
|
12938
|
+
const turboJsonPath = path35.join(root, "turbo.json");
|
|
12939
|
+
if (fs34.existsSync(turboJsonPath)) {
|
|
12183
12940
|
let patterns = [];
|
|
12184
|
-
const pkgJsonPath =
|
|
12185
|
-
if (
|
|
12941
|
+
const pkgJsonPath = path35.join(root, "package.json");
|
|
12942
|
+
if (fs34.existsSync(pkgJsonPath)) {
|
|
12186
12943
|
try {
|
|
12187
|
-
const pkgJson = JSON.parse(
|
|
12944
|
+
const pkgJson = JSON.parse(fs34.readFileSync(pkgJsonPath, "utf-8"));
|
|
12188
12945
|
if (pkgJson.workspaces) {
|
|
12189
12946
|
patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
|
|
12190
12947
|
}
|
|
@@ -12192,16 +12949,16 @@ async function detectWorkspace(root) {
|
|
|
12192
12949
|
}
|
|
12193
12950
|
}
|
|
12194
12951
|
if (patterns.length === 0) {
|
|
12195
|
-
const fallbackDir =
|
|
12196
|
-
if (
|
|
12952
|
+
const fallbackDir = path35.join(root, "packages");
|
|
12953
|
+
if (fs34.existsSync(fallbackDir)) patterns = ["packages/*"];
|
|
12197
12954
|
}
|
|
12198
12955
|
const packages = resolvePackages(root, patterns);
|
|
12199
12956
|
return { type: "turborepo", root, packages };
|
|
12200
12957
|
}
|
|
12201
|
-
const npmPkgJsonPath =
|
|
12202
|
-
if (
|
|
12958
|
+
const npmPkgJsonPath = path35.join(root, "package.json");
|
|
12959
|
+
if (fs34.existsSync(npmPkgJsonPath)) {
|
|
12203
12960
|
try {
|
|
12204
|
-
const pkgJson = JSON.parse(
|
|
12961
|
+
const pkgJson = JSON.parse(fs34.readFileSync(npmPkgJsonPath, "utf-8"));
|
|
12205
12962
|
if (pkgJson.workspaces) {
|
|
12206
12963
|
const patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
|
|
12207
12964
|
const packages = resolvePackages(root, patterns);
|
|
@@ -12210,11 +12967,11 @@ async function detectWorkspace(root) {
|
|
|
12210
12967
|
} catch {
|
|
12211
12968
|
}
|
|
12212
12969
|
}
|
|
12213
|
-
const pnpmYamlPath =
|
|
12214
|
-
if (
|
|
12970
|
+
const pnpmYamlPath = path35.join(root, "pnpm-workspace.yaml");
|
|
12971
|
+
if (fs34.existsSync(pnpmYamlPath)) {
|
|
12215
12972
|
const patterns = [];
|
|
12216
12973
|
try {
|
|
12217
|
-
const content =
|
|
12974
|
+
const content = fs34.readFileSync(pnpmYamlPath, "utf-8");
|
|
12218
12975
|
let inPackages = false;
|
|
12219
12976
|
for (const line of content.split("\n")) {
|
|
12220
12977
|
if (/^packages\s*:/.test(line)) {
|
|
@@ -12234,30 +12991,30 @@ async function detectWorkspace(root) {
|
|
|
12234
12991
|
const packages = resolvePackages(root, patterns);
|
|
12235
12992
|
return { type: "pnpm", root, packages };
|
|
12236
12993
|
}
|
|
12237
|
-
const nxJsonPath =
|
|
12238
|
-
if (
|
|
12994
|
+
const nxJsonPath = path35.join(root, "nx.json");
|
|
12995
|
+
if (fs34.existsSync(nxJsonPath)) {
|
|
12239
12996
|
const packages = [];
|
|
12240
12997
|
const scanForProjects = (dir, depth) => {
|
|
12241
12998
|
if (depth > 2) return;
|
|
12242
12999
|
let entries;
|
|
12243
13000
|
try {
|
|
12244
|
-
entries =
|
|
13001
|
+
entries = fs34.readdirSync(dir);
|
|
12245
13002
|
} catch {
|
|
12246
13003
|
return;
|
|
12247
13004
|
}
|
|
12248
13005
|
for (const entry of entries) {
|
|
12249
13006
|
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
12250
|
-
const fullPath =
|
|
13007
|
+
const fullPath = path35.join(dir, entry);
|
|
12251
13008
|
try {
|
|
12252
|
-
if (!
|
|
13009
|
+
if (!fs34.statSync(fullPath).isDirectory()) continue;
|
|
12253
13010
|
} catch {
|
|
12254
13011
|
continue;
|
|
12255
13012
|
}
|
|
12256
|
-
const projectJsonPath =
|
|
12257
|
-
if (
|
|
13013
|
+
const projectJsonPath = path35.join(fullPath, "project.json");
|
|
13014
|
+
if (fs34.existsSync(projectJsonPath)) {
|
|
12258
13015
|
try {
|
|
12259
|
-
const proj = JSON.parse(
|
|
12260
|
-
const name = proj.name ??
|
|
13016
|
+
const proj = JSON.parse(fs34.readFileSync(projectJsonPath, "utf-8"));
|
|
13017
|
+
const name = proj.name ?? path35.basename(fullPath);
|
|
12261
13018
|
packages.push({ name, path: fullPath });
|
|
12262
13019
|
} catch {
|
|
12263
13020
|
}
|
|
@@ -12365,17 +13122,17 @@ var MigrationRunner = class {
|
|
|
12365
13122
|
autoBackupBeforeMigration() {
|
|
12366
13123
|
try {
|
|
12367
13124
|
const dbFile = this.db.name;
|
|
12368
|
-
if (!dbFile || !
|
|
12369
|
-
const backupDir =
|
|
12370
|
-
|
|
13125
|
+
if (!dbFile || !fs34.existsSync(dbFile)) return;
|
|
13126
|
+
const backupDir = path35.join(os12.homedir(), ".code-intel", "backups", "pre-migration");
|
|
13127
|
+
fs34.mkdirSync(backupDir, { recursive: true });
|
|
12371
13128
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
12372
|
-
const baseName =
|
|
12373
|
-
const backupPath =
|
|
13129
|
+
const baseName = path35.basename(dbFile, ".db");
|
|
13130
|
+
const backupPath = path35.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
|
|
12374
13131
|
try {
|
|
12375
13132
|
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
12376
13133
|
} catch {
|
|
12377
13134
|
}
|
|
12378
|
-
|
|
13135
|
+
fs34.copyFileSync(dbFile, backupPath);
|
|
12379
13136
|
} catch {
|
|
12380
13137
|
}
|
|
12381
13138
|
}
|
|
@@ -12591,7 +13348,7 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
|
|
|
12591
13348
|
Docs: https://github.com/vohongtho/code-intel-platform
|
|
12592
13349
|
`);
|
|
12593
13350
|
async function analyzeWorkspace(targetPath, options) {
|
|
12594
|
-
const workspaceRoot =
|
|
13351
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
12595
13352
|
if (!options?.silent) console.log(`Analyzing: ${workspaceRoot}`);
|
|
12596
13353
|
logger_default.info(`analyze started: ${workspaceRoot}`);
|
|
12597
13354
|
if (options?.force) {
|
|
@@ -12612,14 +13369,14 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12612
13369
|
];
|
|
12613
13370
|
for (const f of wipeFiles) {
|
|
12614
13371
|
try {
|
|
12615
|
-
if (
|
|
13372
|
+
if (fs34.existsSync(f)) fs34.unlinkSync(f);
|
|
12616
13373
|
} catch {
|
|
12617
13374
|
}
|
|
12618
13375
|
}
|
|
12619
13376
|
}
|
|
12620
13377
|
if (!options?.skipGit) {
|
|
12621
|
-
const gitDir =
|
|
12622
|
-
if (!
|
|
13378
|
+
const gitDir = path35.join(workspaceRoot, ".git");
|
|
13379
|
+
if (!fs34.existsSync(gitDir)) {
|
|
12623
13380
|
logger_default.warn(`${workspaceRoot} is not a Git repository`);
|
|
12624
13381
|
}
|
|
12625
13382
|
}
|
|
@@ -12730,17 +13487,17 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12730
13487
|
const result = await runPipeline(phases, context2);
|
|
12731
13488
|
if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
|
|
12732
13489
|
const dbPath = getDbPath(workspaceRoot);
|
|
12733
|
-
if (
|
|
13490
|
+
if (fs34.existsSync(dbPath)) {
|
|
12734
13491
|
try {
|
|
12735
13492
|
const db = new DbManager(dbPath);
|
|
12736
13493
|
await db.init();
|
|
12737
13494
|
for (const absPath of incrementalChangedFiles) {
|
|
12738
|
-
const rel =
|
|
13495
|
+
const rel = path35.relative(workspaceRoot, absPath);
|
|
12739
13496
|
await removeNodesForFile(rel, db);
|
|
12740
13497
|
}
|
|
12741
13498
|
const { upsertNodes: upsertNodesBatch } = await Promise.resolve().then(() => (init_graph_loader(), graph_loader_exports));
|
|
12742
13499
|
const changedRelPaths = new Set(
|
|
12743
|
-
incrementalChangedFiles.map((f) =>
|
|
13500
|
+
incrementalChangedFiles.map((f) => path35.relative(workspaceRoot, f))
|
|
12744
13501
|
);
|
|
12745
13502
|
const nodesToUpsert = [...graph.allNodes()].filter(
|
|
12746
13503
|
(n) => changedRelPaths.has(n.filePath)
|
|
@@ -12765,7 +13522,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12765
13522
|
mergedMtimes = newMtimes;
|
|
12766
13523
|
}
|
|
12767
13524
|
const currentCommitHash = getCurrentCommitHash(workspaceRoot) ?? void 0;
|
|
12768
|
-
const repoName =
|
|
13525
|
+
const repoName = path35.basename(workspaceRoot);
|
|
12769
13526
|
const indexVersion = v4();
|
|
12770
13527
|
saveMetadata(workspaceRoot, {
|
|
12771
13528
|
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12803,7 +13560,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12803
13560
|
];
|
|
12804
13561
|
for (const f of newStaleFiles) {
|
|
12805
13562
|
try {
|
|
12806
|
-
if (
|
|
13563
|
+
if (fs34.existsSync(f)) fs34.unlinkSync(f);
|
|
12807
13564
|
} catch {
|
|
12808
13565
|
}
|
|
12809
13566
|
}
|
|
@@ -12820,21 +13577,21 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12820
13577
|
];
|
|
12821
13578
|
for (const f of staleFiles) {
|
|
12822
13579
|
try {
|
|
12823
|
-
if (
|
|
13580
|
+
if (fs34.existsSync(f)) fs34.unlinkSync(f);
|
|
12824
13581
|
} catch {
|
|
12825
13582
|
}
|
|
12826
13583
|
}
|
|
12827
13584
|
for (const f of newStaleFiles) {
|
|
12828
13585
|
if (f === dbPathNew) continue;
|
|
12829
|
-
if (
|
|
13586
|
+
if (fs34.existsSync(f)) {
|
|
12830
13587
|
const dest = f.replace(dbPathNew, dbPath);
|
|
12831
13588
|
try {
|
|
12832
|
-
|
|
13589
|
+
fs34.renameSync(f, dest);
|
|
12833
13590
|
} catch {
|
|
12834
13591
|
}
|
|
12835
13592
|
}
|
|
12836
13593
|
}
|
|
12837
|
-
|
|
13594
|
+
fs34.renameSync(dbPathNew, dbPath);
|
|
12838
13595
|
stopSpinner();
|
|
12839
13596
|
logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
|
|
12840
13597
|
if (!options?.silent) {
|
|
@@ -12855,7 +13612,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
12855
13612
|
const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
|
|
12856
13613
|
for (const f of staleVdb) {
|
|
12857
13614
|
try {
|
|
12858
|
-
if (
|
|
13615
|
+
if (fs34.existsSync(f)) fs34.unlinkSync(f);
|
|
12859
13616
|
} catch {
|
|
12860
13617
|
}
|
|
12861
13618
|
}
|
|
@@ -12979,8 +13736,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
12979
13736
|
const configFile = `${configDir}/claude_desktop_config.json`;
|
|
12980
13737
|
try {
|
|
12981
13738
|
let existing = {};
|
|
12982
|
-
if (
|
|
12983
|
-
existing = JSON.parse(
|
|
13739
|
+
if (fs34.existsSync(configFile)) {
|
|
13740
|
+
existing = JSON.parse(fs34.readFileSync(configFile, "utf-8"));
|
|
12984
13741
|
}
|
|
12985
13742
|
const merged = {
|
|
12986
13743
|
...existing,
|
|
@@ -12989,8 +13746,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
12989
13746
|
...mcpConfig.mcpServers
|
|
12990
13747
|
}
|
|
12991
13748
|
};
|
|
12992
|
-
|
|
12993
|
-
|
|
13749
|
+
fs34.mkdirSync(configDir, { recursive: true });
|
|
13750
|
+
fs34.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
12994
13751
|
console.log(`
|
|
12995
13752
|
\u2705 Written to ${configFile}`);
|
|
12996
13753
|
} catch (err) {
|
|
@@ -13060,10 +13817,10 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
|
|
|
13060
13817
|
$ code-intel mcp
|
|
13061
13818
|
$ code-intel mcp ./my-project
|
|
13062
13819
|
`).action(async (targetPath) => {
|
|
13063
|
-
const workspaceRoot =
|
|
13064
|
-
const repoName =
|
|
13820
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
13821
|
+
const repoName = path35.basename(workspaceRoot);
|
|
13065
13822
|
const dbPath = getDbPath(workspaceRoot);
|
|
13066
|
-
const existingIndex =
|
|
13823
|
+
const existingIndex = fs34.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
13067
13824
|
if (existingIndex) {
|
|
13068
13825
|
const graph = createKnowledgeGraph();
|
|
13069
13826
|
const db = new DbManager(dbPath);
|
|
@@ -13094,10 +13851,10 @@ program.command("serve").description("Start the local HTTP server + web UI for g
|
|
|
13094
13851
|
$ code-intel serve --port 8080
|
|
13095
13852
|
$ code-intel serve --force
|
|
13096
13853
|
`).action(async (targetPath, options) => {
|
|
13097
|
-
const workspaceRoot =
|
|
13098
|
-
const repoName =
|
|
13854
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
13855
|
+
const repoName = path35.basename(workspaceRoot);
|
|
13099
13856
|
const dbPath = getDbPath(workspaceRoot);
|
|
13100
|
-
const existingIndex = !options.force &&
|
|
13857
|
+
const existingIndex = !options.force && fs34.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
13101
13858
|
if (existingIndex) {
|
|
13102
13859
|
const meta = loadMetadata(workspaceRoot);
|
|
13103
13860
|
if (meta.parser === "regex" || meta.parser === void 0) {
|
|
@@ -13131,10 +13888,10 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
13131
13888
|
`).action(async (targetPath, options) => {
|
|
13132
13889
|
const { FileWatcher: FileWatcher2 } = await Promise.resolve().then(() => (init_file_watcher(), file_watcher_exports));
|
|
13133
13890
|
const { IncrementalIndexer: IncrementalIndexer2 } = await Promise.resolve().then(() => (init_incremental_indexer(), incremental_indexer_exports));
|
|
13134
|
-
const workspaceRoot =
|
|
13135
|
-
const repoName =
|
|
13891
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
13892
|
+
const repoName = path35.basename(workspaceRoot);
|
|
13136
13893
|
const dbPath = getDbPath(workspaceRoot);
|
|
13137
|
-
const existingIndex = !options.force &&
|
|
13894
|
+
const existingIndex = !options.force && fs34.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
13138
13895
|
let graph;
|
|
13139
13896
|
if (existingIndex) {
|
|
13140
13897
|
const meta = loadMetadata(workspaceRoot);
|
|
@@ -13169,7 +13926,7 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
13169
13926
|
type: "graph:updated",
|
|
13170
13927
|
indexVersion: meta?.indexVersion ?? "unknown",
|
|
13171
13928
|
stats: { nodes: graph.size.nodes, edges: graph.size.edges },
|
|
13172
|
-
changedFiles: changedFiles.map((f) =>
|
|
13929
|
+
changedFiles: changedFiles.map((f) => path35.relative(workspaceRoot, f)),
|
|
13173
13930
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13174
13931
|
});
|
|
13175
13932
|
}
|
|
@@ -13220,7 +13977,7 @@ program.command("status").description("Show index freshness and statistics for a
|
|
|
13220
13977
|
$ code-intel status
|
|
13221
13978
|
$ code-intel status ./my-project
|
|
13222
13979
|
`).action((targetPath) => {
|
|
13223
|
-
const workspaceRoot =
|
|
13980
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
13224
13981
|
const meta = loadMetadata(workspaceRoot);
|
|
13225
13982
|
if (!meta) {
|
|
13226
13983
|
console.log(`
|
|
@@ -13244,18 +14001,18 @@ function trashDirName(repoPath) {
|
|
|
13244
14001
|
return `.code-intel-trash-${date}`;
|
|
13245
14002
|
}
|
|
13246
14003
|
function softDeleteCodeIntel(repoPath) {
|
|
13247
|
-
const codeIntelDir =
|
|
13248
|
-
if (!
|
|
14004
|
+
const codeIntelDir = path35.join(repoPath, ".code-intel");
|
|
14005
|
+
if (!fs34.existsSync(codeIntelDir)) return;
|
|
13249
14006
|
const trashName = trashDirName();
|
|
13250
|
-
const trashDir =
|
|
14007
|
+
const trashDir = path35.join(repoPath, trashName);
|
|
13251
14008
|
let dest = trashDir;
|
|
13252
14009
|
let counter = 1;
|
|
13253
|
-
while (
|
|
14010
|
+
while (fs34.existsSync(dest)) {
|
|
13254
14011
|
dest = `${trashDir}-${counter++}`;
|
|
13255
14012
|
}
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
14013
|
+
fs34.renameSync(codeIntelDir, dest);
|
|
14014
|
+
fs34.writeFileSync(
|
|
14015
|
+
path35.join(dest, "TRASH_META.json"),
|
|
13259
14016
|
JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
|
|
13260
14017
|
);
|
|
13261
14018
|
console.log(` \u2713 Moved to trash: ${dest}`);
|
|
@@ -13264,15 +14021,15 @@ function softDeleteCodeIntel(repoPath) {
|
|
|
13264
14021
|
function purgeStaleTrashes(repoPath) {
|
|
13265
14022
|
const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
|
|
13266
14023
|
try {
|
|
13267
|
-
for (const entry of
|
|
14024
|
+
for (const entry of fs34.readdirSync(repoPath)) {
|
|
13268
14025
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
13269
|
-
const fullPath =
|
|
13270
|
-
const metaPath =
|
|
13271
|
-
if (
|
|
14026
|
+
const fullPath = path35.join(repoPath, entry);
|
|
14027
|
+
const metaPath = path35.join(fullPath, "TRASH_META.json");
|
|
14028
|
+
if (fs34.existsSync(metaPath)) {
|
|
13272
14029
|
try {
|
|
13273
|
-
const meta = JSON.parse(
|
|
14030
|
+
const meta = JSON.parse(fs34.readFileSync(metaPath, "utf-8"));
|
|
13274
14031
|
if (new Date(meta.deletedAt).getTime() < cutoff) {
|
|
13275
|
-
|
|
14032
|
+
fs34.rmSync(fullPath, { recursive: true, force: true });
|
|
13276
14033
|
console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
|
|
13277
14034
|
}
|
|
13278
14035
|
} catch {
|
|
@@ -13299,18 +14056,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
13299
14056
|
if (opts.listTrash) {
|
|
13300
14057
|
const repos = loadRegistry();
|
|
13301
14058
|
const roots = repos.map((r) => r.path);
|
|
13302
|
-
if (roots.length === 0) roots.push(
|
|
14059
|
+
if (roots.length === 0) roots.push(path35.resolve("."));
|
|
13303
14060
|
let found = 0;
|
|
13304
14061
|
for (const root of roots) {
|
|
13305
14062
|
try {
|
|
13306
|
-
for (const entry of
|
|
14063
|
+
for (const entry of fs34.readdirSync(root)) {
|
|
13307
14064
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
13308
|
-
const fullPath =
|
|
13309
|
-
const metaPath =
|
|
14065
|
+
const fullPath = path35.join(root, entry);
|
|
14066
|
+
const metaPath = path35.join(fullPath, "TRASH_META.json");
|
|
13310
14067
|
let deletedAt = "unknown";
|
|
13311
|
-
if (
|
|
14068
|
+
if (fs34.existsSync(metaPath)) {
|
|
13312
14069
|
try {
|
|
13313
|
-
deletedAt = JSON.parse(
|
|
14070
|
+
deletedAt = JSON.parse(fs34.readFileSync(metaPath, "utf-8")).deletedAt;
|
|
13314
14071
|
} catch {
|
|
13315
14072
|
}
|
|
13316
14073
|
}
|
|
@@ -13338,9 +14095,9 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
13338
14095
|
}
|
|
13339
14096
|
for (const r of repos) {
|
|
13340
14097
|
if (opts.purge) {
|
|
13341
|
-
const codeIntelDir =
|
|
13342
|
-
if (
|
|
13343
|
-
|
|
14098
|
+
const codeIntelDir = path35.join(r.path, ".code-intel");
|
|
14099
|
+
if (fs34.existsSync(codeIntelDir)) {
|
|
14100
|
+
fs34.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
13344
14101
|
console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
|
|
13345
14102
|
}
|
|
13346
14103
|
} else {
|
|
@@ -13354,11 +14111,11 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
13354
14111
|
`);
|
|
13355
14112
|
return;
|
|
13356
14113
|
}
|
|
13357
|
-
const workspaceRoot =
|
|
14114
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
13358
14115
|
if (opts.purge) {
|
|
13359
|
-
const codeIntelDir =
|
|
13360
|
-
if (
|
|
13361
|
-
|
|
14116
|
+
const codeIntelDir = path35.join(workspaceRoot, ".code-intel");
|
|
14117
|
+
if (fs34.existsSync(codeIntelDir)) {
|
|
14118
|
+
fs34.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
13362
14119
|
console.log(`
|
|
13363
14120
|
\u2713 Hard-deleted ${codeIntelDir}`);
|
|
13364
14121
|
}
|
|
@@ -13370,16 +14127,16 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
13370
14127
|
console.log(" Index cleaned.\n");
|
|
13371
14128
|
});
|
|
13372
14129
|
async function loadOrAnalyzeWorkspace(targetPath) {
|
|
13373
|
-
const workspaceRoot =
|
|
14130
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
13374
14131
|
const dbPath = getDbPath(workspaceRoot);
|
|
13375
|
-
const existingIndex =
|
|
14132
|
+
const existingIndex = fs34.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
13376
14133
|
if (existingIndex) {
|
|
13377
14134
|
const graph = createKnowledgeGraph();
|
|
13378
14135
|
const db = new DbManager(dbPath);
|
|
13379
14136
|
await db.init();
|
|
13380
14137
|
await loadGraphFromDB(graph, db);
|
|
13381
14138
|
db.close();
|
|
13382
|
-
return { graph, workspaceRoot, repoName:
|
|
14139
|
+
return { graph, workspaceRoot, repoName: path35.basename(workspaceRoot) };
|
|
13383
14140
|
}
|
|
13384
14141
|
return analyzeWorkspace(targetPath, { silent: true });
|
|
13385
14142
|
}
|
|
@@ -13514,6 +14271,43 @@ program.command("impact").description("Show the blast radius \u2014 all symbols
|
|
|
13514
14271
|
}
|
|
13515
14272
|
console.log("");
|
|
13516
14273
|
});
|
|
14274
|
+
program.command("deprecated").description("Find usages of deprecated APIs in the codebase").argument("[path]", "Path to the repository (default: current directory)", ".").option("--format <fmt>", "Output format: table or json", "table").addHelpText("after", `
|
|
14275
|
+
Scans the knowledge graph for deprecated symbols (via @deprecated JSDoc,
|
|
14276
|
+
@Deprecated annotations, #[deprecated] attributes, or known Node.js APIs)
|
|
14277
|
+
and reports every caller.
|
|
14278
|
+
|
|
14279
|
+
Examples:
|
|
14280
|
+
$ code-intel deprecated
|
|
14281
|
+
$ code-intel deprecated ./src --format json
|
|
14282
|
+
`).action(async (targetPath, options) => {
|
|
14283
|
+
const { graph } = await loadOrAnalyzeWorkspace(targetPath);
|
|
14284
|
+
const { DeprecatedDetector: DeprecatedDetector2 } = await Promise.resolve().then(() => (init_deprecated_detector(), deprecated_detector_exports));
|
|
14285
|
+
const detector = new DeprecatedDetector2();
|
|
14286
|
+
detector.tagDeprecated(graph);
|
|
14287
|
+
const findings = detector.detect(graph);
|
|
14288
|
+
if (options.format === "json") {
|
|
14289
|
+
console.log(JSON.stringify({ findings, total: findings.length }, null, 2));
|
|
14290
|
+
return;
|
|
14291
|
+
}
|
|
14292
|
+
console.log("\n \u25C8 Deprecated API Usage Report\n");
|
|
14293
|
+
if (findings.length === 0) {
|
|
14294
|
+
console.log(" No deprecated API usages found.\n");
|
|
14295
|
+
return;
|
|
14296
|
+
}
|
|
14297
|
+
console.log(` ${"Symbol".padEnd(30)} ${"File".padEnd(40)} Message`);
|
|
14298
|
+
console.log(` ${"-".repeat(100)}`);
|
|
14299
|
+
for (const f of findings) {
|
|
14300
|
+
console.log(` ${f.symbol.padEnd(30)} ${f.filePath.padEnd(40)} ${f.deprecationMessage}`);
|
|
14301
|
+
if (f.callers.length > 0) {
|
|
14302
|
+
console.log(`
|
|
14303
|
+
Callers:`);
|
|
14304
|
+
for (const c of f.callers) {
|
|
14305
|
+
console.log(` ${c.name.padEnd(30)} ${c.filePath}`);
|
|
14306
|
+
}
|
|
14307
|
+
console.log("");
|
|
14308
|
+
}
|
|
14309
|
+
}
|
|
14310
|
+
});
|
|
13517
14311
|
var groupCmd = program.command("group").description("Manage repository groups for multi-repo / monorepo service tracking").addHelpText("after", `
|
|
13518
14312
|
Repository groups let you track contracts (exports, routes, schemas, events)
|
|
13519
14313
|
across multiple indexed repos and detect cross-repo dependencies automatically.
|
|
@@ -13807,9 +14601,9 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
13807
14601
|
console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT IN REGISTRY`);
|
|
13808
14602
|
continue;
|
|
13809
14603
|
}
|
|
13810
|
-
const metaPath =
|
|
14604
|
+
const metaPath = path35.join(regEntry.path, ".code-intel", "meta.json");
|
|
13811
14605
|
try {
|
|
13812
|
-
const meta = JSON.parse(
|
|
14606
|
+
const meta = JSON.parse(fs34.readFileSync(metaPath, "utf-8"));
|
|
13813
14607
|
const indexedAt = meta.indexedAt;
|
|
13814
14608
|
const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
|
|
13815
14609
|
const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
|
|
@@ -13826,7 +14620,7 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
13826
14620
|
}
|
|
13827
14621
|
});
|
|
13828
14622
|
groupCmd.command("init-workspace [path]").description("Auto-discover workspace packages and create a group").option("--name <name>", "Group name (default: workspace root dirname)").option("--no-analyze", "Register packages without running analysis").option("--yes", "Skip confirmation prompt").option("--parallel <n>", "Concurrent analyses (default: 2)", "2").action(async (targetPath, opts) => {
|
|
13829
|
-
const root =
|
|
14623
|
+
const root = path35.resolve(targetPath ?? ".");
|
|
13830
14624
|
const ws = await detectWorkspace(root);
|
|
13831
14625
|
if (!ws) {
|
|
13832
14626
|
console.error(`
|
|
@@ -13835,7 +14629,7 @@ groupCmd.command("init-workspace [path]").description("Auto-discover workspace p
|
|
|
13835
14629
|
`);
|
|
13836
14630
|
process.exit(1);
|
|
13837
14631
|
}
|
|
13838
|
-
const groupName = opts.name ??
|
|
14632
|
+
const groupName = opts.name ?? path35.basename(root);
|
|
13839
14633
|
console.log(`
|
|
13840
14634
|
\u25C8 Workspace detected: ${ws.type}`);
|
|
13841
14635
|
console.log(` Group name : ${groupName}`);
|
|
@@ -14114,7 +14908,7 @@ backupCmd.command("create [path]").description("Create an encrypted backup of th
|
|
|
14114
14908
|
$ code-intel backup create
|
|
14115
14909
|
$ code-intel backup create ./my-project
|
|
14116
14910
|
`).action((targetPath = ".") => {
|
|
14117
|
-
const repoPath =
|
|
14911
|
+
const repoPath = path35.resolve(targetPath);
|
|
14118
14912
|
const svc = new BackupService();
|
|
14119
14913
|
try {
|
|
14120
14914
|
const entry = svc.createBackup(repoPath);
|
|
@@ -14143,7 +14937,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
|
|
|
14143
14937
|
Backups (${entries.length}):
|
|
14144
14938
|
`);
|
|
14145
14939
|
for (const e of entries) {
|
|
14146
|
-
const exists =
|
|
14940
|
+
const exists = fs34.existsSync(e.path);
|
|
14147
14941
|
const status = exists ? "\u2713" : "\u2717 (missing)";
|
|
14148
14942
|
console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
|
|
14149
14943
|
}
|
|
@@ -14156,7 +14950,7 @@ backupCmd.command("restore <id>").description("Restore a backup by ID").option("
|
|
|
14156
14950
|
`).action((id, opts) => {
|
|
14157
14951
|
const svc = new BackupService();
|
|
14158
14952
|
try {
|
|
14159
|
-
const targetPath = opts.target ?
|
|
14953
|
+
const targetPath = opts.target ? path35.resolve(opts.target) : void 0;
|
|
14160
14954
|
svc.restoreBackup(id, targetPath);
|
|
14161
14955
|
console.log(`
|
|
14162
14956
|
\u2705 Backup "${id}" restored successfully.
|
|
@@ -14175,8 +14969,8 @@ program.command("migrate").description("Manage database schema migrations").opti
|
|
|
14175
14969
|
$ code-intel migrate
|
|
14176
14970
|
$ code-intel migrate --rollback
|
|
14177
14971
|
`).action((opts) => {
|
|
14178
|
-
const dbPath = opts.db ??
|
|
14179
|
-
if (!
|
|
14972
|
+
const dbPath = opts.db ?? path35.join(os12.homedir(), ".code-intel", "users.db");
|
|
14973
|
+
if (!fs34.existsSync(dbPath)) {
|
|
14180
14974
|
console.error(`
|
|
14181
14975
|
\u2717 Database not found: ${dbPath}
|
|
14182
14976
|
Run \`code-intel serve\` or \`code-intel user create\` first.
|
|
@@ -14291,15 +15085,15 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
14291
15085
|
}
|
|
14292
15086
|
try {
|
|
14293
15087
|
const tokens = await pollDeviceFlow3(config, deviceResponse);
|
|
14294
|
-
const tokenPath =
|
|
15088
|
+
const tokenPath = path35.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
14295
15089
|
const tokenData = {
|
|
14296
15090
|
accessToken: tokens.accessToken,
|
|
14297
15091
|
refreshToken: tokens.refreshToken,
|
|
14298
15092
|
server: serverUrl,
|
|
14299
15093
|
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14300
15094
|
};
|
|
14301
|
-
|
|
14302
|
-
|
|
15095
|
+
fs34.mkdirSync(path35.dirname(tokenPath), { recursive: true });
|
|
15096
|
+
fs34.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
|
|
14303
15097
|
console.log(` \u2705 Authenticated successfully!`);
|
|
14304
15098
|
console.log(` Token stored at: ${tokenPath}`);
|
|
14305
15099
|
console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
|
|
@@ -14312,13 +15106,13 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
14312
15106
|
}
|
|
14313
15107
|
});
|
|
14314
15108
|
authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
|
|
14315
|
-
const tokenPath =
|
|
14316
|
-
if (!
|
|
15109
|
+
const tokenPath = path35.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
15110
|
+
if (!fs34.existsSync(tokenPath)) {
|
|
14317
15111
|
console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
|
|
14318
15112
|
return;
|
|
14319
15113
|
}
|
|
14320
15114
|
try {
|
|
14321
|
-
const data = JSON.parse(
|
|
15115
|
+
const data = JSON.parse(fs34.readFileSync(tokenPath, "utf-8"));
|
|
14322
15116
|
console.log(`
|
|
14323
15117
|
\u2705 OIDC token stored`);
|
|
14324
15118
|
console.log(` Server : ${data.server ?? "unknown"}`);
|
|
@@ -14330,9 +15124,9 @@ authCmd.command("status").description("Show the current OIDC authentication stat
|
|
|
14330
15124
|
}
|
|
14331
15125
|
});
|
|
14332
15126
|
authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
|
|
14333
|
-
const tokenPath =
|
|
14334
|
-
if (
|
|
14335
|
-
|
|
15127
|
+
const tokenPath = path35.join(os12.homedir(), ".code-intel", "oidc-token.json");
|
|
15128
|
+
if (fs34.existsSync(tokenPath)) {
|
|
15129
|
+
fs34.unlinkSync(tokenPath);
|
|
14336
15130
|
console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
|
|
14337
15131
|
} else {
|
|
14338
15132
|
console.log("\n No stored token found.\n");
|
|
@@ -14377,7 +15171,7 @@ authCmd.command("rotate-token <id>").description("Rotate an API token \u2014 iss
|
|
|
14377
15171
|
console.log("\n Save the new token now \u2014 it will NOT be shown again!\n");
|
|
14378
15172
|
console.log(" Usage: Authorization: Bearer <new-token>\n");
|
|
14379
15173
|
});
|
|
14380
|
-
var secretsCmd = program.command("
|
|
15174
|
+
var secretsCmd = program.command("keystore").description("Manage the encrypted .code-intel/.secrets store (AES-256-GCM)");
|
|
14381
15175
|
secretsCmd.command("set <key> <value>").description("Store a secret by key").action((key, value) => {
|
|
14382
15176
|
try {
|
|
14383
15177
|
setSecret(key, value);
|
|
@@ -14456,8 +15250,8 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
14456
15250
|
$ code-intel config-validate ./config.json
|
|
14457
15251
|
$ code-intel config-validate ~/.code-intel/config.json
|
|
14458
15252
|
`).action((file) => {
|
|
14459
|
-
const filePath =
|
|
14460
|
-
if (!
|
|
15253
|
+
const filePath = path35.resolve(file);
|
|
15254
|
+
if (!fs34.existsSync(filePath)) {
|
|
14461
15255
|
console.error(`
|
|
14462
15256
|
\u2717 File not found: ${filePath}
|
|
14463
15257
|
`);
|
|
@@ -14465,7 +15259,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
14465
15259
|
}
|
|
14466
15260
|
let cfg;
|
|
14467
15261
|
try {
|
|
14468
|
-
cfg = JSON.parse(
|
|
15262
|
+
cfg = JSON.parse(fs34.readFileSync(filePath, "utf-8"));
|
|
14469
15263
|
} catch (err) {
|
|
14470
15264
|
console.error(`
|
|
14471
15265
|
\u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
|
|
@@ -14486,7 +15280,7 @@ ${err instanceof Error ? err.message : err}
|
|
|
14486
15280
|
});
|
|
14487
15281
|
(function ensurePermissions() {
|
|
14488
15282
|
try {
|
|
14489
|
-
const dir =
|
|
15283
|
+
const dir = path35.join(os12.homedir(), ".code-intel");
|
|
14490
15284
|
secureMkdir(dir);
|
|
14491
15285
|
tightenDbFiles(dir);
|
|
14492
15286
|
} catch {
|
|
@@ -14503,10 +15297,10 @@ program.command("health").description("Run code health checks: dead code, circul
|
|
|
14503
15297
|
$ code-intel health --json
|
|
14504
15298
|
$ code-intel health --threshold 80
|
|
14505
15299
|
`).action(async (targetPath, opts) => {
|
|
14506
|
-
const workspaceRoot =
|
|
15300
|
+
const workspaceRoot = path35.resolve(targetPath);
|
|
14507
15301
|
const dbPath = getDbPath(workspaceRoot);
|
|
14508
15302
|
const meta = loadMetadata(workspaceRoot);
|
|
14509
|
-
if (!meta || !
|
|
15303
|
+
if (!meta || !fs34.existsSync(dbPath)) {
|
|
14510
15304
|
console.error(`
|
|
14511
15305
|
\u2717 ${workspaceRoot} is not indexed.`);
|
|
14512
15306
|
console.error(" Run `code-intel analyze` first to build the index.\n");
|
|
@@ -14591,7 +15385,7 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
14591
15385
|
$ code-intel query --list
|
|
14592
15386
|
$ code-intel query --delete auth-search
|
|
14593
15387
|
`).action(async (gqlArg, opts) => {
|
|
14594
|
-
const workspaceRoot =
|
|
15388
|
+
const workspaceRoot = path35.resolve(opts.path);
|
|
14595
15389
|
const { saveQuery: saveQuery2, loadQuery: loadQuery2, listQueries: listQueries2, deleteQuery: deleteQuery2 } = await Promise.resolve().then(() => (init_saved_queries(), saved_queries_exports));
|
|
14596
15390
|
if (opts.list) {
|
|
14597
15391
|
const queries = listQueries2(workspaceRoot);
|
|
@@ -14650,14 +15444,14 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
14650
15444
|
}
|
|
14651
15445
|
gqlInput = content;
|
|
14652
15446
|
} else if (opts.file) {
|
|
14653
|
-
const filePath =
|
|
14654
|
-
if (!
|
|
15447
|
+
const filePath = path35.resolve(opts.file);
|
|
15448
|
+
if (!fs34.existsSync(filePath)) {
|
|
14655
15449
|
console.error(`
|
|
14656
15450
|
\u2717 File not found: ${filePath}
|
|
14657
15451
|
`);
|
|
14658
15452
|
process.exit(1);
|
|
14659
15453
|
}
|
|
14660
|
-
gqlInput =
|
|
15454
|
+
gqlInput = fs34.readFileSync(filePath, "utf-8");
|
|
14661
15455
|
} else if (gqlArg) {
|
|
14662
15456
|
gqlInput = gqlArg;
|
|
14663
15457
|
} else {
|
|
@@ -14780,7 +15574,7 @@ program.command("pr-impact").description("Compute PR blast radius and risk score
|
|
|
14780
15574
|
$ code-intel pr-impact --base main --head HEAD --format sarif
|
|
14781
15575
|
$ code-intel pr-impact --base main --head HEAD --format json
|
|
14782
15576
|
`).action(async (opts) => {
|
|
14783
|
-
const repoPath =
|
|
15577
|
+
const repoPath = path35.resolve(opts.path ?? ".");
|
|
14784
15578
|
const { execSync: execSync3 } = await import('child_process');
|
|
14785
15579
|
let diff;
|
|
14786
15580
|
try {
|
|
@@ -14866,6 +15660,242 @@ program.command("pr-impact").description("Compute PR blast radius and risk score
|
|
|
14866
15660
|
process.exit(1);
|
|
14867
15661
|
}
|
|
14868
15662
|
});
|
|
15663
|
+
program.command("complexity").description("Show complexity hotspots ranked by cyclomatic complexity").argument("[path]", "Path to the repository (default: current directory)", ".").option("--top <n>", "Show top N results (default: 20)", "20").option("--threshold <n>", "Only show results above this cyclomatic threshold").option("--format <fmt>", "Output format: table or json (default: table)", "table").option("--scope <scope>", "Limit to a file path prefix").addHelpText("after", `
|
|
15664
|
+
Loads the knowledge graph and ranks functions/methods by cyclomatic complexity.
|
|
15665
|
+
|
|
15666
|
+
Examples:
|
|
15667
|
+
$ code-intel complexity
|
|
15668
|
+
$ code-intel complexity --top 10
|
|
15669
|
+
$ code-intel complexity --threshold 10
|
|
15670
|
+
$ code-intel complexity --format json
|
|
15671
|
+
`).action(async (targetPath, opts) => {
|
|
15672
|
+
const { graph } = await loadOrAnalyzeWorkspace(targetPath);
|
|
15673
|
+
const { computeComplexity: computeComplexity2 } = await Promise.resolve().then(() => (init_complexity(), complexity_exports));
|
|
15674
|
+
let results = computeComplexity2(graph, opts.scope);
|
|
15675
|
+
if (opts.threshold !== void 0) {
|
|
15676
|
+
const thresh = parseInt(opts.threshold, 10);
|
|
15677
|
+
if (!isNaN(thresh)) results = results.filter((r) => r.cyclomatic > thresh);
|
|
15678
|
+
}
|
|
15679
|
+
const top = parseInt(opts.top, 10);
|
|
15680
|
+
if (!isNaN(top) && top > 0) results = results.slice(0, top);
|
|
15681
|
+
if (opts.format === "json") {
|
|
15682
|
+
console.log(JSON.stringify(results, null, 2));
|
|
15683
|
+
return;
|
|
15684
|
+
}
|
|
15685
|
+
if (results.length === 0) {
|
|
15686
|
+
console.log("\n No complexity results found.\n");
|
|
15687
|
+
return;
|
|
15688
|
+
}
|
|
15689
|
+
console.log("\n \u25C8 Complexity Hotspots\n");
|
|
15690
|
+
const header = ` ${"Rank".padEnd(5)} ${"Symbol".padEnd(28)} ${"File".padEnd(40)} ${"Cyclo".padEnd(7)} ${"Cogn".padEnd(7)} Severity`;
|
|
15691
|
+
console.log(header);
|
|
15692
|
+
console.log(" " + "\u2500".repeat(header.length - 2));
|
|
15693
|
+
for (let i = 0; i < results.length; i++) {
|
|
15694
|
+
const r = results[i];
|
|
15695
|
+
const rank = String(i + 1).padEnd(5);
|
|
15696
|
+
const name = r.name.slice(0, 27).padEnd(28);
|
|
15697
|
+
const file = r.filePath.slice(0, 39).padEnd(40);
|
|
15698
|
+
const cyc = String(r.cyclomatic).padEnd(7);
|
|
15699
|
+
const cog = String(r.cognitive).padEnd(7);
|
|
15700
|
+
console.log(` ${rank} ${name} ${file} ${cyc} ${cog} ${r.severity}`);
|
|
15701
|
+
}
|
|
15702
|
+
console.log("");
|
|
15703
|
+
});
|
|
15704
|
+
program.command("coverage").description("Show untested exported symbols ranked by blast radius").argument("[path]", "Path to the repository (default: current directory)", ".").option("--threshold <pct>", "Exit code 1 when coverage is below this percentage").option("--format <fmt>", "Output format: table or json (default: table)", "table").option("--scope <scope>", "Limit to a file path prefix").addHelpText("after", `
|
|
15705
|
+
Loads the knowledge graph and identifies exported symbols with no test coverage.
|
|
15706
|
+
|
|
15707
|
+
Examples:
|
|
15708
|
+
$ code-intel coverage
|
|
15709
|
+
$ code-intel coverage --threshold 80
|
|
15710
|
+
$ code-intel coverage --format json
|
|
15711
|
+
`).action(async (targetPath, opts) => {
|
|
15712
|
+
const { graph } = await loadOrAnalyzeWorkspace(targetPath);
|
|
15713
|
+
const { computeCoverage: computeCoverage2 } = await Promise.resolve().then(() => (init_test_coverage(), test_coverage_exports));
|
|
15714
|
+
const summary = computeCoverage2(graph, opts.scope);
|
|
15715
|
+
if (opts.format === "json") {
|
|
15716
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
15717
|
+
} else {
|
|
15718
|
+
console.log("\n \u25C8 Test Coverage Gaps\n");
|
|
15719
|
+
console.log(` Coverage: ${summary.testedExported}/${summary.totalExported} exported symbols tested (${summary.coveragePct}%)
|
|
15720
|
+
`);
|
|
15721
|
+
if (summary.untestedByRisk.length === 0) {
|
|
15722
|
+
console.log(" All exported symbols have test coverage.\n");
|
|
15723
|
+
} else {
|
|
15724
|
+
console.log(" Untested exported symbols (by blast radius):\n");
|
|
15725
|
+
const header = ` ${"Symbol".padEnd(28)} ${"File".padEnd(40)} ${"Blast".padEnd(7)} Risk`;
|
|
15726
|
+
console.log(header);
|
|
15727
|
+
console.log(" " + "\u2500".repeat(header.length - 2));
|
|
15728
|
+
for (const r of summary.untestedByRisk) {
|
|
15729
|
+
const name = r.name.slice(0, 27).padEnd(28);
|
|
15730
|
+
const file = r.filePath.slice(0, 39).padEnd(40);
|
|
15731
|
+
const blast = String(r.blastRadius).padEnd(7);
|
|
15732
|
+
console.log(` ${name} ${file} ${blast} ${r.risk}`);
|
|
15733
|
+
}
|
|
15734
|
+
console.log("");
|
|
15735
|
+
}
|
|
15736
|
+
}
|
|
15737
|
+
if (opts.threshold !== void 0) {
|
|
15738
|
+
const thresh = parseInt(opts.threshold, 10);
|
|
15739
|
+
if (!isNaN(thresh) && summary.coveragePct < thresh) {
|
|
15740
|
+
process.exit(1);
|
|
15741
|
+
}
|
|
15742
|
+
}
|
|
15743
|
+
});
|
|
15744
|
+
program.command("secrets").description("Scan for hardcoded secrets in the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--format <fmt>", "Output format: table|json (default: table)", "table").option("--fail-on", "Exit 1 if any findings are found").option("--fix-hint", "Show hint to use process.env.VARIABLE_NAME instead").option("--include-tests", "Include test/spec/fixture files in scan").addHelpText("after", `
|
|
15745
|
+
Scans the indexed knowledge graph for hardcoded secrets (API keys, passwords,
|
|
15746
|
+
tokens, private keys, high-entropy strings).
|
|
15747
|
+
|
|
15748
|
+
Examples:
|
|
15749
|
+
$ code-intel secrets
|
|
15750
|
+
$ code-intel secrets --format json
|
|
15751
|
+
$ code-intel secrets --fail-on
|
|
15752
|
+
$ code-intel secrets --fix-hint
|
|
15753
|
+
`).action(async (targetPath, opts) => {
|
|
15754
|
+
const { graph } = await loadOrAnalyzeWorkspace(targetPath);
|
|
15755
|
+
const { SecretScanner: SecretScanner2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
15756
|
+
const scanner = new SecretScanner2();
|
|
15757
|
+
const findings = scanner.scan(graph, { includeTestFiles: opts.includeTests });
|
|
15758
|
+
if ((opts.format ?? "table") === "json") {
|
|
15759
|
+
console.log(JSON.stringify({ findings, total: findings.length }, null, 2));
|
|
15760
|
+
} else {
|
|
15761
|
+
if (findings.length === 0) {
|
|
15762
|
+
console.log("\n \u2705 No hardcoded secrets found.\n");
|
|
15763
|
+
} else {
|
|
15764
|
+
console.log(`
|
|
15765
|
+
\u26A0\uFE0F Found ${findings.length} potential secret(s):
|
|
15766
|
+
`);
|
|
15767
|
+
console.log(` ${"FILE".padEnd(50)} ${"LINE".padEnd(6)} ${"SYMBOL".padEnd(30)} PATTERN`);
|
|
15768
|
+
console.log(` ${"\u2500".repeat(100)}`);
|
|
15769
|
+
for (const f of findings) {
|
|
15770
|
+
const line = f.line !== void 0 ? String(f.line) : "?";
|
|
15771
|
+
console.log(` ${f.file.padEnd(50)} ${line.padEnd(6)} ${f.symbol.padEnd(30)} ${f.pattern}`);
|
|
15772
|
+
if (opts.fixHint) {
|
|
15773
|
+
console.log(` \u{1F4A1} Hint: Use process.env.${f.symbol.toUpperCase()} instead of a hardcoded value`);
|
|
15774
|
+
}
|
|
15775
|
+
}
|
|
15776
|
+
console.log("");
|
|
15777
|
+
}
|
|
15778
|
+
}
|
|
15779
|
+
if (opts.failOn && findings.length > 0) {
|
|
15780
|
+
process.exit(1);
|
|
15781
|
+
}
|
|
15782
|
+
});
|
|
15783
|
+
program.command("scan").description("Run security scans: secrets + OWASP vulnerability detection").argument("[path]", "Path to the repository (default: current directory)", ".").option("--type <types>", "Comma-separated detector types: secrets,sql,xss,ssrf,path,cmd").option("--severity <level>", "Minimum severity to report: high|medium|low (default: low)", "low").option("--format <fmt>", "Output format: table|json|sarif (default: table)", "table").option("--fail-on <level>", "Exit 1 if findings at or above this severity: high|medium").option("--exclude <pattern>", "Exclude files matching this pattern").addHelpText("after", `
|
|
15784
|
+
Runs both secret detection and OWASP vulnerability scanning against the
|
|
15785
|
+
indexed knowledge graph.
|
|
15786
|
+
|
|
15787
|
+
Examples:
|
|
15788
|
+
$ code-intel scan
|
|
15789
|
+
$ code-intel scan --type secrets,sql
|
|
15790
|
+
$ code-intel scan --severity high --format json
|
|
15791
|
+
$ code-intel scan --fail-on high
|
|
15792
|
+
`).action(async (targetPath, opts) => {
|
|
15793
|
+
const { graph } = await loadOrAnalyzeWorkspace(targetPath);
|
|
15794
|
+
const { SecretScanner: SecretScanner2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
15795
|
+
const { VulnerabilityDetector: VulnerabilityDetector2 } = await Promise.resolve().then(() => (init_vulnerability_detector(), vulnerability_detector_exports));
|
|
15796
|
+
const requestedTypes = opts.type ? opts.type.split(",").map((t) => t.trim().toLowerCase()) : null;
|
|
15797
|
+
const minSeverity = (opts.severity ?? "low").toLowerCase();
|
|
15798
|
+
const severityRank = { high: 3, medium: 2, low: 1 };
|
|
15799
|
+
const minRank = severityRank[minSeverity] ?? 1;
|
|
15800
|
+
const allFindings = [];
|
|
15801
|
+
if (!requestedTypes || requestedTypes.includes("secrets")) {
|
|
15802
|
+
const scanner = new SecretScanner2();
|
|
15803
|
+
const secrets = scanner.scan(graph);
|
|
15804
|
+
for (const s of secrets) {
|
|
15805
|
+
allFindings.push({
|
|
15806
|
+
kind: "secret",
|
|
15807
|
+
severity: s.severity,
|
|
15808
|
+
file: s.file,
|
|
15809
|
+
line: s.line,
|
|
15810
|
+
symbol: s.symbol,
|
|
15811
|
+
type: `SECRET:${s.pattern}`,
|
|
15812
|
+
description: `Hardcoded secret pattern: ${s.pattern}`
|
|
15813
|
+
});
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
const typeMap = {
|
|
15817
|
+
sql: "SQL_INJECTION",
|
|
15818
|
+
xss: "XSS",
|
|
15819
|
+
ssrf: "SSRF",
|
|
15820
|
+
path: "PATH_TRAVERSAL",
|
|
15821
|
+
cmd: "COMMAND_INJECTION"
|
|
15822
|
+
};
|
|
15823
|
+
const vulnTypes = requestedTypes ? requestedTypes.filter((t) => t in typeMap).map((t) => typeMap[t]) : ["SQL_INJECTION", "XSS", "SSRF", "PATH_TRAVERSAL", "COMMAND_INJECTION"];
|
|
15824
|
+
if (vulnTypes.length > 0 && (!requestedTypes || requestedTypes.some((t) => t in typeMap))) {
|
|
15825
|
+
const detector = new VulnerabilityDetector2();
|
|
15826
|
+
const vulns = detector.detect(graph, { types: vulnTypes });
|
|
15827
|
+
for (const v of vulns) {
|
|
15828
|
+
allFindings.push({
|
|
15829
|
+
kind: "vulnerability",
|
|
15830
|
+
severity: v.severity,
|
|
15831
|
+
file: v.file,
|
|
15832
|
+
line: v.line,
|
|
15833
|
+
symbol: v.symbol,
|
|
15834
|
+
type: v.type,
|
|
15835
|
+
description: v.description,
|
|
15836
|
+
cweId: v.cweId
|
|
15837
|
+
});
|
|
15838
|
+
}
|
|
15839
|
+
}
|
|
15840
|
+
const exclude = opts.exclude;
|
|
15841
|
+
const filtered = allFindings.filter((f) => {
|
|
15842
|
+
if (exclude && f.file.includes(exclude)) return false;
|
|
15843
|
+
const rank = severityRank[f.severity.toLowerCase()] ?? 1;
|
|
15844
|
+
return rank >= minRank;
|
|
15845
|
+
});
|
|
15846
|
+
const format = (opts.format ?? "table").toLowerCase();
|
|
15847
|
+
if (format === "json") {
|
|
15848
|
+
console.log(JSON.stringify({ findings: filtered, total: filtered.length }, null, 2));
|
|
15849
|
+
} else if (format === "sarif") {
|
|
15850
|
+
const sarif = {
|
|
15851
|
+
version: "2.1.0",
|
|
15852
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
15853
|
+
runs: [{
|
|
15854
|
+
tool: { driver: { name: "code-intel", version: "0.8.0", rules: [] } },
|
|
15855
|
+
results: filtered.map((f) => ({
|
|
15856
|
+
ruleId: f.type,
|
|
15857
|
+
message: { text: f.description },
|
|
15858
|
+
locations: [{
|
|
15859
|
+
physicalLocation: {
|
|
15860
|
+
artifactLocation: { uri: f.file },
|
|
15861
|
+
region: { startLine: f.line ?? 1 }
|
|
15862
|
+
}
|
|
15863
|
+
}],
|
|
15864
|
+
level: f.severity === "HIGH" ? "error" : f.severity === "MEDIUM" ? "warning" : "note"
|
|
15865
|
+
}))
|
|
15866
|
+
}]
|
|
15867
|
+
};
|
|
15868
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
15869
|
+
} else {
|
|
15870
|
+
if (filtered.length === 0) {
|
|
15871
|
+
console.log("\n \u2705 No security findings.\n");
|
|
15872
|
+
} else {
|
|
15873
|
+
const highCount = filtered.filter((f) => f.severity === "HIGH").length;
|
|
15874
|
+
const medCount = filtered.filter((f) => f.severity === "MEDIUM").length;
|
|
15875
|
+
const lowCount = filtered.filter((f) => f.severity === "LOW").length;
|
|
15876
|
+
console.log(`
|
|
15877
|
+
\u25C8 Security Scan \u2014 ${path35.resolve(targetPath)}
|
|
15878
|
+
`);
|
|
15879
|
+
console.log(` HIGH: ${highCount} MEDIUM: ${medCount} LOW: ${lowCount}
|
|
15880
|
+
`);
|
|
15881
|
+
for (const f of filtered) {
|
|
15882
|
+
const icon = f.severity === "HIGH" ? "\u{1F534}" : f.severity === "MEDIUM" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
15883
|
+
const cwe = f.cweId ? ` [${f.cweId}]` : "";
|
|
15884
|
+
console.log(` ${icon} ${f.severity.padEnd(8)} ${f.type}${cwe}`);
|
|
15885
|
+
const line = f.line !== void 0 ? `:${f.line}` : "";
|
|
15886
|
+
console.log(` ${f.file}${line} ${f.symbol}`);
|
|
15887
|
+
console.log(` ${f.description}`);
|
|
15888
|
+
console.log("");
|
|
15889
|
+
}
|
|
15890
|
+
}
|
|
15891
|
+
}
|
|
15892
|
+
const failOnLevel = (opts.failOn ?? "").toLowerCase();
|
|
15893
|
+
if (failOnLevel === "high" && filtered.some((f) => f.severity === "HIGH")) {
|
|
15894
|
+
process.exit(1);
|
|
15895
|
+
} else if (failOnLevel === "medium" && filtered.some((f) => f.severity === "HIGH" || f.severity === "MEDIUM")) {
|
|
15896
|
+
process.exit(1);
|
|
15897
|
+
}
|
|
15898
|
+
});
|
|
14869
15899
|
program.parse();
|
|
14870
15900
|
//# sourceMappingURL=main.js.map
|
|
14871
15901
|
//# sourceMappingURL=main.js.map
|