@vohongtho.infotech/code-intel 0.7.0 → 0.9.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 +14 -3
- package/dist/cli/main.js +4741 -2230
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +866 -192
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-yDTUrrnL.js → es-CkND97V_.js} +1 -1
- package/dist/web/assets/{index-B4bH2ZP8.js → index-DExLzJ89.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import path31 from 'path';
|
|
4
|
+
import fs24, { existsSync } from 'fs';
|
|
5
5
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
6
6
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
7
7
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
@@ -11,7 +11,8 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
|
|
|
11
11
|
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
|
|
12
12
|
import winston from 'winston';
|
|
13
13
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
14
|
-
import
|
|
14
|
+
import os13 from 'os';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
15
16
|
import Database3 from 'better-sqlite3';
|
|
16
17
|
import bcrypt from 'bcrypt';
|
|
17
18
|
import crypto5 from 'crypto';
|
|
@@ -24,7 +25,6 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
24
25
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
25
26
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
26
27
|
import { Database, Connection } from '@ladybugdb/core';
|
|
27
|
-
import { execSync } from 'child_process';
|
|
28
28
|
import express from 'express';
|
|
29
29
|
import compression from 'compression';
|
|
30
30
|
import cors from 'cors';
|
|
@@ -126,11 +126,11 @@ var init_shared = __esm({
|
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
function findBundledWasmDir() {
|
|
129
|
-
const fileDir =
|
|
129
|
+
const fileDir = path31.dirname(fileURLToPath(import.meta.url));
|
|
130
130
|
const candidates = [
|
|
131
|
-
|
|
131
|
+
path31.join(fileDir, "wasm"),
|
|
132
132
|
// dist/index.js → dist/wasm/
|
|
133
|
-
|
|
133
|
+
path31.join(fileDir, "../wasm")
|
|
134
134
|
// dist/cli/main.js → dist/wasm/
|
|
135
135
|
];
|
|
136
136
|
for (const candidate of candidates) {
|
|
@@ -171,7 +171,7 @@ function wasmPath(lang) {
|
|
|
171
171
|
}
|
|
172
172
|
const bundled = BUNDLED_WASM_MAP[lang];
|
|
173
173
|
if (bundled) {
|
|
174
|
-
const bundledPath =
|
|
174
|
+
const bundledPath = path31.join(_bundledWasmDir, bundled);
|
|
175
175
|
if (existsSync(bundledPath)) return bundledPath;
|
|
176
176
|
}
|
|
177
177
|
return null;
|
|
@@ -184,14 +184,14 @@ async function initParser() {
|
|
|
184
184
|
}
|
|
185
185
|
async function getLanguage(lang) {
|
|
186
186
|
if (languageCache.has(lang)) return languageCache.get(lang);
|
|
187
|
-
const
|
|
188
|
-
if (!
|
|
187
|
+
const path32 = wasmPath(lang);
|
|
188
|
+
if (!path32) {
|
|
189
189
|
languageCache.set(lang, null);
|
|
190
190
|
return null;
|
|
191
191
|
}
|
|
192
192
|
try {
|
|
193
193
|
await initParser();
|
|
194
|
-
const language = await Language.load(
|
|
194
|
+
const language = await Language.load(path32);
|
|
195
195
|
languageCache.set(lang, language);
|
|
196
196
|
return language;
|
|
197
197
|
} catch {
|
|
@@ -1162,7 +1162,7 @@ var init_logger = __esm({
|
|
|
1162
1162
|
};
|
|
1163
1163
|
}
|
|
1164
1164
|
/** Global log directory: ~/.code-intel/logs */
|
|
1165
|
-
static LOG_DIR =
|
|
1165
|
+
static LOG_DIR = path31.join(os13.homedir(), ".code-intel", "logs");
|
|
1166
1166
|
static getLogger() {
|
|
1167
1167
|
if (!_Logger.instance) {
|
|
1168
1168
|
const isProduction = process.env.NODE_ENV === "production";
|
|
@@ -1171,12 +1171,12 @@ var init_logger = __esm({
|
|
|
1171
1171
|
transports.push(new winston.transports.Console());
|
|
1172
1172
|
if (!isProduction) {
|
|
1173
1173
|
try {
|
|
1174
|
-
if (!
|
|
1175
|
-
|
|
1174
|
+
if (!fs24.existsSync(_Logger.LOG_DIR)) {
|
|
1175
|
+
fs24.mkdirSync(_Logger.LOG_DIR, { recursive: true });
|
|
1176
1176
|
}
|
|
1177
1177
|
transports.push(
|
|
1178
1178
|
new DailyRotateFile({
|
|
1179
|
-
filename:
|
|
1179
|
+
filename: path31.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
|
|
1180
1180
|
datePattern: "YYYY-MM-DD",
|
|
1181
1181
|
maxSize: "20m",
|
|
1182
1182
|
maxFiles: "14d"
|
|
@@ -1961,7 +1961,7 @@ var init_parse_phase = __esm({
|
|
|
1961
1961
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
1962
1962
|
await Promise.all(batch.map(async (filePath) => {
|
|
1963
1963
|
try {
|
|
1964
|
-
const source = await
|
|
1964
|
+
const source = await fs24.promises.readFile(filePath, "utf-8");
|
|
1965
1965
|
context2.fileCache.set(filePath, source);
|
|
1966
1966
|
} catch {
|
|
1967
1967
|
}
|
|
@@ -1974,14 +1974,14 @@ var init_parse_phase = __esm({
|
|
|
1974
1974
|
const lang = detectLanguage(filePath);
|
|
1975
1975
|
if (!lang) {
|
|
1976
1976
|
if (context2.verbose) {
|
|
1977
|
-
const relativePath2 =
|
|
1977
|
+
const relativePath2 = path31.relative(context2.workspaceRoot, filePath);
|
|
1978
1978
|
logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
|
|
1979
1979
|
}
|
|
1980
1980
|
continue;
|
|
1981
1981
|
}
|
|
1982
1982
|
const source = context2.fileCache.get(filePath);
|
|
1983
1983
|
if (!source) continue;
|
|
1984
|
-
const relativePath =
|
|
1984
|
+
const relativePath = path31.relative(context2.workspaceRoot, filePath);
|
|
1985
1985
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
1986
1986
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
1987
1987
|
if (fileNode) {
|
|
@@ -2221,11 +2221,11 @@ var init_resolve_phase = __esm({
|
|
|
2221
2221
|
let heritageEdges = 0;
|
|
2222
2222
|
const fileIndex = /* @__PURE__ */ new Map();
|
|
2223
2223
|
for (const fp of filePaths) {
|
|
2224
|
-
const rel =
|
|
2224
|
+
const rel = path31.relative(workspaceRoot, fp);
|
|
2225
2225
|
fileIndex.set(rel, fp);
|
|
2226
2226
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
2227
2227
|
if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
|
|
2228
|
-
const base =
|
|
2228
|
+
const base = path31.basename(rel, path31.extname(rel));
|
|
2229
2229
|
if (!fileIndex.has(base)) fileIndex.set(base, fp);
|
|
2230
2230
|
}
|
|
2231
2231
|
const symbolIndex = /* @__PURE__ */ new Map();
|
|
@@ -2256,7 +2256,7 @@ var init_resolve_phase = __esm({
|
|
|
2256
2256
|
for (const filePath of filePaths) {
|
|
2257
2257
|
const lang = detectLanguage(filePath);
|
|
2258
2258
|
if (!lang) continue;
|
|
2259
|
-
const relativePath =
|
|
2259
|
+
const relativePath = path31.relative(workspaceRoot, filePath);
|
|
2260
2260
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
2261
2261
|
const source = fileCache.get(filePath);
|
|
2262
2262
|
if (!source) continue;
|
|
@@ -2269,13 +2269,13 @@ var init_resolve_phase = __esm({
|
|
|
2269
2269
|
let resolvedRelPath = null;
|
|
2270
2270
|
if (cleaned.startsWith(".")) {
|
|
2271
2271
|
const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
|
|
2272
|
-
const fromDir =
|
|
2272
|
+
const fromDir = path31.dirname(relativePath);
|
|
2273
2273
|
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
|
|
2274
|
-
const candidate =
|
|
2275
|
-
const normalized =
|
|
2274
|
+
const candidate = path31.join(fromDir, cleanedNoJs + ext);
|
|
2275
|
+
const normalized = path31.normalize(candidate);
|
|
2276
2276
|
if (fileIndex.has(normalized)) {
|
|
2277
2277
|
const absPath = fileIndex.get(normalized);
|
|
2278
|
-
resolvedRelPath =
|
|
2278
|
+
resolvedRelPath = path31.relative(workspaceRoot, absPath);
|
|
2279
2279
|
break;
|
|
2280
2280
|
}
|
|
2281
2281
|
}
|
|
@@ -2589,27 +2589,27 @@ __export(group_registry_exports, {
|
|
|
2589
2589
|
saveSyncResult: () => saveSyncResult
|
|
2590
2590
|
});
|
|
2591
2591
|
function groupFile(name) {
|
|
2592
|
-
return
|
|
2592
|
+
return path31.join(GROUPS_DIR, `${name}.json`);
|
|
2593
2593
|
}
|
|
2594
2594
|
function loadGroup(name) {
|
|
2595
2595
|
try {
|
|
2596
|
-
return JSON.parse(
|
|
2596
|
+
return JSON.parse(fs24.readFileSync(groupFile(name), "utf-8"));
|
|
2597
2597
|
} catch {
|
|
2598
2598
|
return null;
|
|
2599
2599
|
}
|
|
2600
2600
|
}
|
|
2601
2601
|
function saveGroup(group) {
|
|
2602
|
-
|
|
2603
|
-
|
|
2602
|
+
fs24.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2603
|
+
fs24.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
2604
2604
|
}
|
|
2605
2605
|
function listGroups() {
|
|
2606
2606
|
const groups = [];
|
|
2607
2607
|
try {
|
|
2608
|
-
for (const file of
|
|
2608
|
+
for (const file of fs24.readdirSync(GROUPS_DIR)) {
|
|
2609
2609
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
2610
2610
|
try {
|
|
2611
2611
|
const g = JSON.parse(
|
|
2612
|
-
|
|
2612
|
+
fs24.readFileSync(path31.join(GROUPS_DIR, file), "utf-8")
|
|
2613
2613
|
);
|
|
2614
2614
|
groups.push(g);
|
|
2615
2615
|
} catch {
|
|
@@ -2621,16 +2621,16 @@ function listGroups() {
|
|
|
2621
2621
|
}
|
|
2622
2622
|
function deleteGroup(name) {
|
|
2623
2623
|
try {
|
|
2624
|
-
|
|
2624
|
+
fs24.unlinkSync(groupFile(name));
|
|
2625
2625
|
} catch {
|
|
2626
2626
|
}
|
|
2627
2627
|
try {
|
|
2628
|
-
|
|
2628
|
+
fs24.unlinkSync(path31.join(GROUPS_DIR, `${name}.sync.json`));
|
|
2629
2629
|
} catch {
|
|
2630
2630
|
}
|
|
2631
2631
|
}
|
|
2632
2632
|
function groupExists(name) {
|
|
2633
|
-
return
|
|
2633
|
+
return fs24.existsSync(groupFile(name));
|
|
2634
2634
|
}
|
|
2635
2635
|
function addMember(groupName, member) {
|
|
2636
2636
|
const group = loadGroup(groupName);
|
|
@@ -2656,16 +2656,16 @@ function removeMember(groupName, groupPath) {
|
|
|
2656
2656
|
return group;
|
|
2657
2657
|
}
|
|
2658
2658
|
function saveSyncResult(result) {
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2659
|
+
fs24.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2660
|
+
fs24.writeFileSync(
|
|
2661
|
+
path31.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
2662
2662
|
JSON.stringify(result, null, 2) + "\n"
|
|
2663
2663
|
);
|
|
2664
2664
|
}
|
|
2665
2665
|
function loadSyncResult(groupName) {
|
|
2666
2666
|
try {
|
|
2667
2667
|
return JSON.parse(
|
|
2668
|
-
|
|
2668
|
+
fs24.readFileSync(path31.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
2669
2669
|
);
|
|
2670
2670
|
} catch {
|
|
2671
2671
|
return null;
|
|
@@ -2674,7 +2674,7 @@ function loadSyncResult(groupName) {
|
|
|
2674
2674
|
var GROUPS_DIR;
|
|
2675
2675
|
var init_group_registry = __esm({
|
|
2676
2676
|
"src/multi-repo/group-registry.ts"() {
|
|
2677
|
-
GROUPS_DIR =
|
|
2677
|
+
GROUPS_DIR = path31.join(os13.homedir(), ".code-intel", "groups");
|
|
2678
2678
|
}
|
|
2679
2679
|
});
|
|
2680
2680
|
|
|
@@ -2696,14 +2696,14 @@ function detectDeadCode(graph) {
|
|
|
2696
2696
|
if (meta?.deprecated === true) continue;
|
|
2697
2697
|
if (ENTRY_POINT_NAME_RE.test(node.name)) continue;
|
|
2698
2698
|
if (entryPointIds.has(node.id)) continue;
|
|
2699
|
-
let
|
|
2699
|
+
let hasCallers2 = false;
|
|
2700
2700
|
for (const edge of graph.findEdgesTo(node.id)) {
|
|
2701
2701
|
if (edge.kind === "calls") {
|
|
2702
|
-
|
|
2702
|
+
hasCallers2 = true;
|
|
2703
2703
|
break;
|
|
2704
2704
|
}
|
|
2705
2705
|
}
|
|
2706
|
-
if (
|
|
2706
|
+
if (hasCallers2) continue;
|
|
2707
2707
|
let hasImporters = false;
|
|
2708
2708
|
for (const edge of graph.findEdgesTo(node.id)) {
|
|
2709
2709
|
if (edge.kind === "imports") {
|
|
@@ -3702,11 +3702,554 @@ var init_gql_executor = __esm({
|
|
|
3702
3702
|
}
|
|
3703
3703
|
});
|
|
3704
3704
|
|
|
3705
|
-
// src/
|
|
3705
|
+
// src/analysis/deprecated-detector.ts
|
|
3706
|
+
var deprecated_detector_exports = {};
|
|
3707
|
+
__export(deprecated_detector_exports, {
|
|
3708
|
+
DeprecatedDetector: () => DeprecatedDetector
|
|
3709
|
+
});
|
|
3710
|
+
var BUILTIN_DEPRECATED, DeprecatedDetector;
|
|
3711
|
+
var init_deprecated_detector = __esm({
|
|
3712
|
+
"src/analysis/deprecated-detector.ts"() {
|
|
3713
|
+
BUILTIN_DEPRECATED = [
|
|
3714
|
+
{ pattern: "url.parse", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
|
|
3715
|
+
{ pattern: "url.resolve", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
|
|
3716
|
+
{ pattern: "url.format", message: "deprecated in Node.js v11.0.0 \u2014 use the WHATWG URL API instead" },
|
|
3717
|
+
{ pattern: "fs.exists", message: "deprecated \u2014 use fs.access instead" },
|
|
3718
|
+
{ pattern: "crypto.createCipher", message: "deprecated \u2014 use crypto.createCipheriv instead" },
|
|
3719
|
+
{ pattern: "crypto.createDecipher", message: "deprecated \u2014 use crypto.createDecipheriv instead" },
|
|
3720
|
+
{ pattern: "new Buffer()", message: "deprecated \u2014 use Buffer.from() instead" },
|
|
3721
|
+
{ pattern: "domain.create", message: "deprecated \u2014 the domain module is discouraged" },
|
|
3722
|
+
{ pattern: "process.binding", message: "deprecated internal API" }
|
|
3723
|
+
];
|
|
3724
|
+
DeprecatedDetector = class {
|
|
3725
|
+
tagDeprecated(graph) {
|
|
3726
|
+
for (const node of graph.allNodes()) {
|
|
3727
|
+
if (!node.metadata) node.metadata = {};
|
|
3728
|
+
if (node.metadata["deprecated"] === true) continue;
|
|
3729
|
+
let message;
|
|
3730
|
+
const jsdoc = node.metadata["jsdoc"];
|
|
3731
|
+
const comment = node.metadata["comment"];
|
|
3732
|
+
if (jsdoc?.includes("@deprecated") || comment?.includes("@deprecated")) {
|
|
3733
|
+
const src = jsdoc ?? comment ?? "";
|
|
3734
|
+
const match = src.match(/@deprecated\s+(.*)/);
|
|
3735
|
+
message = match?.[1]?.trim() || "deprecated";
|
|
3736
|
+
}
|
|
3737
|
+
if (!message && node.metadata["deprecated"] === true) {
|
|
3738
|
+
message = node.metadata["deprecationMessage"] ?? "deprecated";
|
|
3739
|
+
}
|
|
3740
|
+
if (!message) {
|
|
3741
|
+
const annotations = node.metadata["annotations"];
|
|
3742
|
+
if (Array.isArray(annotations) && annotations.includes("Deprecated")) {
|
|
3743
|
+
message = "marked @Deprecated";
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
if (!message) {
|
|
3747
|
+
const attributes = node.metadata["attributes"];
|
|
3748
|
+
if (Array.isArray(attributes) && attributes.includes("deprecated")) {
|
|
3749
|
+
message = "marked #[deprecated]";
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
if (!message) {
|
|
3753
|
+
for (const entry of BUILTIN_DEPRECATED) {
|
|
3754
|
+
if (node.name === entry.pattern || node.name.includes(entry.pattern)) {
|
|
3755
|
+
message = entry.message;
|
|
3756
|
+
break;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
if (message) {
|
|
3761
|
+
node.metadata["deprecated"] = true;
|
|
3762
|
+
node.metadata["deprecationMessage"] = message;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
detect(graph, scope) {
|
|
3767
|
+
const findings = [];
|
|
3768
|
+
for (const node of graph.allNodes()) {
|
|
3769
|
+
if (!node.metadata?.["deprecated"]) continue;
|
|
3770
|
+
const callers = [];
|
|
3771
|
+
for (const edge of graph.findEdgesTo(node.id)) {
|
|
3772
|
+
if (edge.kind !== "calls" && edge.kind !== "deprecated_use") continue;
|
|
3773
|
+
const caller = graph.getNode(edge.source);
|
|
3774
|
+
if (!caller) continue;
|
|
3775
|
+
if (scope && !caller.filePath.includes(scope)) continue;
|
|
3776
|
+
callers.push({ name: caller.name, filePath: caller.filePath });
|
|
3777
|
+
const edgeId = `dep_use_${edge.source}_${node.id}`;
|
|
3778
|
+
if (!graph.getEdge(edgeId)) {
|
|
3779
|
+
graph.addEdge({ id: edgeId, source: edge.source, target: node.id, kind: "deprecated_use" });
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
findings.push({
|
|
3783
|
+
symbol: node.name,
|
|
3784
|
+
filePath: node.filePath,
|
|
3785
|
+
deprecationMessage: node.metadata?.["deprecationMessage"] ?? "deprecated",
|
|
3786
|
+
callers
|
|
3787
|
+
});
|
|
3788
|
+
}
|
|
3789
|
+
return findings;
|
|
3790
|
+
}
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
});
|
|
3794
|
+
|
|
3795
|
+
// src/analysis/complexity.ts
|
|
3796
|
+
var complexity_exports = {};
|
|
3797
|
+
__export(complexity_exports, {
|
|
3798
|
+
computeComplexity: () => computeComplexity
|
|
3799
|
+
});
|
|
3800
|
+
function getSeverity(cyclomatic) {
|
|
3801
|
+
if (cyclomatic <= 5) return "LOW";
|
|
3802
|
+
if (cyclomatic <= 10) return "MEDIUM";
|
|
3803
|
+
if (cyclomatic <= 20) return "HIGH";
|
|
3804
|
+
return "CRITICAL";
|
|
3805
|
+
}
|
|
3806
|
+
function computeComplexity(graph, scope) {
|
|
3807
|
+
const results = [];
|
|
3808
|
+
for (const node of graph.allNodes()) {
|
|
3809
|
+
if (node.kind !== "function" && node.kind !== "method") continue;
|
|
3810
|
+
if (scope && !node.filePath.startsWith(scope)) continue;
|
|
3811
|
+
let outgoingCalls = 0;
|
|
3812
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
3813
|
+
if (edge.kind === "calls") outgoingCalls++;
|
|
3814
|
+
}
|
|
3815
|
+
let cyclomatic;
|
|
3816
|
+
const meta = node.metadata;
|
|
3817
|
+
const metaComplexity = meta?.complexity;
|
|
3818
|
+
if (typeof metaComplexity?.cyclomatic === "number") {
|
|
3819
|
+
cyclomatic = metaComplexity.cyclomatic;
|
|
3820
|
+
} else {
|
|
3821
|
+
cyclomatic = 1 + Math.floor(outgoingCalls / 2);
|
|
3822
|
+
}
|
|
3823
|
+
cyclomatic = Math.min(cyclomatic, 50);
|
|
3824
|
+
let cognitive;
|
|
3825
|
+
if (typeof metaComplexity?.cognitive === "number") {
|
|
3826
|
+
cognitive = metaComplexity.cognitive;
|
|
3827
|
+
} else {
|
|
3828
|
+
cognitive = Math.ceil(cyclomatic * 1.3);
|
|
3829
|
+
}
|
|
3830
|
+
results.push({
|
|
3831
|
+
nodeId: node.id,
|
|
3832
|
+
name: node.name,
|
|
3833
|
+
filePath: node.filePath,
|
|
3834
|
+
cyclomatic,
|
|
3835
|
+
cognitive,
|
|
3836
|
+
severity: getSeverity(cyclomatic)
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
return results.sort((a, b) => b.cyclomatic - a.cyclomatic);
|
|
3840
|
+
}
|
|
3841
|
+
var init_complexity = __esm({
|
|
3842
|
+
"src/analysis/complexity.ts"() {
|
|
3843
|
+
}
|
|
3844
|
+
});
|
|
3845
|
+
|
|
3846
|
+
// src/analysis/test-coverage.ts
|
|
3847
|
+
var test_coverage_exports = {};
|
|
3848
|
+
__export(test_coverage_exports, {
|
|
3849
|
+
computeCoverage: () => computeCoverage
|
|
3850
|
+
});
|
|
3851
|
+
function isTestFile(filePath) {
|
|
3852
|
+
if (filePath.includes(".test.") || filePath.includes(".spec.")) return true;
|
|
3853
|
+
if (filePath.includes("_test.") || filePath.endsWith("_test.go")) return true;
|
|
3854
|
+
if (filePath.includes("__tests__")) return true;
|
|
3855
|
+
const base = path31.basename(filePath);
|
|
3856
|
+
if (base.startsWith("Test") && filePath.endsWith(".java")) return true;
|
|
3857
|
+
return false;
|
|
3858
|
+
}
|
|
3859
|
+
function computeBlastRadius(graph, nodeId) {
|
|
3860
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3861
|
+
const queue = [{ id: nodeId, depth: 0 }];
|
|
3862
|
+
while (queue.length > 0) {
|
|
3863
|
+
const { id, depth } = queue.shift();
|
|
3864
|
+
if (visited.has(id)) continue;
|
|
3865
|
+
visited.add(id);
|
|
3866
|
+
if (depth >= 3) continue;
|
|
3867
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
3868
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
3869
|
+
if (!visited.has(edge.source)) {
|
|
3870
|
+
queue.push({ id: edge.source, depth: depth + 1 });
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
return Math.max(0, visited.size - 1);
|
|
3876
|
+
}
|
|
3877
|
+
function getRisk(blastRadius) {
|
|
3878
|
+
if (blastRadius > 20) return "HIGH";
|
|
3879
|
+
if (blastRadius >= 5) return "MEDIUM";
|
|
3880
|
+
return "LOW";
|
|
3881
|
+
}
|
|
3882
|
+
function computeCoverage(graph, scope) {
|
|
3883
|
+
const testFilePaths = /* @__PURE__ */ new Set();
|
|
3884
|
+
for (const node of graph.allNodes()) {
|
|
3885
|
+
if (isTestFile(node.filePath)) testFilePaths.add(node.filePath);
|
|
3886
|
+
}
|
|
3887
|
+
const nodesImportedByTests = /* @__PURE__ */ new Set();
|
|
3888
|
+
for (const edge of graph.findEdgesByKind("imports")) {
|
|
3889
|
+
const sourceNode = graph.getNode(edge.source);
|
|
3890
|
+
if (sourceNode && isTestFile(sourceNode.filePath)) {
|
|
3891
|
+
nodesImportedByTests.add(edge.target);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
const nodesWithTestedBy = /* @__PURE__ */ new Set();
|
|
3895
|
+
for (const edge of graph.findEdgesByKind("tested_by")) {
|
|
3896
|
+
nodesWithTestedBy.add(edge.source);
|
|
3897
|
+
}
|
|
3898
|
+
const baseNameToTestFiles = /* @__PURE__ */ new Map();
|
|
3899
|
+
for (const testPath of testFilePaths) {
|
|
3900
|
+
const base = path31.basename(testPath);
|
|
3901
|
+
const stripped = base.replace(/\.test\.[^.]+$/, "").replace(/\.spec\.[^.]+$/, "").replace(/_test\.[^.]+$/, "").replace(/_test$/, "");
|
|
3902
|
+
const existing = baseNameToTestFiles.get(stripped) ?? [];
|
|
3903
|
+
existing.push(testPath);
|
|
3904
|
+
baseNameToTestFiles.set(stripped, existing);
|
|
3905
|
+
}
|
|
3906
|
+
const exportedKinds = /* @__PURE__ */ new Set(["function", "method", "class"]);
|
|
3907
|
+
const results = [];
|
|
3908
|
+
for (const node of graph.allNodes()) {
|
|
3909
|
+
if (!exportedKinds.has(node.kind)) continue;
|
|
3910
|
+
if (node.exported !== true) continue;
|
|
3911
|
+
if (scope && !node.filePath.startsWith(scope)) continue;
|
|
3912
|
+
const testFiles = [];
|
|
3913
|
+
if (nodesWithTestedBy.has(node.id)) {
|
|
3914
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
3915
|
+
if (edge.kind === "tested_by") {
|
|
3916
|
+
const testNode = graph.getNode(edge.target);
|
|
3917
|
+
if (testNode && !testFiles.includes(testNode.filePath)) {
|
|
3918
|
+
testFiles.push(testNode.filePath);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
if (nodesImportedByTests.has(node.id)) {
|
|
3924
|
+
for (const edge of graph.findEdgesTo(node.id)) {
|
|
3925
|
+
if (edge.kind === "imports") {
|
|
3926
|
+
const sourceNode = graph.getNode(edge.source);
|
|
3927
|
+
if (sourceNode && isTestFile(sourceNode.filePath) && !testFiles.includes(sourceNode.filePath)) {
|
|
3928
|
+
testFiles.push(sourceNode.filePath);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
const nodeBase = path31.basename(node.filePath).replace(/\.[^.]+$/, "");
|
|
3934
|
+
const matchingTestFiles = baseNameToTestFiles.get(nodeBase) ?? [];
|
|
3935
|
+
for (const tf of matchingTestFiles) {
|
|
3936
|
+
if (!testFiles.includes(tf)) testFiles.push(tf);
|
|
3937
|
+
}
|
|
3938
|
+
const tested = testFiles.length > 0;
|
|
3939
|
+
const blastRadius = computeBlastRadius(graph, node.id);
|
|
3940
|
+
const risk = getRisk(blastRadius);
|
|
3941
|
+
results.push({
|
|
3942
|
+
nodeId: node.id,
|
|
3943
|
+
name: node.name,
|
|
3944
|
+
filePath: node.filePath,
|
|
3945
|
+
exported: true,
|
|
3946
|
+
tested,
|
|
3947
|
+
testFiles,
|
|
3948
|
+
blastRadius,
|
|
3949
|
+
risk
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
const totalExported = results.length;
|
|
3953
|
+
const testedExported = results.filter((r) => r.tested).length;
|
|
3954
|
+
const coveragePct = totalExported === 0 ? 100 : Math.round(testedExported / totalExported * 100);
|
|
3955
|
+
const untestedByRisk = results.filter((r) => !r.tested).sort((a, b) => b.blastRadius - a.blastRadius);
|
|
3956
|
+
return { totalExported, testedExported, coveragePct, untestedByRisk };
|
|
3957
|
+
}
|
|
3958
|
+
var init_test_coverage = __esm({
|
|
3959
|
+
"src/analysis/test-coverage.ts"() {
|
|
3960
|
+
}
|
|
3961
|
+
});
|
|
3962
|
+
|
|
3963
|
+
// src/security/secret-scanner.ts
|
|
3964
|
+
var secret_scanner_exports = {};
|
|
3965
|
+
__export(secret_scanner_exports, {
|
|
3966
|
+
SecretScanner: () => SecretScanner
|
|
3967
|
+
});
|
|
3968
|
+
function shannonEntropy(s) {
|
|
3969
|
+
const freq = /* @__PURE__ */ new Map();
|
|
3970
|
+
for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
|
|
3971
|
+
let entropy = 0;
|
|
3972
|
+
for (const count of freq.values()) {
|
|
3973
|
+
const p = count / s.length;
|
|
3974
|
+
entropy -= p * Math.log2(p);
|
|
3975
|
+
}
|
|
3976
|
+
return entropy;
|
|
3977
|
+
}
|
|
3978
|
+
function isTestFile2(filePath) {
|
|
3979
|
+
return filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("fixtures/") || filePath.includes("mocks/");
|
|
3980
|
+
}
|
|
3981
|
+
var ENV_VAR_RE, SENSITIVE_NAME_RE, VALUE_PATTERNS, SecretScanner;
|
|
3982
|
+
var init_secret_scanner = __esm({
|
|
3983
|
+
"src/security/secret-scanner.ts"() {
|
|
3984
|
+
ENV_VAR_RE = /^process\.env\./;
|
|
3985
|
+
SENSITIVE_NAME_RE = /_SECRET$|_PASSWORD$|_TOKEN$|_KEY$|_API_KEY$/i;
|
|
3986
|
+
VALUE_PATTERNS = [
|
|
3987
|
+
[/sk-[A-Za-z0-9]{6,}/, "openai-api-key", "HIGH"],
|
|
3988
|
+
[/pk_live_[A-Za-z0-9]{20,}/, "stripe-key", "HIGH"],
|
|
3989
|
+
[/AKIA[0-9A-Z]{16}|aws.access.key/i, "aws-access-key", "HIGH"],
|
|
3990
|
+
[/xoxb-[0-9]{11}-[0-9]{11}-[A-Za-z0-9]{24}/, "slack-token", "HIGH"],
|
|
3991
|
+
[/postgres:\/\/[^@]+:[^@]+@/, "db-url-with-credentials", "HIGH"],
|
|
3992
|
+
[/mysql:\/\/[^@]+:[^@]+@/, "db-url-with-credentials", "HIGH"],
|
|
3993
|
+
[/-----BEGIN RSA PRIVATE KEY-----/, "rsa-private-key", "HIGH"]
|
|
3994
|
+
];
|
|
3995
|
+
SecretScanner = class {
|
|
3996
|
+
scan(graph, options) {
|
|
3997
|
+
const findings = [];
|
|
3998
|
+
const includeTests = options?.includeTestFiles ?? false;
|
|
3999
|
+
const scope = options?.scope;
|
|
4000
|
+
const ignorePatterns = [...options?.ignorePatterns ?? []];
|
|
4001
|
+
if (options?.workspaceRoot) {
|
|
4002
|
+
try {
|
|
4003
|
+
const raw = fs24.readFileSync(path31.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
|
|
4004
|
+
for (const line of raw.split("\n")) {
|
|
4005
|
+
const trimmed = line.trim();
|
|
4006
|
+
if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
|
|
4007
|
+
}
|
|
4008
|
+
} catch {
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
for (const node of graph.allNodes()) {
|
|
4012
|
+
const filePath = node.filePath;
|
|
4013
|
+
if (scope && !filePath.startsWith(scope)) continue;
|
|
4014
|
+
if (!includeTests && isTestFile2(filePath)) continue;
|
|
4015
|
+
if (ignorePatterns.length > 0 && ignorePatterns.some((p) => filePath.includes(p))) continue;
|
|
4016
|
+
const meta = node.metadata;
|
|
4017
|
+
const rawValue = meta?.value ?? meta?.literalValue;
|
|
4018
|
+
if (typeof rawValue !== "string" || rawValue.trim() === "") continue;
|
|
4019
|
+
const value = rawValue.trim();
|
|
4020
|
+
if (ENV_VAR_RE.test(value)) continue;
|
|
4021
|
+
let matched = false;
|
|
4022
|
+
for (const [re, label, severity] of VALUE_PATTERNS) {
|
|
4023
|
+
if (re.test(value)) {
|
|
4024
|
+
node.metadata = {
|
|
4025
|
+
...node.metadata ?? {},
|
|
4026
|
+
security: { secretRisk: true, secretPattern: label }
|
|
4027
|
+
};
|
|
4028
|
+
findings.push({
|
|
4029
|
+
file: filePath,
|
|
4030
|
+
line: node.startLine,
|
|
4031
|
+
symbol: node.name,
|
|
4032
|
+
pattern: label,
|
|
4033
|
+
severity
|
|
4034
|
+
});
|
|
4035
|
+
matched = true;
|
|
4036
|
+
break;
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
if (matched) continue;
|
|
4040
|
+
if (SENSITIVE_NAME_RE.test(node.name)) {
|
|
4041
|
+
node.metadata = {
|
|
4042
|
+
...node.metadata ?? {},
|
|
4043
|
+
security: { secretRisk: true, secretPattern: "sensitive-name-with-value" }
|
|
4044
|
+
};
|
|
4045
|
+
findings.push({
|
|
4046
|
+
file: filePath,
|
|
4047
|
+
line: node.startLine,
|
|
4048
|
+
symbol: node.name,
|
|
4049
|
+
pattern: "sensitive-name-with-value",
|
|
4050
|
+
severity: "MEDIUM"
|
|
4051
|
+
});
|
|
4052
|
+
continue;
|
|
4053
|
+
}
|
|
4054
|
+
if (SENSITIVE_NAME_RE.test(node.name) && value.length > 20 && shannonEntropy(value) > 4.5) {
|
|
4055
|
+
node.metadata = {
|
|
4056
|
+
...node.metadata ?? {},
|
|
4057
|
+
security: { secretRisk: true, secretPattern: "high-entropy-string" }
|
|
4058
|
+
};
|
|
4059
|
+
findings.push({
|
|
4060
|
+
file: filePath,
|
|
4061
|
+
line: node.startLine,
|
|
4062
|
+
symbol: node.name,
|
|
4063
|
+
pattern: "high-entropy-string",
|
|
4064
|
+
severity: "MEDIUM"
|
|
4065
|
+
});
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
return findings;
|
|
4069
|
+
}
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
});
|
|
4073
|
+
|
|
4074
|
+
// src/security/vulnerability-detector.ts
|
|
4075
|
+
var vulnerability_detector_exports = {};
|
|
4076
|
+
__export(vulnerability_detector_exports, {
|
|
4077
|
+
VulnerabilityDetector: () => VulnerabilityDetector
|
|
4078
|
+
});
|
|
4079
|
+
function hasCallers(graph, nodeId) {
|
|
4080
|
+
for (const edge of graph.findEdgesTo(nodeId)) {
|
|
4081
|
+
if (edge.kind === "calls") return true;
|
|
4082
|
+
}
|
|
4083
|
+
return false;
|
|
4084
|
+
}
|
|
4085
|
+
var CWE, SQL_PATTERN, XSS_PATTERN, SSRF_PATTERN, PATH_PATTERN, CMD_PATTERN, VulnerabilityDetector;
|
|
4086
|
+
var init_vulnerability_detector = __esm({
|
|
4087
|
+
"src/security/vulnerability-detector.ts"() {
|
|
4088
|
+
CWE = {
|
|
4089
|
+
SQL_INJECTION: "CWE-89",
|
|
4090
|
+
XSS: "CWE-79",
|
|
4091
|
+
SSRF: "CWE-918",
|
|
4092
|
+
PATH_TRAVERSAL: "CWE-22",
|
|
4093
|
+
COMMAND_INJECTION: "CWE-78"
|
|
4094
|
+
};
|
|
4095
|
+
SQL_PATTERN = /(db|database|connection|knex|sequelize|pool)\.(query|execute|raw)/i;
|
|
4096
|
+
XSS_PATTERN = /innerHTML|outerHTML|document\.write|insertAdjacentHTML/i;
|
|
4097
|
+
SSRF_PATTERN = /^(fetch|axios|http\.request|got)$/i;
|
|
4098
|
+
PATH_PATTERN = /^(fs\.readFile|fs\.writeFile|path\.join|createReadStream)$/i;
|
|
4099
|
+
CMD_PATTERN = /^(exec|execSync|spawn|eval)$/i;
|
|
4100
|
+
VulnerabilityDetector = class {
|
|
4101
|
+
detect(graph, options) {
|
|
4102
|
+
const findings = [];
|
|
4103
|
+
const scope = options?.scope;
|
|
4104
|
+
const types = options?.types ? new Set(options.types) : null;
|
|
4105
|
+
const want = (t) => !types || types.has(t);
|
|
4106
|
+
const nodes = [...graph.allNodes()];
|
|
4107
|
+
for (const node of nodes) {
|
|
4108
|
+
if (node.kind === "vulnerability") continue;
|
|
4109
|
+
const filePath = node.filePath;
|
|
4110
|
+
if (scope && !filePath.startsWith(scope)) continue;
|
|
4111
|
+
const nodeName = node.name;
|
|
4112
|
+
const meta = node.metadata;
|
|
4113
|
+
if (want("SQL_INJECTION") && SQL_PATTERN.test(nodeName)) {
|
|
4114
|
+
const hasDynamic = meta?.hasStringConcatenation === true || hasCallers(graph, node.id);
|
|
4115
|
+
if (hasDynamic) {
|
|
4116
|
+
findings.push({
|
|
4117
|
+
type: "SQL_INJECTION",
|
|
4118
|
+
severity: "MEDIUM",
|
|
4119
|
+
file: filePath,
|
|
4120
|
+
line: node.startLine,
|
|
4121
|
+
symbol: nodeName,
|
|
4122
|
+
description: `Potential SQL injection: ${nodeName} may execute unsanitized user input`,
|
|
4123
|
+
cweId: CWE["SQL_INJECTION"]
|
|
4124
|
+
});
|
|
4125
|
+
this._tagNode(graph, node.id, "SQL_INJECTION");
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
if (want("XSS") && XSS_PATTERN.test(nodeName)) {
|
|
4129
|
+
findings.push({
|
|
4130
|
+
type: "XSS",
|
|
4131
|
+
severity: "HIGH",
|
|
4132
|
+
file: filePath,
|
|
4133
|
+
line: node.startLine,
|
|
4134
|
+
symbol: nodeName,
|
|
4135
|
+
description: `Potential XSS: ${nodeName} writes to DOM sink`,
|
|
4136
|
+
cweId: CWE["XSS"]
|
|
4137
|
+
});
|
|
4138
|
+
this._tagNode(graph, node.id, "XSS");
|
|
4139
|
+
}
|
|
4140
|
+
if (want("XSS")) {
|
|
4141
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
4142
|
+
if (edge.kind === "calls") {
|
|
4143
|
+
const callee = graph.getNode(edge.target);
|
|
4144
|
+
if (callee && XSS_PATTERN.test(callee.name)) {
|
|
4145
|
+
findings.push({
|
|
4146
|
+
type: "XSS",
|
|
4147
|
+
severity: "HIGH",
|
|
4148
|
+
file: filePath,
|
|
4149
|
+
line: node.startLine,
|
|
4150
|
+
symbol: node.name,
|
|
4151
|
+
description: `Potential XSS: ${node.name} calls ${callee.name}`,
|
|
4152
|
+
cweId: CWE["XSS"]
|
|
4153
|
+
});
|
|
4154
|
+
this._tagNode(graph, node.id, "XSS");
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
if (want("SSRF") && SSRF_PATTERN.test(nodeName)) {
|
|
4160
|
+
const isDynamic = meta?.dynamicUrl === true;
|
|
4161
|
+
if (isDynamic) {
|
|
4162
|
+
findings.push({
|
|
4163
|
+
type: "SSRF",
|
|
4164
|
+
severity: "HIGH",
|
|
4165
|
+
file: filePath,
|
|
4166
|
+
line: node.startLine,
|
|
4167
|
+
symbol: nodeName,
|
|
4168
|
+
description: `Potential SSRF: ${nodeName} uses a dynamic URL`,
|
|
4169
|
+
cweId: CWE["SSRF"]
|
|
4170
|
+
});
|
|
4171
|
+
this._tagNode(graph, node.id, "SSRF");
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
if (want("PATH_TRAVERSAL") && PATH_PATTERN.test(nodeName)) {
|
|
4175
|
+
const isDynamic = meta?.dynamicPath === true;
|
|
4176
|
+
if (isDynamic) {
|
|
4177
|
+
findings.push({
|
|
4178
|
+
type: "PATH_TRAVERSAL",
|
|
4179
|
+
severity: "HIGH",
|
|
4180
|
+
file: filePath,
|
|
4181
|
+
line: node.startLine,
|
|
4182
|
+
symbol: nodeName,
|
|
4183
|
+
description: `Potential path traversal: ${nodeName} uses a dynamic path`,
|
|
4184
|
+
cweId: CWE["PATH_TRAVERSAL"]
|
|
4185
|
+
});
|
|
4186
|
+
this._tagNode(graph, node.id, "PATH_TRAVERSAL");
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
if (want("COMMAND_INJECTION") && CMD_PATTERN.test(nodeName)) {
|
|
4190
|
+
const isDynamic = meta?.dynamicArgs === true;
|
|
4191
|
+
if (isDynamic) {
|
|
4192
|
+
findings.push({
|
|
4193
|
+
type: "COMMAND_INJECTION",
|
|
4194
|
+
severity: "HIGH",
|
|
4195
|
+
file: filePath,
|
|
4196
|
+
line: node.startLine,
|
|
4197
|
+
symbol: nodeName,
|
|
4198
|
+
description: `Potential command injection: ${nodeName} uses dynamic arguments`,
|
|
4199
|
+
cweId: CWE["COMMAND_INJECTION"]
|
|
4200
|
+
});
|
|
4201
|
+
this._tagNode(graph, node.id, "COMMAND_INJECTION");
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4206
|
+
return findings.filter((f) => {
|
|
4207
|
+
const key = `${f.type}:${f.file}:${f.line}:${f.symbol}`;
|
|
4208
|
+
if (seen.has(key)) return false;
|
|
4209
|
+
seen.add(key);
|
|
4210
|
+
return true;
|
|
4211
|
+
});
|
|
4212
|
+
}
|
|
4213
|
+
_tagNode(graph, nodeId, vulnType) {
|
|
4214
|
+
const node = graph.getNode(nodeId);
|
|
4215
|
+
if (!node) return;
|
|
4216
|
+
const meta = node.metadata ?? {};
|
|
4217
|
+
node.metadata = {
|
|
4218
|
+
...meta,
|
|
4219
|
+
security: {
|
|
4220
|
+
...meta["security"] ?? {},
|
|
4221
|
+
vulnerability: vulnType
|
|
4222
|
+
}
|
|
4223
|
+
};
|
|
4224
|
+
const vulnNodeId = `vuln:${vulnType}:${nodeId}`;
|
|
4225
|
+
if (!graph.getNode(vulnNodeId)) {
|
|
4226
|
+
graph.addNode({
|
|
4227
|
+
id: vulnNodeId,
|
|
4228
|
+
kind: "vulnerability",
|
|
4229
|
+
name: `${vulnType}:${node.name}`,
|
|
4230
|
+
filePath: node.filePath,
|
|
4231
|
+
startLine: node.startLine,
|
|
4232
|
+
metadata: { cweId: CWE[vulnType], vulnType }
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
const edgeId = `has_vulnerability:${nodeId}:${vulnNodeId}`;
|
|
4236
|
+
if (!graph.getEdge(edgeId)) {
|
|
4237
|
+
graph.addEdge({
|
|
4238
|
+
id: edgeId,
|
|
4239
|
+
source: nodeId,
|
|
4240
|
+
target: vulnNodeId,
|
|
4241
|
+
kind: "has_vulnerability"
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
});
|
|
3706
4248
|
var ErrorCodes, AppError;
|
|
3707
4249
|
var init_codes = __esm({
|
|
3708
4250
|
"src/errors/codes.ts"() {
|
|
3709
4251
|
ErrorCodes = {
|
|
4252
|
+
// Auth (CI-1xxx)
|
|
3710
4253
|
UNAUTHORIZED: "CI-1000",
|
|
3711
4254
|
FORBIDDEN: "CI-1001",
|
|
3712
4255
|
NOT_FOUND: "CI-1002",
|
|
@@ -3716,6 +4259,16 @@ var init_codes = __esm({
|
|
|
3716
4259
|
RATE_LIMIT_EXCEEDED: "CI-1100",
|
|
3717
4260
|
PAYLOAD_TOO_LARGE: "CI-1101",
|
|
3718
4261
|
INVALID_REQUEST: "CI-1200",
|
|
4262
|
+
// Config (CI-2xxx)
|
|
4263
|
+
CONFIG_INVALID: "CI-2000",
|
|
4264
|
+
CONFIG_NOT_FOUND: "CI-2001",
|
|
4265
|
+
// Analysis (CI-3xxx)
|
|
4266
|
+
ANALYSIS_FAILED: "CI-3000",
|
|
4267
|
+
// DB (CI-4xxx)
|
|
4268
|
+
DB_ERROR: "CI-4000",
|
|
4269
|
+
// Network (CI-5xxx)
|
|
4270
|
+
NETWORK_ERROR: "CI-5001",
|
|
4271
|
+
// Internal (CI-9xxx)
|
|
3719
4272
|
INTERNAL_ERROR: "CI-5000"
|
|
3720
4273
|
};
|
|
3721
4274
|
AppError = class extends Error {
|
|
@@ -3735,10 +4288,10 @@ var init_codes = __esm({
|
|
|
3735
4288
|
}
|
|
3736
4289
|
});
|
|
3737
4290
|
function secureMkdir(dir) {
|
|
3738
|
-
|
|
4291
|
+
fs24.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
3739
4292
|
if (process.platform !== "win32") {
|
|
3740
4293
|
try {
|
|
3741
|
-
|
|
4294
|
+
fs24.chmodSync(dir, SECURE_DIR_MODE);
|
|
3742
4295
|
} catch {
|
|
3743
4296
|
}
|
|
3744
4297
|
}
|
|
@@ -3746,17 +4299,17 @@ function secureMkdir(dir) {
|
|
|
3746
4299
|
function secureChmodFile(file) {
|
|
3747
4300
|
if (process.platform === "win32") return;
|
|
3748
4301
|
try {
|
|
3749
|
-
|
|
4302
|
+
fs24.chmodSync(file, SECURE_FILE_MODE);
|
|
3750
4303
|
} catch {
|
|
3751
4304
|
}
|
|
3752
4305
|
}
|
|
3753
4306
|
function tightenDbFiles(dir) {
|
|
3754
4307
|
if (process.platform === "win32") return;
|
|
3755
|
-
if (!
|
|
3756
|
-
for (const name of
|
|
4308
|
+
if (!fs24.existsSync(dir)) return;
|
|
4309
|
+
for (const name of fs24.readdirSync(dir)) {
|
|
3757
4310
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
3758
4311
|
try {
|
|
3759
|
-
|
|
4312
|
+
fs24.chmodSync(path31.join(dir, name), SECURE_FILE_MODE);
|
|
3760
4313
|
} catch {
|
|
3761
4314
|
}
|
|
3762
4315
|
}
|
|
@@ -3770,7 +4323,7 @@ var init_fs_secure = __esm({
|
|
|
3770
4323
|
}
|
|
3771
4324
|
});
|
|
3772
4325
|
function getUsersDBPath() {
|
|
3773
|
-
return process.env["CODE_INTEL_USERS_DB_PATH"] ??
|
|
4326
|
+
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path31.join(os13.homedir(), ".code-intel", "users.db");
|
|
3774
4327
|
}
|
|
3775
4328
|
function getOrCreateUsersDB() {
|
|
3776
4329
|
if (!_usersDB) {
|
|
@@ -3786,7 +4339,7 @@ var init_users_db = __esm({
|
|
|
3786
4339
|
UsersDB = class {
|
|
3787
4340
|
db;
|
|
3788
4341
|
constructor(dbPath) {
|
|
3789
|
-
const dir =
|
|
4342
|
+
const dir = path31.dirname(dbPath);
|
|
3790
4343
|
secureMkdir(dir);
|
|
3791
4344
|
this.db = new Database3(dbPath);
|
|
3792
4345
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -4063,17 +4616,17 @@ function getScryptN() {
|
|
|
4063
4616
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
4064
4617
|
}
|
|
4065
4618
|
function getSecretsPath() {
|
|
4066
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ??
|
|
4619
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path31.join(os13.homedir(), ".code-intel", ".secrets");
|
|
4067
4620
|
}
|
|
4068
4621
|
function getMasterPassword() {
|
|
4069
4622
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
4070
4623
|
if (fromEnv && fromEnv.length >= 16) return fromEnv;
|
|
4071
4624
|
let username = "unknown";
|
|
4072
4625
|
try {
|
|
4073
|
-
username =
|
|
4626
|
+
username = os13.userInfo().username;
|
|
4074
4627
|
} catch {
|
|
4075
4628
|
}
|
|
4076
|
-
return `code-intel-machine:${
|
|
4629
|
+
return `code-intel-machine:${os13.hostname()}:${username}:${os13.platform()}`;
|
|
4077
4630
|
}
|
|
4078
4631
|
function deriveKey(password, salt) {
|
|
4079
4632
|
return crypto5.scryptSync(password, salt, KEY_LEN, { N: getScryptN(), r: 8, p: 1 });
|
|
@@ -4094,8 +4647,8 @@ function decryptSecrets(encrypted) {
|
|
|
4094
4647
|
return JSON.parse(plaintext.toString("utf8"));
|
|
4095
4648
|
}
|
|
4096
4649
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
4097
|
-
if (!
|
|
4098
|
-
const blob =
|
|
4650
|
+
if (!fs24.existsSync(secretsPath)) return {};
|
|
4651
|
+
const blob = fs24.readFileSync(secretsPath);
|
|
4099
4652
|
return decryptSecrets(blob);
|
|
4100
4653
|
}
|
|
4101
4654
|
function getSecret(key, secretsPath = getSecretsPath()) {
|
|
@@ -4662,7 +5215,7 @@ init_shared();
|
|
|
4662
5215
|
init_shared();
|
|
4663
5216
|
init_typescript();
|
|
4664
5217
|
function resolveRelative(rawPath, fromFile, workspace) {
|
|
4665
|
-
const fromDir =
|
|
5218
|
+
const fromDir = path31.dirname(fromFile);
|
|
4666
5219
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
4667
5220
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
|
|
4668
5221
|
const resolved = workspace.resolve(fromDir, cleaned);
|
|
@@ -4714,7 +5267,7 @@ var pythonModule = {
|
|
|
4714
5267
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4715
5268
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
4716
5269
|
const parts = cleaned.split(".");
|
|
4717
|
-
const fromDir =
|
|
5270
|
+
const fromDir = path31.dirname(fromFile);
|
|
4718
5271
|
const relPath = parts.join("/");
|
|
4719
5272
|
for (const suffix of ["/__init__.py", ".py"]) {
|
|
4720
5273
|
const r = workspace.resolve(fromDir, relPath + suffix);
|
|
@@ -4793,7 +5346,7 @@ var cModule = {
|
|
|
4793
5346
|
inheritanceStrategy: "none",
|
|
4794
5347
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4795
5348
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4796
|
-
const fromDir =
|
|
5349
|
+
const fromDir = path31.dirname(fromFile);
|
|
4797
5350
|
return workspace.resolve(fromDir, cleaned);
|
|
4798
5351
|
},
|
|
4799
5352
|
isExported(_node) {
|
|
@@ -4816,7 +5369,7 @@ var cppModule = {
|
|
|
4816
5369
|
inheritanceStrategy: "depth-first",
|
|
4817
5370
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4818
5371
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4819
|
-
const fromDir =
|
|
5372
|
+
const fromDir = path31.dirname(fromFile);
|
|
4820
5373
|
return workspace.resolve(fromDir, cleaned);
|
|
4821
5374
|
},
|
|
4822
5375
|
isExported(_node) {
|
|
@@ -4978,7 +5531,7 @@ var dartModule = {
|
|
|
4978
5531
|
const pkg = cleaned.replace("package:", "");
|
|
4979
5532
|
return workspace.findByPackage(pkg);
|
|
4980
5533
|
}
|
|
4981
|
-
const fromDir =
|
|
5534
|
+
const fromDir = path31.dirname(fromFile);
|
|
4982
5535
|
return workspace.resolve(fromDir, cleaned);
|
|
4983
5536
|
},
|
|
4984
5537
|
isExported(node) {
|
|
@@ -5333,25 +5886,25 @@ function validateDAG(phases) {
|
|
|
5333
5886
|
const visiting = /* @__PURE__ */ new Set();
|
|
5334
5887
|
const visited = /* @__PURE__ */ new Set();
|
|
5335
5888
|
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
5336
|
-
function dfs(name,
|
|
5889
|
+
function dfs(name, path32) {
|
|
5337
5890
|
if (visiting.has(name)) {
|
|
5338
|
-
const cycleStart =
|
|
5339
|
-
const cycle =
|
|
5891
|
+
const cycleStart = path32.indexOf(name);
|
|
5892
|
+
const cycle = path32.slice(cycleStart).concat(name);
|
|
5340
5893
|
errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
5341
5894
|
return true;
|
|
5342
5895
|
}
|
|
5343
5896
|
if (visited.has(name)) return false;
|
|
5344
5897
|
visiting.add(name);
|
|
5345
|
-
|
|
5898
|
+
path32.push(name);
|
|
5346
5899
|
const phase = phaseMap.get(name);
|
|
5347
5900
|
if (phase) {
|
|
5348
5901
|
for (const dep of phase.dependencies) {
|
|
5349
|
-
if (dfs(dep,
|
|
5902
|
+
if (dfs(dep, path32)) return true;
|
|
5350
5903
|
}
|
|
5351
5904
|
}
|
|
5352
5905
|
visiting.delete(name);
|
|
5353
5906
|
visited.add(name);
|
|
5354
|
-
|
|
5907
|
+
path32.pop();
|
|
5355
5908
|
return false;
|
|
5356
5909
|
}
|
|
5357
5910
|
for (const phase of phases) {
|
|
@@ -5564,7 +6117,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
5564
6117
|
]);
|
|
5565
6118
|
function loadIgnorePatterns(workspaceRoot) {
|
|
5566
6119
|
try {
|
|
5567
|
-
const raw =
|
|
6120
|
+
const raw = fs24.readFileSync(path31.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
5568
6121
|
const extras = /* @__PURE__ */ new Set();
|
|
5569
6122
|
for (const line of raw.split("\n")) {
|
|
5570
6123
|
const trimmed = line.trim();
|
|
@@ -5588,7 +6141,7 @@ var scanPhase = {
|
|
|
5588
6141
|
function walk(dir) {
|
|
5589
6142
|
let entries;
|
|
5590
6143
|
try {
|
|
5591
|
-
entries =
|
|
6144
|
+
entries = fs24.readdirSync(dir, { withFileTypes: true });
|
|
5592
6145
|
} catch {
|
|
5593
6146
|
return;
|
|
5594
6147
|
}
|
|
@@ -5597,15 +6150,15 @@ var scanPhase = {
|
|
|
5597
6150
|
if (entry.name.startsWith(".")) continue;
|
|
5598
6151
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5599
6152
|
if (extraIgnore.has(entry.name)) continue;
|
|
5600
|
-
walk(
|
|
6153
|
+
walk(path31.join(dir, entry.name));
|
|
5601
6154
|
} else if (entry.isFile()) {
|
|
5602
6155
|
const name = entry.name;
|
|
5603
6156
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
5604
|
-
const ext =
|
|
6157
|
+
const ext = path31.extname(name);
|
|
5605
6158
|
if (!extensions.has(ext)) continue;
|
|
5606
|
-
const fullPath =
|
|
6159
|
+
const fullPath = path31.join(dir, name);
|
|
5607
6160
|
try {
|
|
5608
|
-
const stat =
|
|
6161
|
+
const stat = fs24.statSync(fullPath);
|
|
5609
6162
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
5610
6163
|
} catch {
|
|
5611
6164
|
continue;
|
|
@@ -5632,20 +6185,20 @@ var structurePhase = {
|
|
|
5632
6185
|
const dirs = /* @__PURE__ */ new Set();
|
|
5633
6186
|
let structDone = 0;
|
|
5634
6187
|
for (const filePath of context2.filePaths) {
|
|
5635
|
-
const relativePath =
|
|
6188
|
+
const relativePath = path31.relative(context2.workspaceRoot, filePath);
|
|
5636
6189
|
const lang = detectLanguage(filePath);
|
|
5637
6190
|
context2.graph.addNode({
|
|
5638
6191
|
id: generateNodeId("file", relativePath, relativePath),
|
|
5639
6192
|
kind: "file",
|
|
5640
|
-
name:
|
|
6193
|
+
name: path31.basename(filePath),
|
|
5641
6194
|
filePath: relativePath,
|
|
5642
6195
|
metadata: lang ? { language: lang } : void 0
|
|
5643
6196
|
});
|
|
5644
|
-
let dir =
|
|
6197
|
+
let dir = path31.dirname(relativePath);
|
|
5645
6198
|
while (dir && dir !== "." && dir !== "") {
|
|
5646
6199
|
if (dirs.has(dir)) break;
|
|
5647
6200
|
dirs.add(dir);
|
|
5648
|
-
dir =
|
|
6201
|
+
dir = path31.dirname(dir);
|
|
5649
6202
|
}
|
|
5650
6203
|
structDone++;
|
|
5651
6204
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -5654,7 +6207,7 @@ var structurePhase = {
|
|
|
5654
6207
|
context2.graph.addNode({
|
|
5655
6208
|
id: generateNodeId("directory", dir, dir),
|
|
5656
6209
|
kind: "directory",
|
|
5657
|
-
name:
|
|
6210
|
+
name: path31.basename(dir),
|
|
5658
6211
|
filePath: dir
|
|
5659
6212
|
});
|
|
5660
6213
|
}
|
|
@@ -5741,9 +6294,9 @@ var flowPhase = {
|
|
|
5741
6294
|
for (const node of graph.allNodes()) {
|
|
5742
6295
|
if (!["function", "method"].includes(node.kind)) continue;
|
|
5743
6296
|
let score = 0;
|
|
5744
|
-
const
|
|
6297
|
+
const hasCallers2 = calledNodes.has(node.id);
|
|
5745
6298
|
const outCalls = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "calls");
|
|
5746
|
-
if (!
|
|
6299
|
+
if (!hasCallers2 && outCalls.length > 0) score += 10;
|
|
5747
6300
|
if (node.exported) score += 5;
|
|
5748
6301
|
if (/^(main|handle|init|start|run|execute|process|serve|listen|bootstrap)/.test(node.name)) score += 3;
|
|
5749
6302
|
if (node.filePath.includes("test") || node.filePath.includes("spec") || node.filePath.includes("__test")) score -= 20;
|
|
@@ -5765,22 +6318,22 @@ var flowPhase = {
|
|
|
5765
6318
|
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
5766
6319
|
const visited = /* @__PURE__ */ new Set();
|
|
5767
6320
|
while (queue.length > 0 && flowCount < maxFlows) {
|
|
5768
|
-
const { nodeId, path:
|
|
5769
|
-
if (
|
|
6321
|
+
const { nodeId, path: path32 } = queue.shift();
|
|
6322
|
+
if (path32.length > maxDepth) continue;
|
|
5770
6323
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
5771
|
-
if (callEdges.length === 0 &&
|
|
6324
|
+
if (callEdges.length === 0 && path32.length >= 3) {
|
|
5772
6325
|
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
5773
6326
|
graph.addNode({
|
|
5774
6327
|
id: flowId,
|
|
5775
6328
|
kind: "flow",
|
|
5776
6329
|
name: `${ep.name} flow ${flowCount}`,
|
|
5777
6330
|
filePath: ep.filePath,
|
|
5778
|
-
metadata: { steps:
|
|
6331
|
+
metadata: { steps: path32, entryPoint: ep.name }
|
|
5779
6332
|
});
|
|
5780
|
-
for (let i = 0; i <
|
|
6333
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5781
6334
|
graph.addEdge({
|
|
5782
|
-
id: generateEdgeId(
|
|
5783
|
-
source:
|
|
6335
|
+
id: generateEdgeId(path32[i], flowId, `step_of_${i}`),
|
|
6336
|
+
source: path32[i],
|
|
5784
6337
|
target: flowId,
|
|
5785
6338
|
kind: "step_of",
|
|
5786
6339
|
weight: 1,
|
|
@@ -5793,7 +6346,7 @@ var flowPhase = {
|
|
|
5793
6346
|
for (const edge of callEdges) {
|
|
5794
6347
|
if (visited.has(edge.target)) continue;
|
|
5795
6348
|
visited.add(edge.target);
|
|
5796
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6349
|
+
queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
|
|
5797
6350
|
}
|
|
5798
6351
|
}
|
|
5799
6352
|
}
|
|
@@ -5811,7 +6364,7 @@ var LLMGovernanceLogger = class {
|
|
|
5811
6364
|
}
|
|
5812
6365
|
/** Path to the JSONL log file. */
|
|
5813
6366
|
getLogPath() {
|
|
5814
|
-
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ??
|
|
6367
|
+
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path31.join(os13.homedir(), ".code-intel", "llm-governance.jsonl");
|
|
5815
6368
|
}
|
|
5816
6369
|
/**
|
|
5817
6370
|
* Append an entry to the governance log.
|
|
@@ -5827,8 +6380,8 @@ var LLMGovernanceLogger = class {
|
|
|
5827
6380
|
...entry
|
|
5828
6381
|
};
|
|
5829
6382
|
const logPath = this.getLogPath();
|
|
5830
|
-
|
|
5831
|
-
|
|
6383
|
+
fs24.mkdirSync(path31.dirname(logPath), { recursive: true });
|
|
6384
|
+
fs24.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
5832
6385
|
} catch {
|
|
5833
6386
|
}
|
|
5834
6387
|
}
|
|
@@ -5838,7 +6391,7 @@ var LLMGovernanceLogger = class {
|
|
|
5838
6391
|
*/
|
|
5839
6392
|
readLog(limit = 100) {
|
|
5840
6393
|
try {
|
|
5841
|
-
const raw =
|
|
6394
|
+
const raw = fs24.readFileSync(this.getLogPath(), "utf-8");
|
|
5842
6395
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
5843
6396
|
return lines.map((l) => JSON.parse(l));
|
|
5844
6397
|
} catch {
|
|
@@ -5982,17 +6535,17 @@ function traceFlow(entryId, graph, maxDepth = 10, maxBranching = 4) {
|
|
|
5982
6535
|
const queue = [{ nodeId: entryId, path: [entryId] }];
|
|
5983
6536
|
const visited = /* @__PURE__ */ new Set();
|
|
5984
6537
|
while (queue.length > 0 && flows.length < maxFlows) {
|
|
5985
|
-
const { nodeId, path:
|
|
5986
|
-
if (
|
|
6538
|
+
const { nodeId, path: path32 } = queue.shift();
|
|
6539
|
+
if (path32.length > maxDepth) continue;
|
|
5987
6540
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
5988
|
-
if (callEdges.length === 0 &&
|
|
5989
|
-
flows.push({ entryPointId: entryId, steps: [...
|
|
6541
|
+
if (callEdges.length === 0 && path32.length >= 3) {
|
|
6542
|
+
flows.push({ entryPointId: entryId, steps: [...path32] });
|
|
5990
6543
|
continue;
|
|
5991
6544
|
}
|
|
5992
6545
|
for (const edge of callEdges) {
|
|
5993
6546
|
if (visited.has(edge.target)) continue;
|
|
5994
6547
|
visited.add(edge.target);
|
|
5995
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6548
|
+
queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
|
|
5996
6549
|
}
|
|
5997
6550
|
}
|
|
5998
6551
|
}
|
|
@@ -6226,7 +6779,7 @@ init_embedder();
|
|
|
6226
6779
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6227
6780
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
6228
6781
|
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6229
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
6782
|
+
const hasVectorDb = Boolean(vectorDbPath && fs24.existsSync(vectorDbPath));
|
|
6230
6783
|
if (!hasVectorDb) {
|
|
6231
6784
|
const bm25Results2 = await bm25Promise;
|
|
6232
6785
|
return {
|
|
@@ -6283,7 +6836,7 @@ var DbManager = class {
|
|
|
6283
6836
|
this.dbPath = dbPath;
|
|
6284
6837
|
}
|
|
6285
6838
|
async init() {
|
|
6286
|
-
|
|
6839
|
+
fs24.mkdirSync(path31.dirname(this.dbPath), { recursive: true });
|
|
6287
6840
|
this.db = new Database(this.dbPath);
|
|
6288
6841
|
await this.db.init();
|
|
6289
6842
|
this.conn = new Connection(this.db);
|
|
@@ -6373,7 +6926,7 @@ function getCreateEdgeTableDDL() {
|
|
|
6373
6926
|
)`];
|
|
6374
6927
|
}
|
|
6375
6928
|
function writeNodeCSVs(graph, outputDir) {
|
|
6376
|
-
|
|
6929
|
+
fs24.mkdirSync(outputDir, { recursive: true });
|
|
6377
6930
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
6378
6931
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
6379
6932
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -6381,7 +6934,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
6381
6934
|
const table = NODE_TABLE_MAP[node.kind];
|
|
6382
6935
|
if (!tableBuffers.has(table)) {
|
|
6383
6936
|
tableBuffers.set(table, [header]);
|
|
6384
|
-
tableFilePaths.set(table,
|
|
6937
|
+
tableFilePaths.set(table, path31.join(outputDir, `${table}.csv`));
|
|
6385
6938
|
}
|
|
6386
6939
|
tableBuffers.get(table).push(
|
|
6387
6940
|
csvRow([
|
|
@@ -6401,12 +6954,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
6401
6954
|
);
|
|
6402
6955
|
}
|
|
6403
6956
|
for (const [table, lines] of tableBuffers) {
|
|
6404
|
-
|
|
6957
|
+
fs24.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
6405
6958
|
}
|
|
6406
6959
|
return tableFilePaths;
|
|
6407
6960
|
}
|
|
6408
6961
|
function writeEdgeCSV(graph, outputDir) {
|
|
6409
|
-
|
|
6962
|
+
fs24.mkdirSync(outputDir, { recursive: true });
|
|
6410
6963
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
6411
6964
|
const groups = /* @__PURE__ */ new Map();
|
|
6412
6965
|
for (const edge of graph.allEdges()) {
|
|
@@ -6417,7 +6970,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
6417
6970
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
6418
6971
|
const key = `${fromTable}->${toTable}`;
|
|
6419
6972
|
if (!groups.has(key)) {
|
|
6420
|
-
const filePath =
|
|
6973
|
+
const filePath = path31.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
6421
6974
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
6422
6975
|
}
|
|
6423
6976
|
groups.get(key).lines.push(
|
|
@@ -6432,7 +6985,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
6432
6985
|
}
|
|
6433
6986
|
const result = [];
|
|
6434
6987
|
for (const group of groups.values()) {
|
|
6435
|
-
|
|
6988
|
+
fs24.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
6436
6989
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
6437
6990
|
}
|
|
6438
6991
|
return result;
|
|
@@ -6460,7 +7013,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6460
7013
|
} catch {
|
|
6461
7014
|
}
|
|
6462
7015
|
}
|
|
6463
|
-
const tmpDir =
|
|
7016
|
+
const tmpDir = fs24.mkdtempSync(path31.join(os13.tmpdir(), "code-intel-csv-"));
|
|
6464
7017
|
try {
|
|
6465
7018
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
6466
7019
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -6479,8 +7032,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6479
7032
|
}
|
|
6480
7033
|
let nodeCount = 0;
|
|
6481
7034
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
6482
|
-
if (!
|
|
6483
|
-
const stat =
|
|
7035
|
+
if (!fs24.existsSync(csvPath)) continue;
|
|
7036
|
+
const stat = fs24.statSync(csvPath);
|
|
6484
7037
|
if (stat.size < 50) continue;
|
|
6485
7038
|
try {
|
|
6486
7039
|
await dbManager.execute(
|
|
@@ -6493,8 +7046,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6493
7046
|
}
|
|
6494
7047
|
let edgeCount = 0;
|
|
6495
7048
|
for (const group of edgeGroups) {
|
|
6496
|
-
if (!
|
|
6497
|
-
const stat =
|
|
7049
|
+
if (!fs24.existsSync(group.filePath)) continue;
|
|
7050
|
+
const stat = fs24.statSync(group.filePath);
|
|
6498
7051
|
if (stat.size < 50) continue;
|
|
6499
7052
|
try {
|
|
6500
7053
|
await dbManager.execute(
|
|
@@ -6508,7 +7061,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6508
7061
|
return { nodeCount, edgeCount };
|
|
6509
7062
|
} finally {
|
|
6510
7063
|
try {
|
|
6511
|
-
|
|
7064
|
+
fs24.rmSync(tmpDir, { recursive: true, force: true });
|
|
6512
7065
|
} catch {
|
|
6513
7066
|
}
|
|
6514
7067
|
}
|
|
@@ -6560,19 +7113,19 @@ function buildNodeProps(node) {
|
|
|
6560
7113
|
function escCypher(s) {
|
|
6561
7114
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
6562
7115
|
}
|
|
6563
|
-
var GLOBAL_DIR =
|
|
6564
|
-
var REPOS_FILE =
|
|
7116
|
+
var GLOBAL_DIR = path31.join(os13.homedir(), ".code-intel");
|
|
7117
|
+
var REPOS_FILE = path31.join(GLOBAL_DIR, "repos.json");
|
|
6565
7118
|
function loadRegistry() {
|
|
6566
7119
|
try {
|
|
6567
|
-
const data =
|
|
7120
|
+
const data = fs24.readFileSync(REPOS_FILE, "utf-8");
|
|
6568
7121
|
return JSON.parse(data);
|
|
6569
7122
|
} catch {
|
|
6570
7123
|
return [];
|
|
6571
7124
|
}
|
|
6572
7125
|
}
|
|
6573
7126
|
function saveRegistry(entries) {
|
|
6574
|
-
|
|
6575
|
-
|
|
7127
|
+
fs24.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
7128
|
+
fs24.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
6576
7129
|
}
|
|
6577
7130
|
function upsertRepo(entry) {
|
|
6578
7131
|
const entries = loadRegistry();
|
|
@@ -6589,23 +7142,23 @@ function removeRepo(repoPath) {
|
|
|
6589
7142
|
saveRegistry(entries);
|
|
6590
7143
|
}
|
|
6591
7144
|
function saveMetadata(repoDir, metadata) {
|
|
6592
|
-
const metaDir =
|
|
6593
|
-
|
|
6594
|
-
|
|
7145
|
+
const metaDir = path31.join(repoDir, ".code-intel");
|
|
7146
|
+
fs24.mkdirSync(metaDir, { recursive: true });
|
|
7147
|
+
fs24.writeFileSync(path31.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
6595
7148
|
}
|
|
6596
7149
|
function loadMetadata(repoDir) {
|
|
6597
7150
|
try {
|
|
6598
|
-
const data =
|
|
7151
|
+
const data = fs24.readFileSync(path31.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
6599
7152
|
return JSON.parse(data);
|
|
6600
7153
|
} catch {
|
|
6601
7154
|
return null;
|
|
6602
7155
|
}
|
|
6603
7156
|
}
|
|
6604
7157
|
function getDbPath(repoDir) {
|
|
6605
|
-
return
|
|
7158
|
+
return path31.join(repoDir, ".code-intel", "graph.db");
|
|
6606
7159
|
}
|
|
6607
7160
|
function getVectorDbPath(repoDir) {
|
|
6608
|
-
return
|
|
7161
|
+
return path31.join(repoDir, ".code-intel", "vector.db");
|
|
6609
7162
|
}
|
|
6610
7163
|
|
|
6611
7164
|
// src/mcp-server/server.ts
|
|
@@ -6689,12 +7242,12 @@ function scanForFiles(root, matcher, maxDepth = 2) {
|
|
|
6689
7242
|
if (depth > maxDepth) return;
|
|
6690
7243
|
let entries;
|
|
6691
7244
|
try {
|
|
6692
|
-
entries =
|
|
7245
|
+
entries = fs24.readdirSync(dir, { withFileTypes: true });
|
|
6693
7246
|
} catch {
|
|
6694
7247
|
return;
|
|
6695
7248
|
}
|
|
6696
7249
|
for (const entry of entries) {
|
|
6697
|
-
const full =
|
|
7250
|
+
const full = path31.join(dir, entry.name);
|
|
6698
7251
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
6699
7252
|
walk(full, depth + 1);
|
|
6700
7253
|
} else if (entry.isFile() && matcher(entry.name)) {
|
|
@@ -6717,8 +7270,8 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
6717
7270
|
]);
|
|
6718
7271
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
6719
7272
|
function tryParseFile(filePath) {
|
|
6720
|
-
const ext =
|
|
6721
|
-
const content =
|
|
7273
|
+
const ext = path31.extname(filePath).toLowerCase();
|
|
7274
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6722
7275
|
if (ext === ".json") {
|
|
6723
7276
|
try {
|
|
6724
7277
|
return JSON.parse(content);
|
|
@@ -6772,7 +7325,7 @@ async function parseGraphQLContracts(repoRoot) {
|
|
|
6772
7325
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
6773
7326
|
const contracts = [];
|
|
6774
7327
|
for (const filePath of files) {
|
|
6775
|
-
const content =
|
|
7328
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6776
7329
|
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
6777
7330
|
let match;
|
|
6778
7331
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
@@ -6803,7 +7356,7 @@ async function parseProtoContracts(repoRoot) {
|
|
|
6803
7356
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
6804
7357
|
const contracts = [];
|
|
6805
7358
|
for (const filePath of files) {
|
|
6806
|
-
const content =
|
|
7359
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6807
7360
|
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
6808
7361
|
let serviceMatch;
|
|
6809
7362
|
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
@@ -7008,8 +7561,8 @@ async function syncGroup(group) {
|
|
|
7008
7561
|
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
7009
7562
|
continue;
|
|
7010
7563
|
}
|
|
7011
|
-
const dbPath =
|
|
7012
|
-
if (!
|
|
7564
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
7565
|
+
if (!fs24.existsSync(dbPath)) {
|
|
7013
7566
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
7014
7567
|
continue;
|
|
7015
7568
|
}
|
|
@@ -7082,8 +7635,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7082
7635
|
for (const member of group.members) {
|
|
7083
7636
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7084
7637
|
if (!regEntry) continue;
|
|
7085
|
-
const dbPath =
|
|
7086
|
-
if (!
|
|
7638
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
7639
|
+
if (!fs24.existsSync(dbPath)) continue;
|
|
7087
7640
|
const graph = createKnowledgeGraph();
|
|
7088
7641
|
const db = new DbManager(dbPath);
|
|
7089
7642
|
try {
|
|
@@ -7583,22 +8136,22 @@ function suggestTests(graph, symbolName) {
|
|
|
7583
8136
|
const callPaths = [];
|
|
7584
8137
|
const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
|
|
7585
8138
|
while (pathQueue.length > 0 && callPaths.length < 5) {
|
|
7586
|
-
const { id, path:
|
|
7587
|
-
let
|
|
8139
|
+
const { id, path: path32, depth } = pathQueue.shift();
|
|
8140
|
+
let hasCallers2 = false;
|
|
7588
8141
|
for (const edge of graph.findEdgesTo(id)) {
|
|
7589
8142
|
if (edge.kind !== "calls") continue;
|
|
7590
8143
|
const callerNode = graph.getNode(edge.source);
|
|
7591
8144
|
if (!callerNode) continue;
|
|
7592
|
-
|
|
7593
|
-
const newPath = [callerNode.name, ...
|
|
8145
|
+
hasCallers2 = true;
|
|
8146
|
+
const newPath = [callerNode.name, ...path32];
|
|
7594
8147
|
if (depth + 1 >= 3 || callPaths.length >= 5) {
|
|
7595
8148
|
if (callPaths.length < 5) callPaths.push(newPath);
|
|
7596
8149
|
continue;
|
|
7597
8150
|
}
|
|
7598
8151
|
pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
|
|
7599
8152
|
}
|
|
7600
|
-
if (!
|
|
7601
|
-
callPaths.push(
|
|
8153
|
+
if (!hasCallers2 && path32.length > 1) {
|
|
8154
|
+
callPaths.push(path32);
|
|
7602
8155
|
}
|
|
7603
8156
|
}
|
|
7604
8157
|
if (callPaths.length === 0) {
|
|
@@ -8073,6 +8626,70 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8073
8626
|
},
|
|
8074
8627
|
required: ["cluster"]
|
|
8075
8628
|
}
|
|
8629
|
+
},
|
|
8630
|
+
{
|
|
8631
|
+
name: "deprecated_usage",
|
|
8632
|
+
description: "Find usages of deprecated APIs in the codebase",
|
|
8633
|
+
inputSchema: {
|
|
8634
|
+
type: "object",
|
|
8635
|
+
properties: {
|
|
8636
|
+
scope: { type: "string", description: "Directory scope filter" },
|
|
8637
|
+
..._tokenProp
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
},
|
|
8641
|
+
{
|
|
8642
|
+
name: "complexity_hotspots",
|
|
8643
|
+
description: "Ranked list of functions/methods by cyclomatic complexity. Useful for identifying refactoring candidates.",
|
|
8644
|
+
inputSchema: {
|
|
8645
|
+
type: "object",
|
|
8646
|
+
properties: {
|
|
8647
|
+
scope: { type: "string", description: "Limit to a file path prefix (optional)" },
|
|
8648
|
+
limit: { type: "number", description: "Maximum number of results (default: 20)" },
|
|
8649
|
+
..._tokenProp
|
|
8650
|
+
}
|
|
8651
|
+
}
|
|
8652
|
+
},
|
|
8653
|
+
{
|
|
8654
|
+
name: "coverage_gaps",
|
|
8655
|
+
description: "Find exported symbols with no test coverage, ranked by blast radius. Useful for prioritizing test writing.",
|
|
8656
|
+
inputSchema: {
|
|
8657
|
+
type: "object",
|
|
8658
|
+
properties: {
|
|
8659
|
+
scope: { type: "string", description: "Limit to a file path prefix (optional)" },
|
|
8660
|
+
limit: { type: "number", description: "Maximum number of untested results to return (default: 20)" },
|
|
8661
|
+
..._tokenProp
|
|
8662
|
+
}
|
|
8663
|
+
}
|
|
8664
|
+
},
|
|
8665
|
+
{
|
|
8666
|
+
name: "secrets",
|
|
8667
|
+
description: "Scan the knowledge graph for hardcoded secrets: API keys, passwords, tokens, private keys, high-entropy strings",
|
|
8668
|
+
inputSchema: {
|
|
8669
|
+
type: "object",
|
|
8670
|
+
properties: {
|
|
8671
|
+
scope: { type: "string", description: "Limit scan to files under this path prefix" },
|
|
8672
|
+
includeTestFiles: { type: "boolean", description: "Include test/spec/fixture files (default: false)" },
|
|
8673
|
+
..._tokenProp
|
|
8674
|
+
}
|
|
8675
|
+
}
|
|
8676
|
+
},
|
|
8677
|
+
{
|
|
8678
|
+
name: "vulnerability_scan",
|
|
8679
|
+
description: "Scan the knowledge graph for OWASP vulnerabilities: SQL injection, XSS, SSRF, path traversal, command injection",
|
|
8680
|
+
inputSchema: {
|
|
8681
|
+
type: "object",
|
|
8682
|
+
properties: {
|
|
8683
|
+
scope: { type: "string", description: "Limit scan to files under this path prefix" },
|
|
8684
|
+
types: {
|
|
8685
|
+
type: "array",
|
|
8686
|
+
items: { type: "string", enum: ["SQL_INJECTION", "XSS", "SSRF", "PATH_TRAVERSAL", "COMMAND_INJECTION"] },
|
|
8687
|
+
description: "Vulnerability types to detect (default: all)"
|
|
8688
|
+
},
|
|
8689
|
+
severity: { type: "string", description: "Minimum severity to report: HIGH|MEDIUM|LOW (default: LOW)" },
|
|
8690
|
+
..._tokenProp
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8076
8693
|
}
|
|
8077
8694
|
]
|
|
8078
8695
|
}));
|
|
@@ -8496,7 +9113,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
8496
9113
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
8497
9114
|
for (const node of graph.allNodes()) {
|
|
8498
9115
|
if (!node.filePath) continue;
|
|
8499
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
9116
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path31.sep, "");
|
|
8500
9117
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
8501
9118
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
8502
9119
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -8769,6 +9386,63 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
8769
9386
|
const result = summarizeCluster(graph, cluster);
|
|
8770
9387
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
8771
9388
|
}
|
|
9389
|
+
case "deprecated_usage": {
|
|
9390
|
+
const scope = a.scope;
|
|
9391
|
+
const { DeprecatedDetector: DeprecatedDetector2 } = await Promise.resolve().then(() => (init_deprecated_detector(), deprecated_detector_exports));
|
|
9392
|
+
const detector = new DeprecatedDetector2();
|
|
9393
|
+
detector.tagDeprecated(graph);
|
|
9394
|
+
const findings = detector.detect(graph, scope);
|
|
9395
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
9396
|
+
}
|
|
9397
|
+
// ── complexity_hotspots ────────────────────────────────────────────────
|
|
9398
|
+
case "complexity_hotspots": {
|
|
9399
|
+
const { computeComplexity: computeComplexity2 } = await Promise.resolve().then(() => (init_complexity(), complexity_exports));
|
|
9400
|
+
const scope = a.scope;
|
|
9401
|
+
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
9402
|
+
const hotspots = computeComplexity2(graph, scope).slice(0, limit);
|
|
9403
|
+
return { content: [{ type: "text", text: JSON.stringify({ hotspots, total: hotspots.length }, null, 2) }] };
|
|
9404
|
+
}
|
|
9405
|
+
// ── coverage_gaps ──────────────────────────────────────────────────────
|
|
9406
|
+
case "coverage_gaps": {
|
|
9407
|
+
const { computeCoverage: computeCoverage2 } = await Promise.resolve().then(() => (init_test_coverage(), test_coverage_exports));
|
|
9408
|
+
const scope = a.scope;
|
|
9409
|
+
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
9410
|
+
const summary = computeCoverage2(graph, scope);
|
|
9411
|
+
const untestedByRisk = summary.untestedByRisk.slice(0, limit);
|
|
9412
|
+
return {
|
|
9413
|
+
content: [{
|
|
9414
|
+
type: "text",
|
|
9415
|
+
text: JSON.stringify({
|
|
9416
|
+
untestedByRisk,
|
|
9417
|
+
coveragePct: summary.coveragePct,
|
|
9418
|
+
totalExported: summary.totalExported,
|
|
9419
|
+
testedExported: summary.testedExported
|
|
9420
|
+
}, null, 2)
|
|
9421
|
+
}]
|
|
9422
|
+
};
|
|
9423
|
+
}
|
|
9424
|
+
// ── secrets ────────────────────────────────────────────────────────────
|
|
9425
|
+
case "secrets": {
|
|
9426
|
+
const { SecretScanner: SecretScanner2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
9427
|
+
const scanner = new SecretScanner2();
|
|
9428
|
+
const scope = a.scope;
|
|
9429
|
+
const includeTestFiles = a.includeTestFiles ?? false;
|
|
9430
|
+
const findings = scanner.scan(graph, { scope, includeTestFiles });
|
|
9431
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
9432
|
+
}
|
|
9433
|
+
// ── vulnerability_scan ─────────────────────────────────────────────────
|
|
9434
|
+
case "vulnerability_scan": {
|
|
9435
|
+
const { VulnerabilityDetector: VulnerabilityDetector2 } = await Promise.resolve().then(() => (init_vulnerability_detector(), vulnerability_detector_exports));
|
|
9436
|
+
const detector = new VulnerabilityDetector2();
|
|
9437
|
+
const scope = a.scope;
|
|
9438
|
+
const types = a.types;
|
|
9439
|
+
const minSev = (a.severity ?? "LOW").toUpperCase();
|
|
9440
|
+
const sevRank = { HIGH: 3, MEDIUM: 2, LOW: 1 };
|
|
9441
|
+
const minRank = sevRank[minSev] ?? 1;
|
|
9442
|
+
let findings = detector.detect(graph, { scope, types });
|
|
9443
|
+
findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
|
|
9444
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
9445
|
+
}
|
|
8772
9446
|
default:
|
|
8773
9447
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
8774
9448
|
}
|
|
@@ -8862,7 +9536,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
8862
9536
|
var JobsDB = class {
|
|
8863
9537
|
db;
|
|
8864
9538
|
constructor(dbPath) {
|
|
8865
|
-
|
|
9539
|
+
fs24.mkdirSync(path31.dirname(dbPath), { recursive: true });
|
|
8866
9540
|
this.db = new Database3(dbPath);
|
|
8867
9541
|
this.db.pragma("journal_mode = WAL");
|
|
8868
9542
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -9004,7 +9678,7 @@ var JobsDB = class {
|
|
|
9004
9678
|
}
|
|
9005
9679
|
};
|
|
9006
9680
|
function getJobsDBPath() {
|
|
9007
|
-
return
|
|
9681
|
+
return path31.join(os13.homedir(), ".code-intel", "jobs.db");
|
|
9008
9682
|
}
|
|
9009
9683
|
var _jobsDB = null;
|
|
9010
9684
|
function getOrCreateJobsDB() {
|
|
@@ -9096,14 +9770,14 @@ var BACKUP_VERSION = "1.0";
|
|
|
9096
9770
|
var ALGORITHM = "aes-256-gcm";
|
|
9097
9771
|
var IV_LENGTH = 16;
|
|
9098
9772
|
function getBackupDir() {
|
|
9099
|
-
return
|
|
9773
|
+
return path31.join(os13.homedir(), ".code-intel", "backups");
|
|
9100
9774
|
}
|
|
9101
9775
|
function getBackupKey() {
|
|
9102
9776
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
9103
9777
|
if (keyHex && keyHex.length >= 64) {
|
|
9104
9778
|
return Buffer.from(keyHex.slice(0, 64), "hex");
|
|
9105
9779
|
}
|
|
9106
|
-
const seed = `code-intel-backup-${
|
|
9780
|
+
const seed = `code-intel-backup-${os13.hostname()}-${os13.homedir()}`;
|
|
9107
9781
|
return crypto5.createHash("sha256").update(seed).digest();
|
|
9108
9782
|
}
|
|
9109
9783
|
function encryptBuffer(data, key) {
|
|
@@ -9127,30 +9801,30 @@ var BackupService = class {
|
|
|
9127
9801
|
constructor(backupDir) {
|
|
9128
9802
|
this.backupDir = backupDir ?? getBackupDir();
|
|
9129
9803
|
this.key = getBackupKey();
|
|
9130
|
-
|
|
9804
|
+
fs24.mkdirSync(this.backupDir, { recursive: true });
|
|
9131
9805
|
}
|
|
9132
9806
|
/**
|
|
9133
9807
|
* Create a backup for a repository.
|
|
9134
9808
|
* Returns the backup entry.
|
|
9135
9809
|
*/
|
|
9136
9810
|
createBackup(repoPath) {
|
|
9137
|
-
const codeIntelDir =
|
|
9811
|
+
const codeIntelDir = path31.join(repoPath, ".code-intel");
|
|
9138
9812
|
const id = v4();
|
|
9139
9813
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9140
9814
|
const filesToBackup = [];
|
|
9141
9815
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
9142
9816
|
for (const f of candidates) {
|
|
9143
|
-
const fp =
|
|
9144
|
-
if (
|
|
9817
|
+
const fp = path31.join(codeIntelDir, f);
|
|
9818
|
+
if (fs24.existsSync(fp)) {
|
|
9145
9819
|
filesToBackup.push({ name: f, localPath: fp });
|
|
9146
9820
|
}
|
|
9147
9821
|
}
|
|
9148
|
-
const registryPath =
|
|
9149
|
-
if (
|
|
9822
|
+
const registryPath = path31.join(os13.homedir(), ".code-intel", "registry.json");
|
|
9823
|
+
if (fs24.existsSync(registryPath)) {
|
|
9150
9824
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
9151
9825
|
}
|
|
9152
|
-
const usersDbPath =
|
|
9153
|
-
if (
|
|
9826
|
+
const usersDbPath = path31.join(os13.homedir(), ".code-intel", "users.db");
|
|
9827
|
+
if (fs24.existsSync(usersDbPath)) {
|
|
9154
9828
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
9155
9829
|
}
|
|
9156
9830
|
if (filesToBackup.length === 0) {
|
|
@@ -9161,7 +9835,7 @@ var BackupService = class {
|
|
|
9161
9835
|
createdAt,
|
|
9162
9836
|
version: BACKUP_VERSION,
|
|
9163
9837
|
files: filesToBackup.map((f) => {
|
|
9164
|
-
const data =
|
|
9838
|
+
const data = fs24.readFileSync(f.localPath);
|
|
9165
9839
|
return {
|
|
9166
9840
|
name: f.name,
|
|
9167
9841
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -9175,7 +9849,7 @@ var BackupService = class {
|
|
|
9175
9849
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
9176
9850
|
parts.push(manifestLenBuf, manifestBuf);
|
|
9177
9851
|
for (const f of filesToBackup) {
|
|
9178
|
-
const data =
|
|
9852
|
+
const data = fs24.readFileSync(f.localPath);
|
|
9179
9853
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
9180
9854
|
const nameLenBuf = Buffer.alloc(2);
|
|
9181
9855
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -9186,8 +9860,8 @@ var BackupService = class {
|
|
|
9186
9860
|
const plaintext = Buffer.concat(parts);
|
|
9187
9861
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
9188
9862
|
const backupFileName = `backup-${id}.cib`;
|
|
9189
|
-
const backupPath =
|
|
9190
|
-
|
|
9863
|
+
const backupPath = path31.join(this.backupDir, backupFileName);
|
|
9864
|
+
fs24.writeFileSync(backupPath, encrypted);
|
|
9191
9865
|
const entry = {
|
|
9192
9866
|
id,
|
|
9193
9867
|
createdAt,
|
|
@@ -9214,9 +9888,9 @@ var BackupService = class {
|
|
|
9214
9888
|
async uploadToS3(entry) {
|
|
9215
9889
|
const cfg = getS3Config();
|
|
9216
9890
|
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.");
|
|
9217
|
-
const fileName =
|
|
9891
|
+
const fileName = path31.basename(entry.path);
|
|
9218
9892
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
9219
|
-
const body =
|
|
9893
|
+
const body = fs24.readFileSync(entry.path);
|
|
9220
9894
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
9221
9895
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
9222
9896
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -9233,8 +9907,8 @@ var BackupService = class {
|
|
|
9233
9907
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
9234
9908
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
9235
9909
|
}
|
|
9236
|
-
|
|
9237
|
-
|
|
9910
|
+
fs24.mkdirSync(path31.dirname(destPath), { recursive: true });
|
|
9911
|
+
fs24.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
9238
9912
|
}
|
|
9239
9913
|
/**
|
|
9240
9914
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -9280,10 +9954,10 @@ var BackupService = class {
|
|
|
9280
9954
|
if (!entry) {
|
|
9281
9955
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
9282
9956
|
}
|
|
9283
|
-
if (!
|
|
9957
|
+
if (!fs24.existsSync(entry.path)) {
|
|
9284
9958
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
9285
9959
|
}
|
|
9286
|
-
const encrypted =
|
|
9960
|
+
const encrypted = fs24.readFileSync(entry.path);
|
|
9287
9961
|
let plaintext;
|
|
9288
9962
|
try {
|
|
9289
9963
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -9297,8 +9971,8 @@ var BackupService = class {
|
|
|
9297
9971
|
offset += manifestLen;
|
|
9298
9972
|
const manifest = JSON.parse(manifestStr);
|
|
9299
9973
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
9300
|
-
const codeIntelDir =
|
|
9301
|
-
|
|
9974
|
+
const codeIntelDir = path31.join(restoreBase, ".code-intel");
|
|
9975
|
+
fs24.mkdirSync(codeIntelDir, { recursive: true });
|
|
9302
9976
|
for (const fileEntry of manifest.files) {
|
|
9303
9977
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
9304
9978
|
offset += 2;
|
|
@@ -9315,18 +9989,18 @@ var BackupService = class {
|
|
|
9315
9989
|
}
|
|
9316
9990
|
let destPath;
|
|
9317
9991
|
if (name === "registry.json" || name === "users.db") {
|
|
9318
|
-
destPath =
|
|
9992
|
+
destPath = path31.join(os13.homedir(), ".code-intel", name);
|
|
9319
9993
|
} else {
|
|
9320
|
-
destPath =
|
|
9994
|
+
destPath = path31.join(codeIntelDir, name);
|
|
9321
9995
|
}
|
|
9322
|
-
|
|
9996
|
+
fs24.writeFileSync(destPath, data);
|
|
9323
9997
|
}
|
|
9324
9998
|
}
|
|
9325
9999
|
/**
|
|
9326
10000
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
9327
10001
|
*/
|
|
9328
10002
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
9329
|
-
const entries = this._loadIndex().filter((e) =>
|
|
10003
|
+
const entries = this._loadIndex().filter((e) => fs24.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
9330
10004
|
const keep = /* @__PURE__ */ new Set();
|
|
9331
10005
|
const now = /* @__PURE__ */ new Date();
|
|
9332
10006
|
const dailyCutoff = new Date(now);
|
|
@@ -9356,7 +10030,7 @@ var BackupService = class {
|
|
|
9356
10030
|
for (const e of entries) {
|
|
9357
10031
|
if (!keep.has(e.id)) {
|
|
9358
10032
|
try {
|
|
9359
|
-
|
|
10033
|
+
fs24.unlinkSync(e.path);
|
|
9360
10034
|
deleted++;
|
|
9361
10035
|
} catch {
|
|
9362
10036
|
}
|
|
@@ -9368,17 +10042,17 @@ var BackupService = class {
|
|
|
9368
10042
|
}
|
|
9369
10043
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
9370
10044
|
_indexPath() {
|
|
9371
|
-
return
|
|
10045
|
+
return path31.join(this.backupDir, "index.json");
|
|
9372
10046
|
}
|
|
9373
10047
|
_loadIndex() {
|
|
9374
10048
|
try {
|
|
9375
|
-
return JSON.parse(
|
|
10049
|
+
return JSON.parse(fs24.readFileSync(this._indexPath(), "utf-8"));
|
|
9376
10050
|
} catch {
|
|
9377
10051
|
return [];
|
|
9378
10052
|
}
|
|
9379
10053
|
}
|
|
9380
10054
|
_saveIndex(entries) {
|
|
9381
|
-
|
|
10055
|
+
fs24.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
9382
10056
|
}
|
|
9383
10057
|
_appendIndex(entry) {
|
|
9384
10058
|
const entries = this._loadIndex();
|
|
@@ -10171,11 +10845,11 @@ var openApiSpec = {
|
|
|
10171
10845
|
};
|
|
10172
10846
|
|
|
10173
10847
|
// src/http/app.ts
|
|
10174
|
-
var __dirname$1 =
|
|
10848
|
+
var __dirname$1 = path31.dirname(fileURLToPath(import.meta.url));
|
|
10175
10849
|
var WEB_DIST = (() => {
|
|
10176
|
-
const bundled =
|
|
10177
|
-
if (
|
|
10178
|
-
return
|
|
10850
|
+
const bundled = path31.resolve(__dirname$1, "..", "web");
|
|
10851
|
+
if (fs24.existsSync(bundled)) return bundled;
|
|
10852
|
+
return path31.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
10179
10853
|
})();
|
|
10180
10854
|
function getAllowedOrigins() {
|
|
10181
10855
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -10706,8 +11380,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10706
11380
|
const registry = loadRegistry();
|
|
10707
11381
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
10708
11382
|
if (!entry) return null;
|
|
10709
|
-
const dbPath =
|
|
10710
|
-
if (!
|
|
11383
|
+
const dbPath = path31.join(entry.path, ".code-intel", "graph.db");
|
|
11384
|
+
if (!fs24.existsSync(dbPath)) return null;
|
|
10711
11385
|
const repoGraph = createKnowledgeGraph();
|
|
10712
11386
|
const db = new DbManager(dbPath);
|
|
10713
11387
|
try {
|
|
@@ -10794,7 +11468,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10794
11468
|
return;
|
|
10795
11469
|
}
|
|
10796
11470
|
try {
|
|
10797
|
-
const content =
|
|
11471
|
+
const content = fs24.readFileSync(file_path, "utf-8");
|
|
10798
11472
|
res.json({ content });
|
|
10799
11473
|
} catch {
|
|
10800
11474
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -11052,8 +11726,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11052
11726
|
for (const member of group.members) {
|
|
11053
11727
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
11054
11728
|
if (!regEntry) continue;
|
|
11055
|
-
const dbPath =
|
|
11056
|
-
if (!
|
|
11729
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
11730
|
+
if (!fs24.existsSync(dbPath)) continue;
|
|
11057
11731
|
const db = new DbManager(dbPath);
|
|
11058
11732
|
try {
|
|
11059
11733
|
await db.init();
|
|
@@ -11079,8 +11753,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11079
11753
|
let nodeCount = 0;
|
|
11080
11754
|
let edgeCount = 0;
|
|
11081
11755
|
if (regEntry) {
|
|
11082
|
-
const dbPath =
|
|
11083
|
-
if (
|
|
11756
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
11757
|
+
if (fs24.existsSync(dbPath)) {
|
|
11084
11758
|
try {
|
|
11085
11759
|
const db = new DbManager(dbPath);
|
|
11086
11760
|
await db.init();
|
|
@@ -11129,14 +11803,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11129
11803
|
});
|
|
11130
11804
|
return;
|
|
11131
11805
|
}
|
|
11132
|
-
let rawResolved =
|
|
11133
|
-
if (!
|
|
11134
|
-
rawResolved =
|
|
11806
|
+
let rawResolved = path31.normalize(file);
|
|
11807
|
+
if (!path31.isAbsolute(rawResolved) && workspaceRoot) {
|
|
11808
|
+
rawResolved = path31.join(workspaceRoot, rawResolved);
|
|
11135
11809
|
}
|
|
11136
|
-
const resolvedFile =
|
|
11810
|
+
const resolvedFile = path31.resolve(rawResolved);
|
|
11137
11811
|
function isInsideDir(fileAbs, dir) {
|
|
11138
|
-
const rel =
|
|
11139
|
-
return !rel.startsWith("..") && !
|
|
11812
|
+
const rel = path31.relative(path31.resolve(dir), fileAbs);
|
|
11813
|
+
return !rel.startsWith("..") && !path31.isAbsolute(rel);
|
|
11140
11814
|
}
|
|
11141
11815
|
if (workspaceRoot) {
|
|
11142
11816
|
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
@@ -11173,7 +11847,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11173
11847
|
}
|
|
11174
11848
|
let fileContent;
|
|
11175
11849
|
try {
|
|
11176
|
-
fileContent =
|
|
11850
|
+
fileContent = fs24.readFileSync(resolvedFile, "utf-8");
|
|
11177
11851
|
} catch {
|
|
11178
11852
|
res.status(404).json({
|
|
11179
11853
|
error: {
|
|
@@ -11204,7 +11878,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11204
11878
|
const contextStart = Math.max(1, startLine - 20);
|
|
11205
11879
|
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
11206
11880
|
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
11207
|
-
const ext =
|
|
11881
|
+
const ext = path31.extname(resolvedFile).toLowerCase();
|
|
11208
11882
|
const languageMap = {
|
|
11209
11883
|
".ts": "typescript",
|
|
11210
11884
|
".tsx": "typescript",
|
|
@@ -11339,10 +12013,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11339
12013
|
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() } });
|
|
11340
12014
|
}
|
|
11341
12015
|
});
|
|
11342
|
-
if (
|
|
12016
|
+
if (fs24.existsSync(WEB_DIST)) {
|
|
11343
12017
|
app.use(express.static(WEB_DIST));
|
|
11344
12018
|
app.get("/{*path}", (_req, res) => {
|
|
11345
|
-
res.sendFile(
|
|
12019
|
+
res.sendFile(path31.join(WEB_DIST, "index.html"));
|
|
11346
12020
|
});
|
|
11347
12021
|
}
|
|
11348
12022
|
app.use("/admin", requireRole("admin"));
|