@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/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';
|
|
@@ -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(os12.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(os12.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,6 +3702,550 @@ var init_gql_executor = __esm({
|
|
|
3702
3702
|
}
|
|
3703
3703
|
});
|
|
3704
3704
|
|
|
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
|
+
});
|
|
4248
|
+
|
|
3705
4249
|
// src/errors/codes.ts
|
|
3706
4250
|
var ErrorCodes, AppError;
|
|
3707
4251
|
var init_codes = __esm({
|
|
@@ -3735,10 +4279,10 @@ var init_codes = __esm({
|
|
|
3735
4279
|
}
|
|
3736
4280
|
});
|
|
3737
4281
|
function secureMkdir(dir) {
|
|
3738
|
-
|
|
4282
|
+
fs24.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
3739
4283
|
if (process.platform !== "win32") {
|
|
3740
4284
|
try {
|
|
3741
|
-
|
|
4285
|
+
fs24.chmodSync(dir, SECURE_DIR_MODE);
|
|
3742
4286
|
} catch {
|
|
3743
4287
|
}
|
|
3744
4288
|
}
|
|
@@ -3746,17 +4290,17 @@ function secureMkdir(dir) {
|
|
|
3746
4290
|
function secureChmodFile(file) {
|
|
3747
4291
|
if (process.platform === "win32") return;
|
|
3748
4292
|
try {
|
|
3749
|
-
|
|
4293
|
+
fs24.chmodSync(file, SECURE_FILE_MODE);
|
|
3750
4294
|
} catch {
|
|
3751
4295
|
}
|
|
3752
4296
|
}
|
|
3753
4297
|
function tightenDbFiles(dir) {
|
|
3754
4298
|
if (process.platform === "win32") return;
|
|
3755
|
-
if (!
|
|
3756
|
-
for (const name of
|
|
4299
|
+
if (!fs24.existsSync(dir)) return;
|
|
4300
|
+
for (const name of fs24.readdirSync(dir)) {
|
|
3757
4301
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
3758
4302
|
try {
|
|
3759
|
-
|
|
4303
|
+
fs24.chmodSync(path31.join(dir, name), SECURE_FILE_MODE);
|
|
3760
4304
|
} catch {
|
|
3761
4305
|
}
|
|
3762
4306
|
}
|
|
@@ -3770,7 +4314,7 @@ var init_fs_secure = __esm({
|
|
|
3770
4314
|
}
|
|
3771
4315
|
});
|
|
3772
4316
|
function getUsersDBPath() {
|
|
3773
|
-
return process.env["CODE_INTEL_USERS_DB_PATH"] ??
|
|
4317
|
+
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path31.join(os12.homedir(), ".code-intel", "users.db");
|
|
3774
4318
|
}
|
|
3775
4319
|
function getOrCreateUsersDB() {
|
|
3776
4320
|
if (!_usersDB) {
|
|
@@ -3786,7 +4330,7 @@ var init_users_db = __esm({
|
|
|
3786
4330
|
UsersDB = class {
|
|
3787
4331
|
db;
|
|
3788
4332
|
constructor(dbPath) {
|
|
3789
|
-
const dir =
|
|
4333
|
+
const dir = path31.dirname(dbPath);
|
|
3790
4334
|
secureMkdir(dir);
|
|
3791
4335
|
this.db = new Database3(dbPath);
|
|
3792
4336
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -4063,7 +4607,7 @@ function getScryptN() {
|
|
|
4063
4607
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
4064
4608
|
}
|
|
4065
4609
|
function getSecretsPath() {
|
|
4066
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ??
|
|
4610
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path31.join(os12.homedir(), ".code-intel", ".secrets");
|
|
4067
4611
|
}
|
|
4068
4612
|
function getMasterPassword() {
|
|
4069
4613
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
@@ -4094,8 +4638,8 @@ function decryptSecrets(encrypted) {
|
|
|
4094
4638
|
return JSON.parse(plaintext.toString("utf8"));
|
|
4095
4639
|
}
|
|
4096
4640
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
4097
|
-
if (!
|
|
4098
|
-
const blob =
|
|
4641
|
+
if (!fs24.existsSync(secretsPath)) return {};
|
|
4642
|
+
const blob = fs24.readFileSync(secretsPath);
|
|
4099
4643
|
return decryptSecrets(blob);
|
|
4100
4644
|
}
|
|
4101
4645
|
function getSecret(key, secretsPath = getSecretsPath()) {
|
|
@@ -4662,7 +5206,7 @@ init_shared();
|
|
|
4662
5206
|
init_shared();
|
|
4663
5207
|
init_typescript();
|
|
4664
5208
|
function resolveRelative(rawPath, fromFile, workspace) {
|
|
4665
|
-
const fromDir =
|
|
5209
|
+
const fromDir = path31.dirname(fromFile);
|
|
4666
5210
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
4667
5211
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
|
|
4668
5212
|
const resolved = workspace.resolve(fromDir, cleaned);
|
|
@@ -4714,7 +5258,7 @@ var pythonModule = {
|
|
|
4714
5258
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4715
5259
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
4716
5260
|
const parts = cleaned.split(".");
|
|
4717
|
-
const fromDir =
|
|
5261
|
+
const fromDir = path31.dirname(fromFile);
|
|
4718
5262
|
const relPath = parts.join("/");
|
|
4719
5263
|
for (const suffix of ["/__init__.py", ".py"]) {
|
|
4720
5264
|
const r = workspace.resolve(fromDir, relPath + suffix);
|
|
@@ -4793,7 +5337,7 @@ var cModule = {
|
|
|
4793
5337
|
inheritanceStrategy: "none",
|
|
4794
5338
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4795
5339
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4796
|
-
const fromDir =
|
|
5340
|
+
const fromDir = path31.dirname(fromFile);
|
|
4797
5341
|
return workspace.resolve(fromDir, cleaned);
|
|
4798
5342
|
},
|
|
4799
5343
|
isExported(_node) {
|
|
@@ -4816,7 +5360,7 @@ var cppModule = {
|
|
|
4816
5360
|
inheritanceStrategy: "depth-first",
|
|
4817
5361
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4818
5362
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4819
|
-
const fromDir =
|
|
5363
|
+
const fromDir = path31.dirname(fromFile);
|
|
4820
5364
|
return workspace.resolve(fromDir, cleaned);
|
|
4821
5365
|
},
|
|
4822
5366
|
isExported(_node) {
|
|
@@ -4978,7 +5522,7 @@ var dartModule = {
|
|
|
4978
5522
|
const pkg = cleaned.replace("package:", "");
|
|
4979
5523
|
return workspace.findByPackage(pkg);
|
|
4980
5524
|
}
|
|
4981
|
-
const fromDir =
|
|
5525
|
+
const fromDir = path31.dirname(fromFile);
|
|
4982
5526
|
return workspace.resolve(fromDir, cleaned);
|
|
4983
5527
|
},
|
|
4984
5528
|
isExported(node) {
|
|
@@ -5333,25 +5877,25 @@ function validateDAG(phases) {
|
|
|
5333
5877
|
const visiting = /* @__PURE__ */ new Set();
|
|
5334
5878
|
const visited = /* @__PURE__ */ new Set();
|
|
5335
5879
|
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
5336
|
-
function dfs(name,
|
|
5880
|
+
function dfs(name, path32) {
|
|
5337
5881
|
if (visiting.has(name)) {
|
|
5338
|
-
const cycleStart =
|
|
5339
|
-
const cycle =
|
|
5882
|
+
const cycleStart = path32.indexOf(name);
|
|
5883
|
+
const cycle = path32.slice(cycleStart).concat(name);
|
|
5340
5884
|
errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
5341
5885
|
return true;
|
|
5342
5886
|
}
|
|
5343
5887
|
if (visited.has(name)) return false;
|
|
5344
5888
|
visiting.add(name);
|
|
5345
|
-
|
|
5889
|
+
path32.push(name);
|
|
5346
5890
|
const phase = phaseMap.get(name);
|
|
5347
5891
|
if (phase) {
|
|
5348
5892
|
for (const dep of phase.dependencies) {
|
|
5349
|
-
if (dfs(dep,
|
|
5893
|
+
if (dfs(dep, path32)) return true;
|
|
5350
5894
|
}
|
|
5351
5895
|
}
|
|
5352
5896
|
visiting.delete(name);
|
|
5353
5897
|
visited.add(name);
|
|
5354
|
-
|
|
5898
|
+
path32.pop();
|
|
5355
5899
|
return false;
|
|
5356
5900
|
}
|
|
5357
5901
|
for (const phase of phases) {
|
|
@@ -5564,7 +6108,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
5564
6108
|
]);
|
|
5565
6109
|
function loadIgnorePatterns(workspaceRoot) {
|
|
5566
6110
|
try {
|
|
5567
|
-
const raw =
|
|
6111
|
+
const raw = fs24.readFileSync(path31.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
5568
6112
|
const extras = /* @__PURE__ */ new Set();
|
|
5569
6113
|
for (const line of raw.split("\n")) {
|
|
5570
6114
|
const trimmed = line.trim();
|
|
@@ -5588,7 +6132,7 @@ var scanPhase = {
|
|
|
5588
6132
|
function walk(dir) {
|
|
5589
6133
|
let entries;
|
|
5590
6134
|
try {
|
|
5591
|
-
entries =
|
|
6135
|
+
entries = fs24.readdirSync(dir, { withFileTypes: true });
|
|
5592
6136
|
} catch {
|
|
5593
6137
|
return;
|
|
5594
6138
|
}
|
|
@@ -5597,15 +6141,15 @@ var scanPhase = {
|
|
|
5597
6141
|
if (entry.name.startsWith(".")) continue;
|
|
5598
6142
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5599
6143
|
if (extraIgnore.has(entry.name)) continue;
|
|
5600
|
-
walk(
|
|
6144
|
+
walk(path31.join(dir, entry.name));
|
|
5601
6145
|
} else if (entry.isFile()) {
|
|
5602
6146
|
const name = entry.name;
|
|
5603
6147
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
5604
|
-
const ext =
|
|
6148
|
+
const ext = path31.extname(name);
|
|
5605
6149
|
if (!extensions.has(ext)) continue;
|
|
5606
|
-
const fullPath =
|
|
6150
|
+
const fullPath = path31.join(dir, name);
|
|
5607
6151
|
try {
|
|
5608
|
-
const stat =
|
|
6152
|
+
const stat = fs24.statSync(fullPath);
|
|
5609
6153
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
5610
6154
|
} catch {
|
|
5611
6155
|
continue;
|
|
@@ -5632,20 +6176,20 @@ var structurePhase = {
|
|
|
5632
6176
|
const dirs = /* @__PURE__ */ new Set();
|
|
5633
6177
|
let structDone = 0;
|
|
5634
6178
|
for (const filePath of context2.filePaths) {
|
|
5635
|
-
const relativePath =
|
|
6179
|
+
const relativePath = path31.relative(context2.workspaceRoot, filePath);
|
|
5636
6180
|
const lang = detectLanguage(filePath);
|
|
5637
6181
|
context2.graph.addNode({
|
|
5638
6182
|
id: generateNodeId("file", relativePath, relativePath),
|
|
5639
6183
|
kind: "file",
|
|
5640
|
-
name:
|
|
6184
|
+
name: path31.basename(filePath),
|
|
5641
6185
|
filePath: relativePath,
|
|
5642
6186
|
metadata: lang ? { language: lang } : void 0
|
|
5643
6187
|
});
|
|
5644
|
-
let dir =
|
|
6188
|
+
let dir = path31.dirname(relativePath);
|
|
5645
6189
|
while (dir && dir !== "." && dir !== "") {
|
|
5646
6190
|
if (dirs.has(dir)) break;
|
|
5647
6191
|
dirs.add(dir);
|
|
5648
|
-
dir =
|
|
6192
|
+
dir = path31.dirname(dir);
|
|
5649
6193
|
}
|
|
5650
6194
|
structDone++;
|
|
5651
6195
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -5654,7 +6198,7 @@ var structurePhase = {
|
|
|
5654
6198
|
context2.graph.addNode({
|
|
5655
6199
|
id: generateNodeId("directory", dir, dir),
|
|
5656
6200
|
kind: "directory",
|
|
5657
|
-
name:
|
|
6201
|
+
name: path31.basename(dir),
|
|
5658
6202
|
filePath: dir
|
|
5659
6203
|
});
|
|
5660
6204
|
}
|
|
@@ -5741,9 +6285,9 @@ var flowPhase = {
|
|
|
5741
6285
|
for (const node of graph.allNodes()) {
|
|
5742
6286
|
if (!["function", "method"].includes(node.kind)) continue;
|
|
5743
6287
|
let score = 0;
|
|
5744
|
-
const
|
|
6288
|
+
const hasCallers2 = calledNodes.has(node.id);
|
|
5745
6289
|
const outCalls = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "calls");
|
|
5746
|
-
if (!
|
|
6290
|
+
if (!hasCallers2 && outCalls.length > 0) score += 10;
|
|
5747
6291
|
if (node.exported) score += 5;
|
|
5748
6292
|
if (/^(main|handle|init|start|run|execute|process|serve|listen|bootstrap)/.test(node.name)) score += 3;
|
|
5749
6293
|
if (node.filePath.includes("test") || node.filePath.includes("spec") || node.filePath.includes("__test")) score -= 20;
|
|
@@ -5765,22 +6309,22 @@ var flowPhase = {
|
|
|
5765
6309
|
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
5766
6310
|
const visited = /* @__PURE__ */ new Set();
|
|
5767
6311
|
while (queue.length > 0 && flowCount < maxFlows) {
|
|
5768
|
-
const { nodeId, path:
|
|
5769
|
-
if (
|
|
6312
|
+
const { nodeId, path: path32 } = queue.shift();
|
|
6313
|
+
if (path32.length > maxDepth) continue;
|
|
5770
6314
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
5771
|
-
if (callEdges.length === 0 &&
|
|
6315
|
+
if (callEdges.length === 0 && path32.length >= 3) {
|
|
5772
6316
|
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
5773
6317
|
graph.addNode({
|
|
5774
6318
|
id: flowId,
|
|
5775
6319
|
kind: "flow",
|
|
5776
6320
|
name: `${ep.name} flow ${flowCount}`,
|
|
5777
6321
|
filePath: ep.filePath,
|
|
5778
|
-
metadata: { steps:
|
|
6322
|
+
metadata: { steps: path32, entryPoint: ep.name }
|
|
5779
6323
|
});
|
|
5780
|
-
for (let i = 0; i <
|
|
6324
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5781
6325
|
graph.addEdge({
|
|
5782
|
-
id: generateEdgeId(
|
|
5783
|
-
source:
|
|
6326
|
+
id: generateEdgeId(path32[i], flowId, `step_of_${i}`),
|
|
6327
|
+
source: path32[i],
|
|
5784
6328
|
target: flowId,
|
|
5785
6329
|
kind: "step_of",
|
|
5786
6330
|
weight: 1,
|
|
@@ -5793,7 +6337,7 @@ var flowPhase = {
|
|
|
5793
6337
|
for (const edge of callEdges) {
|
|
5794
6338
|
if (visited.has(edge.target)) continue;
|
|
5795
6339
|
visited.add(edge.target);
|
|
5796
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6340
|
+
queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
|
|
5797
6341
|
}
|
|
5798
6342
|
}
|
|
5799
6343
|
}
|
|
@@ -5811,7 +6355,7 @@ var LLMGovernanceLogger = class {
|
|
|
5811
6355
|
}
|
|
5812
6356
|
/** Path to the JSONL log file. */
|
|
5813
6357
|
getLogPath() {
|
|
5814
|
-
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ??
|
|
6358
|
+
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path31.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
|
|
5815
6359
|
}
|
|
5816
6360
|
/**
|
|
5817
6361
|
* Append an entry to the governance log.
|
|
@@ -5827,8 +6371,8 @@ var LLMGovernanceLogger = class {
|
|
|
5827
6371
|
...entry
|
|
5828
6372
|
};
|
|
5829
6373
|
const logPath = this.getLogPath();
|
|
5830
|
-
|
|
5831
|
-
|
|
6374
|
+
fs24.mkdirSync(path31.dirname(logPath), { recursive: true });
|
|
6375
|
+
fs24.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
5832
6376
|
} catch {
|
|
5833
6377
|
}
|
|
5834
6378
|
}
|
|
@@ -5838,7 +6382,7 @@ var LLMGovernanceLogger = class {
|
|
|
5838
6382
|
*/
|
|
5839
6383
|
readLog(limit = 100) {
|
|
5840
6384
|
try {
|
|
5841
|
-
const raw =
|
|
6385
|
+
const raw = fs24.readFileSync(this.getLogPath(), "utf-8");
|
|
5842
6386
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
5843
6387
|
return lines.map((l) => JSON.parse(l));
|
|
5844
6388
|
} catch {
|
|
@@ -5982,17 +6526,17 @@ function traceFlow(entryId, graph, maxDepth = 10, maxBranching = 4) {
|
|
|
5982
6526
|
const queue = [{ nodeId: entryId, path: [entryId] }];
|
|
5983
6527
|
const visited = /* @__PURE__ */ new Set();
|
|
5984
6528
|
while (queue.length > 0 && flows.length < maxFlows) {
|
|
5985
|
-
const { nodeId, path:
|
|
5986
|
-
if (
|
|
6529
|
+
const { nodeId, path: path32 } = queue.shift();
|
|
6530
|
+
if (path32.length > maxDepth) continue;
|
|
5987
6531
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
5988
|
-
if (callEdges.length === 0 &&
|
|
5989
|
-
flows.push({ entryPointId: entryId, steps: [...
|
|
6532
|
+
if (callEdges.length === 0 && path32.length >= 3) {
|
|
6533
|
+
flows.push({ entryPointId: entryId, steps: [...path32] });
|
|
5990
6534
|
continue;
|
|
5991
6535
|
}
|
|
5992
6536
|
for (const edge of callEdges) {
|
|
5993
6537
|
if (visited.has(edge.target)) continue;
|
|
5994
6538
|
visited.add(edge.target);
|
|
5995
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6539
|
+
queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
|
|
5996
6540
|
}
|
|
5997
6541
|
}
|
|
5998
6542
|
}
|
|
@@ -6226,7 +6770,7 @@ init_embedder();
|
|
|
6226
6770
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6227
6771
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
6228
6772
|
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6229
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
6773
|
+
const hasVectorDb = Boolean(vectorDbPath && fs24.existsSync(vectorDbPath));
|
|
6230
6774
|
if (!hasVectorDb) {
|
|
6231
6775
|
const bm25Results2 = await bm25Promise;
|
|
6232
6776
|
return {
|
|
@@ -6283,7 +6827,7 @@ var DbManager = class {
|
|
|
6283
6827
|
this.dbPath = dbPath;
|
|
6284
6828
|
}
|
|
6285
6829
|
async init() {
|
|
6286
|
-
|
|
6830
|
+
fs24.mkdirSync(path31.dirname(this.dbPath), { recursive: true });
|
|
6287
6831
|
this.db = new Database(this.dbPath);
|
|
6288
6832
|
await this.db.init();
|
|
6289
6833
|
this.conn = new Connection(this.db);
|
|
@@ -6373,7 +6917,7 @@ function getCreateEdgeTableDDL() {
|
|
|
6373
6917
|
)`];
|
|
6374
6918
|
}
|
|
6375
6919
|
function writeNodeCSVs(graph, outputDir) {
|
|
6376
|
-
|
|
6920
|
+
fs24.mkdirSync(outputDir, { recursive: true });
|
|
6377
6921
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
6378
6922
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
6379
6923
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -6381,7 +6925,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
6381
6925
|
const table = NODE_TABLE_MAP[node.kind];
|
|
6382
6926
|
if (!tableBuffers.has(table)) {
|
|
6383
6927
|
tableBuffers.set(table, [header]);
|
|
6384
|
-
tableFilePaths.set(table,
|
|
6928
|
+
tableFilePaths.set(table, path31.join(outputDir, `${table}.csv`));
|
|
6385
6929
|
}
|
|
6386
6930
|
tableBuffers.get(table).push(
|
|
6387
6931
|
csvRow([
|
|
@@ -6401,12 +6945,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
6401
6945
|
);
|
|
6402
6946
|
}
|
|
6403
6947
|
for (const [table, lines] of tableBuffers) {
|
|
6404
|
-
|
|
6948
|
+
fs24.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
6405
6949
|
}
|
|
6406
6950
|
return tableFilePaths;
|
|
6407
6951
|
}
|
|
6408
6952
|
function writeEdgeCSV(graph, outputDir) {
|
|
6409
|
-
|
|
6953
|
+
fs24.mkdirSync(outputDir, { recursive: true });
|
|
6410
6954
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
6411
6955
|
const groups = /* @__PURE__ */ new Map();
|
|
6412
6956
|
for (const edge of graph.allEdges()) {
|
|
@@ -6417,7 +6961,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
6417
6961
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
6418
6962
|
const key = `${fromTable}->${toTable}`;
|
|
6419
6963
|
if (!groups.has(key)) {
|
|
6420
|
-
const filePath =
|
|
6964
|
+
const filePath = path31.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
6421
6965
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
6422
6966
|
}
|
|
6423
6967
|
groups.get(key).lines.push(
|
|
@@ -6432,7 +6976,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
6432
6976
|
}
|
|
6433
6977
|
const result = [];
|
|
6434
6978
|
for (const group of groups.values()) {
|
|
6435
|
-
|
|
6979
|
+
fs24.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
6436
6980
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
6437
6981
|
}
|
|
6438
6982
|
return result;
|
|
@@ -6460,7 +7004,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6460
7004
|
} catch {
|
|
6461
7005
|
}
|
|
6462
7006
|
}
|
|
6463
|
-
const tmpDir =
|
|
7007
|
+
const tmpDir = fs24.mkdtempSync(path31.join(os12.tmpdir(), "code-intel-csv-"));
|
|
6464
7008
|
try {
|
|
6465
7009
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
6466
7010
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -6479,8 +7023,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6479
7023
|
}
|
|
6480
7024
|
let nodeCount = 0;
|
|
6481
7025
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
6482
|
-
if (!
|
|
6483
|
-
const stat =
|
|
7026
|
+
if (!fs24.existsSync(csvPath)) continue;
|
|
7027
|
+
const stat = fs24.statSync(csvPath);
|
|
6484
7028
|
if (stat.size < 50) continue;
|
|
6485
7029
|
try {
|
|
6486
7030
|
await dbManager.execute(
|
|
@@ -6493,8 +7037,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6493
7037
|
}
|
|
6494
7038
|
let edgeCount = 0;
|
|
6495
7039
|
for (const group of edgeGroups) {
|
|
6496
|
-
if (!
|
|
6497
|
-
const stat =
|
|
7040
|
+
if (!fs24.existsSync(group.filePath)) continue;
|
|
7041
|
+
const stat = fs24.statSync(group.filePath);
|
|
6498
7042
|
if (stat.size < 50) continue;
|
|
6499
7043
|
try {
|
|
6500
7044
|
await dbManager.execute(
|
|
@@ -6508,7 +7052,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6508
7052
|
return { nodeCount, edgeCount };
|
|
6509
7053
|
} finally {
|
|
6510
7054
|
try {
|
|
6511
|
-
|
|
7055
|
+
fs24.rmSync(tmpDir, { recursive: true, force: true });
|
|
6512
7056
|
} catch {
|
|
6513
7057
|
}
|
|
6514
7058
|
}
|
|
@@ -6560,19 +7104,19 @@ function buildNodeProps(node) {
|
|
|
6560
7104
|
function escCypher(s) {
|
|
6561
7105
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
6562
7106
|
}
|
|
6563
|
-
var GLOBAL_DIR =
|
|
6564
|
-
var REPOS_FILE =
|
|
7107
|
+
var GLOBAL_DIR = path31.join(os12.homedir(), ".code-intel");
|
|
7108
|
+
var REPOS_FILE = path31.join(GLOBAL_DIR, "repos.json");
|
|
6565
7109
|
function loadRegistry() {
|
|
6566
7110
|
try {
|
|
6567
|
-
const data =
|
|
7111
|
+
const data = fs24.readFileSync(REPOS_FILE, "utf-8");
|
|
6568
7112
|
return JSON.parse(data);
|
|
6569
7113
|
} catch {
|
|
6570
7114
|
return [];
|
|
6571
7115
|
}
|
|
6572
7116
|
}
|
|
6573
7117
|
function saveRegistry(entries) {
|
|
6574
|
-
|
|
6575
|
-
|
|
7118
|
+
fs24.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
7119
|
+
fs24.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
6576
7120
|
}
|
|
6577
7121
|
function upsertRepo(entry) {
|
|
6578
7122
|
const entries = loadRegistry();
|
|
@@ -6589,23 +7133,23 @@ function removeRepo(repoPath) {
|
|
|
6589
7133
|
saveRegistry(entries);
|
|
6590
7134
|
}
|
|
6591
7135
|
function saveMetadata(repoDir, metadata) {
|
|
6592
|
-
const metaDir =
|
|
6593
|
-
|
|
6594
|
-
|
|
7136
|
+
const metaDir = path31.join(repoDir, ".code-intel");
|
|
7137
|
+
fs24.mkdirSync(metaDir, { recursive: true });
|
|
7138
|
+
fs24.writeFileSync(path31.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
6595
7139
|
}
|
|
6596
7140
|
function loadMetadata(repoDir) {
|
|
6597
7141
|
try {
|
|
6598
|
-
const data =
|
|
7142
|
+
const data = fs24.readFileSync(path31.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
6599
7143
|
return JSON.parse(data);
|
|
6600
7144
|
} catch {
|
|
6601
7145
|
return null;
|
|
6602
7146
|
}
|
|
6603
7147
|
}
|
|
6604
7148
|
function getDbPath(repoDir) {
|
|
6605
|
-
return
|
|
7149
|
+
return path31.join(repoDir, ".code-intel", "graph.db");
|
|
6606
7150
|
}
|
|
6607
7151
|
function getVectorDbPath(repoDir) {
|
|
6608
|
-
return
|
|
7152
|
+
return path31.join(repoDir, ".code-intel", "vector.db");
|
|
6609
7153
|
}
|
|
6610
7154
|
|
|
6611
7155
|
// src/mcp-server/server.ts
|
|
@@ -6689,12 +7233,12 @@ function scanForFiles(root, matcher, maxDepth = 2) {
|
|
|
6689
7233
|
if (depth > maxDepth) return;
|
|
6690
7234
|
let entries;
|
|
6691
7235
|
try {
|
|
6692
|
-
entries =
|
|
7236
|
+
entries = fs24.readdirSync(dir, { withFileTypes: true });
|
|
6693
7237
|
} catch {
|
|
6694
7238
|
return;
|
|
6695
7239
|
}
|
|
6696
7240
|
for (const entry of entries) {
|
|
6697
|
-
const full =
|
|
7241
|
+
const full = path31.join(dir, entry.name);
|
|
6698
7242
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
6699
7243
|
walk(full, depth + 1);
|
|
6700
7244
|
} else if (entry.isFile() && matcher(entry.name)) {
|
|
@@ -6717,8 +7261,8 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
6717
7261
|
]);
|
|
6718
7262
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
6719
7263
|
function tryParseFile(filePath) {
|
|
6720
|
-
const ext =
|
|
6721
|
-
const content =
|
|
7264
|
+
const ext = path31.extname(filePath).toLowerCase();
|
|
7265
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6722
7266
|
if (ext === ".json") {
|
|
6723
7267
|
try {
|
|
6724
7268
|
return JSON.parse(content);
|
|
@@ -6772,7 +7316,7 @@ async function parseGraphQLContracts(repoRoot) {
|
|
|
6772
7316
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
6773
7317
|
const contracts = [];
|
|
6774
7318
|
for (const filePath of files) {
|
|
6775
|
-
const content =
|
|
7319
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6776
7320
|
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
6777
7321
|
let match;
|
|
6778
7322
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
@@ -6803,7 +7347,7 @@ async function parseProtoContracts(repoRoot) {
|
|
|
6803
7347
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
6804
7348
|
const contracts = [];
|
|
6805
7349
|
for (const filePath of files) {
|
|
6806
|
-
const content =
|
|
7350
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6807
7351
|
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
6808
7352
|
let serviceMatch;
|
|
6809
7353
|
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
@@ -7008,8 +7552,8 @@ async function syncGroup(group) {
|
|
|
7008
7552
|
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
7009
7553
|
continue;
|
|
7010
7554
|
}
|
|
7011
|
-
const dbPath =
|
|
7012
|
-
if (!
|
|
7555
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
7556
|
+
if (!fs24.existsSync(dbPath)) {
|
|
7013
7557
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
7014
7558
|
continue;
|
|
7015
7559
|
}
|
|
@@ -7082,8 +7626,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7082
7626
|
for (const member of group.members) {
|
|
7083
7627
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7084
7628
|
if (!regEntry) continue;
|
|
7085
|
-
const dbPath =
|
|
7086
|
-
if (!
|
|
7629
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
7630
|
+
if (!fs24.existsSync(dbPath)) continue;
|
|
7087
7631
|
const graph = createKnowledgeGraph();
|
|
7088
7632
|
const db = new DbManager(dbPath);
|
|
7089
7633
|
try {
|
|
@@ -7583,22 +8127,22 @@ function suggestTests(graph, symbolName) {
|
|
|
7583
8127
|
const callPaths = [];
|
|
7584
8128
|
const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
|
|
7585
8129
|
while (pathQueue.length > 0 && callPaths.length < 5) {
|
|
7586
|
-
const { id, path:
|
|
7587
|
-
let
|
|
8130
|
+
const { id, path: path32, depth } = pathQueue.shift();
|
|
8131
|
+
let hasCallers2 = false;
|
|
7588
8132
|
for (const edge of graph.findEdgesTo(id)) {
|
|
7589
8133
|
if (edge.kind !== "calls") continue;
|
|
7590
8134
|
const callerNode = graph.getNode(edge.source);
|
|
7591
8135
|
if (!callerNode) continue;
|
|
7592
|
-
|
|
7593
|
-
const newPath = [callerNode.name, ...
|
|
8136
|
+
hasCallers2 = true;
|
|
8137
|
+
const newPath = [callerNode.name, ...path32];
|
|
7594
8138
|
if (depth + 1 >= 3 || callPaths.length >= 5) {
|
|
7595
8139
|
if (callPaths.length < 5) callPaths.push(newPath);
|
|
7596
8140
|
continue;
|
|
7597
8141
|
}
|
|
7598
8142
|
pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
|
|
7599
8143
|
}
|
|
7600
|
-
if (!
|
|
7601
|
-
callPaths.push(
|
|
8144
|
+
if (!hasCallers2 && path32.length > 1) {
|
|
8145
|
+
callPaths.push(path32);
|
|
7602
8146
|
}
|
|
7603
8147
|
}
|
|
7604
8148
|
if (callPaths.length === 0) {
|
|
@@ -8073,6 +8617,70 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8073
8617
|
},
|
|
8074
8618
|
required: ["cluster"]
|
|
8075
8619
|
}
|
|
8620
|
+
},
|
|
8621
|
+
{
|
|
8622
|
+
name: "deprecated_usage",
|
|
8623
|
+
description: "Find usages of deprecated APIs in the codebase",
|
|
8624
|
+
inputSchema: {
|
|
8625
|
+
type: "object",
|
|
8626
|
+
properties: {
|
|
8627
|
+
scope: { type: "string", description: "Directory scope filter" },
|
|
8628
|
+
..._tokenProp
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
},
|
|
8632
|
+
{
|
|
8633
|
+
name: "complexity_hotspots",
|
|
8634
|
+
description: "Ranked list of functions/methods by cyclomatic complexity. Useful for identifying refactoring candidates.",
|
|
8635
|
+
inputSchema: {
|
|
8636
|
+
type: "object",
|
|
8637
|
+
properties: {
|
|
8638
|
+
scope: { type: "string", description: "Limit to a file path prefix (optional)" },
|
|
8639
|
+
limit: { type: "number", description: "Maximum number of results (default: 20)" },
|
|
8640
|
+
..._tokenProp
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
},
|
|
8644
|
+
{
|
|
8645
|
+
name: "coverage_gaps",
|
|
8646
|
+
description: "Find exported symbols with no test coverage, ranked by blast radius. Useful for prioritizing test writing.",
|
|
8647
|
+
inputSchema: {
|
|
8648
|
+
type: "object",
|
|
8649
|
+
properties: {
|
|
8650
|
+
scope: { type: "string", description: "Limit to a file path prefix (optional)" },
|
|
8651
|
+
limit: { type: "number", description: "Maximum number of untested results to return (default: 20)" },
|
|
8652
|
+
..._tokenProp
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
},
|
|
8656
|
+
{
|
|
8657
|
+
name: "secrets",
|
|
8658
|
+
description: "Scan the knowledge graph for hardcoded secrets: API keys, passwords, tokens, private keys, high-entropy strings",
|
|
8659
|
+
inputSchema: {
|
|
8660
|
+
type: "object",
|
|
8661
|
+
properties: {
|
|
8662
|
+
scope: { type: "string", description: "Limit scan to files under this path prefix" },
|
|
8663
|
+
includeTestFiles: { type: "boolean", description: "Include test/spec/fixture files (default: false)" },
|
|
8664
|
+
..._tokenProp
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
},
|
|
8668
|
+
{
|
|
8669
|
+
name: "vulnerability_scan",
|
|
8670
|
+
description: "Scan the knowledge graph for OWASP vulnerabilities: SQL injection, XSS, SSRF, path traversal, command injection",
|
|
8671
|
+
inputSchema: {
|
|
8672
|
+
type: "object",
|
|
8673
|
+
properties: {
|
|
8674
|
+
scope: { type: "string", description: "Limit scan to files under this path prefix" },
|
|
8675
|
+
types: {
|
|
8676
|
+
type: "array",
|
|
8677
|
+
items: { type: "string", enum: ["SQL_INJECTION", "XSS", "SSRF", "PATH_TRAVERSAL", "COMMAND_INJECTION"] },
|
|
8678
|
+
description: "Vulnerability types to detect (default: all)"
|
|
8679
|
+
},
|
|
8680
|
+
severity: { type: "string", description: "Minimum severity to report: HIGH|MEDIUM|LOW (default: LOW)" },
|
|
8681
|
+
..._tokenProp
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8076
8684
|
}
|
|
8077
8685
|
]
|
|
8078
8686
|
}));
|
|
@@ -8496,7 +9104,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
8496
9104
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
8497
9105
|
for (const node of graph.allNodes()) {
|
|
8498
9106
|
if (!node.filePath) continue;
|
|
8499
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
9107
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path31.sep, "");
|
|
8500
9108
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
8501
9109
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
8502
9110
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -8769,6 +9377,63 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
8769
9377
|
const result = summarizeCluster(graph, cluster);
|
|
8770
9378
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
8771
9379
|
}
|
|
9380
|
+
case "deprecated_usage": {
|
|
9381
|
+
const scope = a.scope;
|
|
9382
|
+
const { DeprecatedDetector: DeprecatedDetector2 } = await Promise.resolve().then(() => (init_deprecated_detector(), deprecated_detector_exports));
|
|
9383
|
+
const detector = new DeprecatedDetector2();
|
|
9384
|
+
detector.tagDeprecated(graph);
|
|
9385
|
+
const findings = detector.detect(graph, scope);
|
|
9386
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
9387
|
+
}
|
|
9388
|
+
// ── complexity_hotspots ────────────────────────────────────────────────
|
|
9389
|
+
case "complexity_hotspots": {
|
|
9390
|
+
const { computeComplexity: computeComplexity2 } = await Promise.resolve().then(() => (init_complexity(), complexity_exports));
|
|
9391
|
+
const scope = a.scope;
|
|
9392
|
+
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
9393
|
+
const hotspots = computeComplexity2(graph, scope).slice(0, limit);
|
|
9394
|
+
return { content: [{ type: "text", text: JSON.stringify({ hotspots, total: hotspots.length }, null, 2) }] };
|
|
9395
|
+
}
|
|
9396
|
+
// ── coverage_gaps ──────────────────────────────────────────────────────
|
|
9397
|
+
case "coverage_gaps": {
|
|
9398
|
+
const { computeCoverage: computeCoverage2 } = await Promise.resolve().then(() => (init_test_coverage(), test_coverage_exports));
|
|
9399
|
+
const scope = a.scope;
|
|
9400
|
+
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
9401
|
+
const summary = computeCoverage2(graph, scope);
|
|
9402
|
+
const untestedByRisk = summary.untestedByRisk.slice(0, limit);
|
|
9403
|
+
return {
|
|
9404
|
+
content: [{
|
|
9405
|
+
type: "text",
|
|
9406
|
+
text: JSON.stringify({
|
|
9407
|
+
untestedByRisk,
|
|
9408
|
+
coveragePct: summary.coveragePct,
|
|
9409
|
+
totalExported: summary.totalExported,
|
|
9410
|
+
testedExported: summary.testedExported
|
|
9411
|
+
}, null, 2)
|
|
9412
|
+
}]
|
|
9413
|
+
};
|
|
9414
|
+
}
|
|
9415
|
+
// ── secrets ────────────────────────────────────────────────────────────
|
|
9416
|
+
case "secrets": {
|
|
9417
|
+
const { SecretScanner: SecretScanner2 } = await Promise.resolve().then(() => (init_secret_scanner(), secret_scanner_exports));
|
|
9418
|
+
const scanner = new SecretScanner2();
|
|
9419
|
+
const scope = a.scope;
|
|
9420
|
+
const includeTestFiles = a.includeTestFiles ?? false;
|
|
9421
|
+
const findings = scanner.scan(graph, { scope, includeTestFiles });
|
|
9422
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
9423
|
+
}
|
|
9424
|
+
// ── vulnerability_scan ─────────────────────────────────────────────────
|
|
9425
|
+
case "vulnerability_scan": {
|
|
9426
|
+
const { VulnerabilityDetector: VulnerabilityDetector2 } = await Promise.resolve().then(() => (init_vulnerability_detector(), vulnerability_detector_exports));
|
|
9427
|
+
const detector = new VulnerabilityDetector2();
|
|
9428
|
+
const scope = a.scope;
|
|
9429
|
+
const types = a.types;
|
|
9430
|
+
const minSev = (a.severity ?? "LOW").toUpperCase();
|
|
9431
|
+
const sevRank = { HIGH: 3, MEDIUM: 2, LOW: 1 };
|
|
9432
|
+
const minRank = sevRank[minSev] ?? 1;
|
|
9433
|
+
let findings = detector.detect(graph, { scope, types });
|
|
9434
|
+
findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
|
|
9435
|
+
return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
|
|
9436
|
+
}
|
|
8772
9437
|
default:
|
|
8773
9438
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
8774
9439
|
}
|
|
@@ -8862,7 +9527,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
8862
9527
|
var JobsDB = class {
|
|
8863
9528
|
db;
|
|
8864
9529
|
constructor(dbPath) {
|
|
8865
|
-
|
|
9530
|
+
fs24.mkdirSync(path31.dirname(dbPath), { recursive: true });
|
|
8866
9531
|
this.db = new Database3(dbPath);
|
|
8867
9532
|
this.db.pragma("journal_mode = WAL");
|
|
8868
9533
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -9004,7 +9669,7 @@ var JobsDB = class {
|
|
|
9004
9669
|
}
|
|
9005
9670
|
};
|
|
9006
9671
|
function getJobsDBPath() {
|
|
9007
|
-
return
|
|
9672
|
+
return path31.join(os12.homedir(), ".code-intel", "jobs.db");
|
|
9008
9673
|
}
|
|
9009
9674
|
var _jobsDB = null;
|
|
9010
9675
|
function getOrCreateJobsDB() {
|
|
@@ -9096,7 +9761,7 @@ var BACKUP_VERSION = "1.0";
|
|
|
9096
9761
|
var ALGORITHM = "aes-256-gcm";
|
|
9097
9762
|
var IV_LENGTH = 16;
|
|
9098
9763
|
function getBackupDir() {
|
|
9099
|
-
return
|
|
9764
|
+
return path31.join(os12.homedir(), ".code-intel", "backups");
|
|
9100
9765
|
}
|
|
9101
9766
|
function getBackupKey() {
|
|
9102
9767
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
@@ -9127,30 +9792,30 @@ var BackupService = class {
|
|
|
9127
9792
|
constructor(backupDir) {
|
|
9128
9793
|
this.backupDir = backupDir ?? getBackupDir();
|
|
9129
9794
|
this.key = getBackupKey();
|
|
9130
|
-
|
|
9795
|
+
fs24.mkdirSync(this.backupDir, { recursive: true });
|
|
9131
9796
|
}
|
|
9132
9797
|
/**
|
|
9133
9798
|
* Create a backup for a repository.
|
|
9134
9799
|
* Returns the backup entry.
|
|
9135
9800
|
*/
|
|
9136
9801
|
createBackup(repoPath) {
|
|
9137
|
-
const codeIntelDir =
|
|
9802
|
+
const codeIntelDir = path31.join(repoPath, ".code-intel");
|
|
9138
9803
|
const id = v4();
|
|
9139
9804
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9140
9805
|
const filesToBackup = [];
|
|
9141
9806
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
9142
9807
|
for (const f of candidates) {
|
|
9143
|
-
const fp =
|
|
9144
|
-
if (
|
|
9808
|
+
const fp = path31.join(codeIntelDir, f);
|
|
9809
|
+
if (fs24.existsSync(fp)) {
|
|
9145
9810
|
filesToBackup.push({ name: f, localPath: fp });
|
|
9146
9811
|
}
|
|
9147
9812
|
}
|
|
9148
|
-
const registryPath =
|
|
9149
|
-
if (
|
|
9813
|
+
const registryPath = path31.join(os12.homedir(), ".code-intel", "registry.json");
|
|
9814
|
+
if (fs24.existsSync(registryPath)) {
|
|
9150
9815
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
9151
9816
|
}
|
|
9152
|
-
const usersDbPath =
|
|
9153
|
-
if (
|
|
9817
|
+
const usersDbPath = path31.join(os12.homedir(), ".code-intel", "users.db");
|
|
9818
|
+
if (fs24.existsSync(usersDbPath)) {
|
|
9154
9819
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
9155
9820
|
}
|
|
9156
9821
|
if (filesToBackup.length === 0) {
|
|
@@ -9161,7 +9826,7 @@ var BackupService = class {
|
|
|
9161
9826
|
createdAt,
|
|
9162
9827
|
version: BACKUP_VERSION,
|
|
9163
9828
|
files: filesToBackup.map((f) => {
|
|
9164
|
-
const data =
|
|
9829
|
+
const data = fs24.readFileSync(f.localPath);
|
|
9165
9830
|
return {
|
|
9166
9831
|
name: f.name,
|
|
9167
9832
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -9175,7 +9840,7 @@ var BackupService = class {
|
|
|
9175
9840
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
9176
9841
|
parts.push(manifestLenBuf, manifestBuf);
|
|
9177
9842
|
for (const f of filesToBackup) {
|
|
9178
|
-
const data =
|
|
9843
|
+
const data = fs24.readFileSync(f.localPath);
|
|
9179
9844
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
9180
9845
|
const nameLenBuf = Buffer.alloc(2);
|
|
9181
9846
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -9186,8 +9851,8 @@ var BackupService = class {
|
|
|
9186
9851
|
const plaintext = Buffer.concat(parts);
|
|
9187
9852
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
9188
9853
|
const backupFileName = `backup-${id}.cib`;
|
|
9189
|
-
const backupPath =
|
|
9190
|
-
|
|
9854
|
+
const backupPath = path31.join(this.backupDir, backupFileName);
|
|
9855
|
+
fs24.writeFileSync(backupPath, encrypted);
|
|
9191
9856
|
const entry = {
|
|
9192
9857
|
id,
|
|
9193
9858
|
createdAt,
|
|
@@ -9214,9 +9879,9 @@ var BackupService = class {
|
|
|
9214
9879
|
async uploadToS3(entry) {
|
|
9215
9880
|
const cfg = getS3Config();
|
|
9216
9881
|
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 =
|
|
9882
|
+
const fileName = path31.basename(entry.path);
|
|
9218
9883
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
9219
|
-
const body =
|
|
9884
|
+
const body = fs24.readFileSync(entry.path);
|
|
9220
9885
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
9221
9886
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
9222
9887
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -9233,8 +9898,8 @@ var BackupService = class {
|
|
|
9233
9898
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
9234
9899
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
9235
9900
|
}
|
|
9236
|
-
|
|
9237
|
-
|
|
9901
|
+
fs24.mkdirSync(path31.dirname(destPath), { recursive: true });
|
|
9902
|
+
fs24.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
9238
9903
|
}
|
|
9239
9904
|
/**
|
|
9240
9905
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -9280,10 +9945,10 @@ var BackupService = class {
|
|
|
9280
9945
|
if (!entry) {
|
|
9281
9946
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
9282
9947
|
}
|
|
9283
|
-
if (!
|
|
9948
|
+
if (!fs24.existsSync(entry.path)) {
|
|
9284
9949
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
9285
9950
|
}
|
|
9286
|
-
const encrypted =
|
|
9951
|
+
const encrypted = fs24.readFileSync(entry.path);
|
|
9287
9952
|
let plaintext;
|
|
9288
9953
|
try {
|
|
9289
9954
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -9297,8 +9962,8 @@ var BackupService = class {
|
|
|
9297
9962
|
offset += manifestLen;
|
|
9298
9963
|
const manifest = JSON.parse(manifestStr);
|
|
9299
9964
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
9300
|
-
const codeIntelDir =
|
|
9301
|
-
|
|
9965
|
+
const codeIntelDir = path31.join(restoreBase, ".code-intel");
|
|
9966
|
+
fs24.mkdirSync(codeIntelDir, { recursive: true });
|
|
9302
9967
|
for (const fileEntry of manifest.files) {
|
|
9303
9968
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
9304
9969
|
offset += 2;
|
|
@@ -9315,18 +9980,18 @@ var BackupService = class {
|
|
|
9315
9980
|
}
|
|
9316
9981
|
let destPath;
|
|
9317
9982
|
if (name === "registry.json" || name === "users.db") {
|
|
9318
|
-
destPath =
|
|
9983
|
+
destPath = path31.join(os12.homedir(), ".code-intel", name);
|
|
9319
9984
|
} else {
|
|
9320
|
-
destPath =
|
|
9985
|
+
destPath = path31.join(codeIntelDir, name);
|
|
9321
9986
|
}
|
|
9322
|
-
|
|
9987
|
+
fs24.writeFileSync(destPath, data);
|
|
9323
9988
|
}
|
|
9324
9989
|
}
|
|
9325
9990
|
/**
|
|
9326
9991
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
9327
9992
|
*/
|
|
9328
9993
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
9329
|
-
const entries = this._loadIndex().filter((e) =>
|
|
9994
|
+
const entries = this._loadIndex().filter((e) => fs24.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
9330
9995
|
const keep = /* @__PURE__ */ new Set();
|
|
9331
9996
|
const now = /* @__PURE__ */ new Date();
|
|
9332
9997
|
const dailyCutoff = new Date(now);
|
|
@@ -9356,7 +10021,7 @@ var BackupService = class {
|
|
|
9356
10021
|
for (const e of entries) {
|
|
9357
10022
|
if (!keep.has(e.id)) {
|
|
9358
10023
|
try {
|
|
9359
|
-
|
|
10024
|
+
fs24.unlinkSync(e.path);
|
|
9360
10025
|
deleted++;
|
|
9361
10026
|
} catch {
|
|
9362
10027
|
}
|
|
@@ -9368,17 +10033,17 @@ var BackupService = class {
|
|
|
9368
10033
|
}
|
|
9369
10034
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
9370
10035
|
_indexPath() {
|
|
9371
|
-
return
|
|
10036
|
+
return path31.join(this.backupDir, "index.json");
|
|
9372
10037
|
}
|
|
9373
10038
|
_loadIndex() {
|
|
9374
10039
|
try {
|
|
9375
|
-
return JSON.parse(
|
|
10040
|
+
return JSON.parse(fs24.readFileSync(this._indexPath(), "utf-8"));
|
|
9376
10041
|
} catch {
|
|
9377
10042
|
return [];
|
|
9378
10043
|
}
|
|
9379
10044
|
}
|
|
9380
10045
|
_saveIndex(entries) {
|
|
9381
|
-
|
|
10046
|
+
fs24.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
9382
10047
|
}
|
|
9383
10048
|
_appendIndex(entry) {
|
|
9384
10049
|
const entries = this._loadIndex();
|
|
@@ -10171,11 +10836,11 @@ var openApiSpec = {
|
|
|
10171
10836
|
};
|
|
10172
10837
|
|
|
10173
10838
|
// src/http/app.ts
|
|
10174
|
-
var __dirname$1 =
|
|
10839
|
+
var __dirname$1 = path31.dirname(fileURLToPath(import.meta.url));
|
|
10175
10840
|
var WEB_DIST = (() => {
|
|
10176
|
-
const bundled =
|
|
10177
|
-
if (
|
|
10178
|
-
return
|
|
10841
|
+
const bundled = path31.resolve(__dirname$1, "..", "web");
|
|
10842
|
+
if (fs24.existsSync(bundled)) return bundled;
|
|
10843
|
+
return path31.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
10179
10844
|
})();
|
|
10180
10845
|
function getAllowedOrigins() {
|
|
10181
10846
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -10706,8 +11371,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10706
11371
|
const registry = loadRegistry();
|
|
10707
11372
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
10708
11373
|
if (!entry) return null;
|
|
10709
|
-
const dbPath =
|
|
10710
|
-
if (!
|
|
11374
|
+
const dbPath = path31.join(entry.path, ".code-intel", "graph.db");
|
|
11375
|
+
if (!fs24.existsSync(dbPath)) return null;
|
|
10711
11376
|
const repoGraph = createKnowledgeGraph();
|
|
10712
11377
|
const db = new DbManager(dbPath);
|
|
10713
11378
|
try {
|
|
@@ -10794,7 +11459,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10794
11459
|
return;
|
|
10795
11460
|
}
|
|
10796
11461
|
try {
|
|
10797
|
-
const content =
|
|
11462
|
+
const content = fs24.readFileSync(file_path, "utf-8");
|
|
10798
11463
|
res.json({ content });
|
|
10799
11464
|
} catch {
|
|
10800
11465
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -11052,8 +11717,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11052
11717
|
for (const member of group.members) {
|
|
11053
11718
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
11054
11719
|
if (!regEntry) continue;
|
|
11055
|
-
const dbPath =
|
|
11056
|
-
if (!
|
|
11720
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
11721
|
+
if (!fs24.existsSync(dbPath)) continue;
|
|
11057
11722
|
const db = new DbManager(dbPath);
|
|
11058
11723
|
try {
|
|
11059
11724
|
await db.init();
|
|
@@ -11079,8 +11744,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11079
11744
|
let nodeCount = 0;
|
|
11080
11745
|
let edgeCount = 0;
|
|
11081
11746
|
if (regEntry) {
|
|
11082
|
-
const dbPath =
|
|
11083
|
-
if (
|
|
11747
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
11748
|
+
if (fs24.existsSync(dbPath)) {
|
|
11084
11749
|
try {
|
|
11085
11750
|
const db = new DbManager(dbPath);
|
|
11086
11751
|
await db.init();
|
|
@@ -11129,14 +11794,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11129
11794
|
});
|
|
11130
11795
|
return;
|
|
11131
11796
|
}
|
|
11132
|
-
let rawResolved =
|
|
11133
|
-
if (!
|
|
11134
|
-
rawResolved =
|
|
11797
|
+
let rawResolved = path31.normalize(file);
|
|
11798
|
+
if (!path31.isAbsolute(rawResolved) && workspaceRoot) {
|
|
11799
|
+
rawResolved = path31.join(workspaceRoot, rawResolved);
|
|
11135
11800
|
}
|
|
11136
|
-
const resolvedFile =
|
|
11801
|
+
const resolvedFile = path31.resolve(rawResolved);
|
|
11137
11802
|
function isInsideDir(fileAbs, dir) {
|
|
11138
|
-
const rel =
|
|
11139
|
-
return !rel.startsWith("..") && !
|
|
11803
|
+
const rel = path31.relative(path31.resolve(dir), fileAbs);
|
|
11804
|
+
return !rel.startsWith("..") && !path31.isAbsolute(rel);
|
|
11140
11805
|
}
|
|
11141
11806
|
if (workspaceRoot) {
|
|
11142
11807
|
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
@@ -11173,7 +11838,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11173
11838
|
}
|
|
11174
11839
|
let fileContent;
|
|
11175
11840
|
try {
|
|
11176
|
-
fileContent =
|
|
11841
|
+
fileContent = fs24.readFileSync(resolvedFile, "utf-8");
|
|
11177
11842
|
} catch {
|
|
11178
11843
|
res.status(404).json({
|
|
11179
11844
|
error: {
|
|
@@ -11204,7 +11869,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11204
11869
|
const contextStart = Math.max(1, startLine - 20);
|
|
11205
11870
|
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
11206
11871
|
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
11207
|
-
const ext =
|
|
11872
|
+
const ext = path31.extname(resolvedFile).toLowerCase();
|
|
11208
11873
|
const languageMap = {
|
|
11209
11874
|
".ts": "typescript",
|
|
11210
11875
|
".tsx": "typescript",
|
|
@@ -11339,10 +12004,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11339
12004
|
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
12005
|
}
|
|
11341
12006
|
});
|
|
11342
|
-
if (
|
|
12007
|
+
if (fs24.existsSync(WEB_DIST)) {
|
|
11343
12008
|
app.use(express.static(WEB_DIST));
|
|
11344
12009
|
app.get("/{*path}", (_req, res) => {
|
|
11345
|
-
res.sendFile(
|
|
12010
|
+
res.sendFile(path31.join(WEB_DIST, "index.html"));
|
|
11346
12011
|
});
|
|
11347
12012
|
}
|
|
11348
12013
|
app.use("/admin", requireRole("admin"));
|