@vohongtho.infotech/code-intel 0.6.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 +2804 -1003
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +1162 -182
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-CnPQcqTr.js → es-J7AmFCht.js} +1 -1
- package/dist/web/assets/index-DSIgTcZc.css +2 -0
- package/dist/web/assets/{index-j-iO6isa.js → index-upRm-kxQ.js} +4 -4
- package/dist/web/index.html +2 -2
- package/package.json +12 -1
- package/dist/web/assets/index-rprt8Su_.css +0 -2
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()) {
|
|
@@ -4214,6 +4758,9 @@ function requireAuth(req, res, next) {
|
|
|
4214
4758
|
}
|
|
4215
4759
|
next();
|
|
4216
4760
|
}
|
|
4761
|
+
function meetsRole(userRole, required) {
|
|
4762
|
+
return (ROLE_RANK[userRole] ?? 0) >= (ROLE_RANK[required] ?? 0);
|
|
4763
|
+
}
|
|
4217
4764
|
function requireRole(...roles) {
|
|
4218
4765
|
return (req, res, next) => {
|
|
4219
4766
|
if (!req.user) {
|
|
@@ -4228,7 +4775,8 @@ function requireRole(...roles) {
|
|
|
4228
4775
|
});
|
|
4229
4776
|
return;
|
|
4230
4777
|
}
|
|
4231
|
-
|
|
4778
|
+
const allowed = roles.some((r) => meetsRole(req.user.role, r));
|
|
4779
|
+
if (!allowed) {
|
|
4232
4780
|
res.status(403).json({
|
|
4233
4781
|
error: {
|
|
4234
4782
|
code: ErrorCodes.FORBIDDEN,
|
|
@@ -4337,7 +4885,7 @@ function clearSessionCookie() {
|
|
|
4337
4885
|
async function verifyPassword(plain, hash) {
|
|
4338
4886
|
return bcrypt.compare(plain, hash);
|
|
4339
4887
|
}
|
|
4340
|
-
var sessionStore, SESSION_COOKIE_NAME;
|
|
4888
|
+
var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
|
|
4341
4889
|
var init_middleware = __esm({
|
|
4342
4890
|
"src/auth/middleware.ts"() {
|
|
4343
4891
|
init_users_db();
|
|
@@ -4345,6 +4893,12 @@ var init_middleware = __esm({
|
|
|
4345
4893
|
init_secret_store();
|
|
4346
4894
|
sessionStore = /* @__PURE__ */ new Map();
|
|
4347
4895
|
SESSION_COOKIE_NAME = "code_intel_session";
|
|
4896
|
+
ROLE_RANK = {
|
|
4897
|
+
viewer: 1,
|
|
4898
|
+
"repo-owner": 2,
|
|
4899
|
+
analyst: 3,
|
|
4900
|
+
admin: 4
|
|
4901
|
+
};
|
|
4348
4902
|
}
|
|
4349
4903
|
});
|
|
4350
4904
|
function verifyWebSocketHandshake(req) {
|
|
@@ -4652,7 +5206,7 @@ init_shared();
|
|
|
4652
5206
|
init_shared();
|
|
4653
5207
|
init_typescript();
|
|
4654
5208
|
function resolveRelative(rawPath, fromFile, workspace) {
|
|
4655
|
-
const fromDir =
|
|
5209
|
+
const fromDir = path31.dirname(fromFile);
|
|
4656
5210
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
4657
5211
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
|
|
4658
5212
|
const resolved = workspace.resolve(fromDir, cleaned);
|
|
@@ -4704,7 +5258,7 @@ var pythonModule = {
|
|
|
4704
5258
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4705
5259
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
4706
5260
|
const parts = cleaned.split(".");
|
|
4707
|
-
const fromDir =
|
|
5261
|
+
const fromDir = path31.dirname(fromFile);
|
|
4708
5262
|
const relPath = parts.join("/");
|
|
4709
5263
|
for (const suffix of ["/__init__.py", ".py"]) {
|
|
4710
5264
|
const r = workspace.resolve(fromDir, relPath + suffix);
|
|
@@ -4783,7 +5337,7 @@ var cModule = {
|
|
|
4783
5337
|
inheritanceStrategy: "none",
|
|
4784
5338
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4785
5339
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4786
|
-
const fromDir =
|
|
5340
|
+
const fromDir = path31.dirname(fromFile);
|
|
4787
5341
|
return workspace.resolve(fromDir, cleaned);
|
|
4788
5342
|
},
|
|
4789
5343
|
isExported(_node) {
|
|
@@ -4806,7 +5360,7 @@ var cppModule = {
|
|
|
4806
5360
|
inheritanceStrategy: "depth-first",
|
|
4807
5361
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4808
5362
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4809
|
-
const fromDir =
|
|
5363
|
+
const fromDir = path31.dirname(fromFile);
|
|
4810
5364
|
return workspace.resolve(fromDir, cleaned);
|
|
4811
5365
|
},
|
|
4812
5366
|
isExported(_node) {
|
|
@@ -4968,7 +5522,7 @@ var dartModule = {
|
|
|
4968
5522
|
const pkg = cleaned.replace("package:", "");
|
|
4969
5523
|
return workspace.findByPackage(pkg);
|
|
4970
5524
|
}
|
|
4971
|
-
const fromDir =
|
|
5525
|
+
const fromDir = path31.dirname(fromFile);
|
|
4972
5526
|
return workspace.resolve(fromDir, cleaned);
|
|
4973
5527
|
},
|
|
4974
5528
|
isExported(node) {
|
|
@@ -5323,25 +5877,25 @@ function validateDAG(phases) {
|
|
|
5323
5877
|
const visiting = /* @__PURE__ */ new Set();
|
|
5324
5878
|
const visited = /* @__PURE__ */ new Set();
|
|
5325
5879
|
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
5326
|
-
function dfs(name,
|
|
5880
|
+
function dfs(name, path32) {
|
|
5327
5881
|
if (visiting.has(name)) {
|
|
5328
|
-
const cycleStart =
|
|
5329
|
-
const cycle =
|
|
5882
|
+
const cycleStart = path32.indexOf(name);
|
|
5883
|
+
const cycle = path32.slice(cycleStart).concat(name);
|
|
5330
5884
|
errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
5331
5885
|
return true;
|
|
5332
5886
|
}
|
|
5333
5887
|
if (visited.has(name)) return false;
|
|
5334
5888
|
visiting.add(name);
|
|
5335
|
-
|
|
5889
|
+
path32.push(name);
|
|
5336
5890
|
const phase = phaseMap.get(name);
|
|
5337
5891
|
if (phase) {
|
|
5338
5892
|
for (const dep of phase.dependencies) {
|
|
5339
|
-
if (dfs(dep,
|
|
5893
|
+
if (dfs(dep, path32)) return true;
|
|
5340
5894
|
}
|
|
5341
5895
|
}
|
|
5342
5896
|
visiting.delete(name);
|
|
5343
5897
|
visited.add(name);
|
|
5344
|
-
|
|
5898
|
+
path32.pop();
|
|
5345
5899
|
return false;
|
|
5346
5900
|
}
|
|
5347
5901
|
for (const phase of phases) {
|
|
@@ -5554,7 +6108,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
5554
6108
|
]);
|
|
5555
6109
|
function loadIgnorePatterns(workspaceRoot) {
|
|
5556
6110
|
try {
|
|
5557
|
-
const raw =
|
|
6111
|
+
const raw = fs24.readFileSync(path31.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
5558
6112
|
const extras = /* @__PURE__ */ new Set();
|
|
5559
6113
|
for (const line of raw.split("\n")) {
|
|
5560
6114
|
const trimmed = line.trim();
|
|
@@ -5578,7 +6132,7 @@ var scanPhase = {
|
|
|
5578
6132
|
function walk(dir) {
|
|
5579
6133
|
let entries;
|
|
5580
6134
|
try {
|
|
5581
|
-
entries =
|
|
6135
|
+
entries = fs24.readdirSync(dir, { withFileTypes: true });
|
|
5582
6136
|
} catch {
|
|
5583
6137
|
return;
|
|
5584
6138
|
}
|
|
@@ -5587,15 +6141,15 @@ var scanPhase = {
|
|
|
5587
6141
|
if (entry.name.startsWith(".")) continue;
|
|
5588
6142
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5589
6143
|
if (extraIgnore.has(entry.name)) continue;
|
|
5590
|
-
walk(
|
|
6144
|
+
walk(path31.join(dir, entry.name));
|
|
5591
6145
|
} else if (entry.isFile()) {
|
|
5592
6146
|
const name = entry.name;
|
|
5593
6147
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
5594
|
-
const ext =
|
|
6148
|
+
const ext = path31.extname(name);
|
|
5595
6149
|
if (!extensions.has(ext)) continue;
|
|
5596
|
-
const fullPath =
|
|
6150
|
+
const fullPath = path31.join(dir, name);
|
|
5597
6151
|
try {
|
|
5598
|
-
const stat =
|
|
6152
|
+
const stat = fs24.statSync(fullPath);
|
|
5599
6153
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
5600
6154
|
} catch {
|
|
5601
6155
|
continue;
|
|
@@ -5622,20 +6176,20 @@ var structurePhase = {
|
|
|
5622
6176
|
const dirs = /* @__PURE__ */ new Set();
|
|
5623
6177
|
let structDone = 0;
|
|
5624
6178
|
for (const filePath of context2.filePaths) {
|
|
5625
|
-
const relativePath =
|
|
6179
|
+
const relativePath = path31.relative(context2.workspaceRoot, filePath);
|
|
5626
6180
|
const lang = detectLanguage(filePath);
|
|
5627
6181
|
context2.graph.addNode({
|
|
5628
6182
|
id: generateNodeId("file", relativePath, relativePath),
|
|
5629
6183
|
kind: "file",
|
|
5630
|
-
name:
|
|
6184
|
+
name: path31.basename(filePath),
|
|
5631
6185
|
filePath: relativePath,
|
|
5632
6186
|
metadata: lang ? { language: lang } : void 0
|
|
5633
6187
|
});
|
|
5634
|
-
let dir =
|
|
6188
|
+
let dir = path31.dirname(relativePath);
|
|
5635
6189
|
while (dir && dir !== "." && dir !== "") {
|
|
5636
6190
|
if (dirs.has(dir)) break;
|
|
5637
6191
|
dirs.add(dir);
|
|
5638
|
-
dir =
|
|
6192
|
+
dir = path31.dirname(dir);
|
|
5639
6193
|
}
|
|
5640
6194
|
structDone++;
|
|
5641
6195
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -5644,7 +6198,7 @@ var structurePhase = {
|
|
|
5644
6198
|
context2.graph.addNode({
|
|
5645
6199
|
id: generateNodeId("directory", dir, dir),
|
|
5646
6200
|
kind: "directory",
|
|
5647
|
-
name:
|
|
6201
|
+
name: path31.basename(dir),
|
|
5648
6202
|
filePath: dir
|
|
5649
6203
|
});
|
|
5650
6204
|
}
|
|
@@ -5731,9 +6285,9 @@ var flowPhase = {
|
|
|
5731
6285
|
for (const node of graph.allNodes()) {
|
|
5732
6286
|
if (!["function", "method"].includes(node.kind)) continue;
|
|
5733
6287
|
let score = 0;
|
|
5734
|
-
const
|
|
6288
|
+
const hasCallers2 = calledNodes.has(node.id);
|
|
5735
6289
|
const outCalls = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "calls");
|
|
5736
|
-
if (!
|
|
6290
|
+
if (!hasCallers2 && outCalls.length > 0) score += 10;
|
|
5737
6291
|
if (node.exported) score += 5;
|
|
5738
6292
|
if (/^(main|handle|init|start|run|execute|process|serve|listen|bootstrap)/.test(node.name)) score += 3;
|
|
5739
6293
|
if (node.filePath.includes("test") || node.filePath.includes("spec") || node.filePath.includes("__test")) score -= 20;
|
|
@@ -5755,22 +6309,22 @@ var flowPhase = {
|
|
|
5755
6309
|
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
5756
6310
|
const visited = /* @__PURE__ */ new Set();
|
|
5757
6311
|
while (queue.length > 0 && flowCount < maxFlows) {
|
|
5758
|
-
const { nodeId, path:
|
|
5759
|
-
if (
|
|
6312
|
+
const { nodeId, path: path32 } = queue.shift();
|
|
6313
|
+
if (path32.length > maxDepth) continue;
|
|
5760
6314
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
5761
|
-
if (callEdges.length === 0 &&
|
|
6315
|
+
if (callEdges.length === 0 && path32.length >= 3) {
|
|
5762
6316
|
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
5763
6317
|
graph.addNode({
|
|
5764
6318
|
id: flowId,
|
|
5765
6319
|
kind: "flow",
|
|
5766
6320
|
name: `${ep.name} flow ${flowCount}`,
|
|
5767
6321
|
filePath: ep.filePath,
|
|
5768
|
-
metadata: { steps:
|
|
6322
|
+
metadata: { steps: path32, entryPoint: ep.name }
|
|
5769
6323
|
});
|
|
5770
|
-
for (let i = 0; i <
|
|
6324
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5771
6325
|
graph.addEdge({
|
|
5772
|
-
id: generateEdgeId(
|
|
5773
|
-
source:
|
|
6326
|
+
id: generateEdgeId(path32[i], flowId, `step_of_${i}`),
|
|
6327
|
+
source: path32[i],
|
|
5774
6328
|
target: flowId,
|
|
5775
6329
|
kind: "step_of",
|
|
5776
6330
|
weight: 1,
|
|
@@ -5783,7 +6337,7 @@ var flowPhase = {
|
|
|
5783
6337
|
for (const edge of callEdges) {
|
|
5784
6338
|
if (visited.has(edge.target)) continue;
|
|
5785
6339
|
visited.add(edge.target);
|
|
5786
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6340
|
+
queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
|
|
5787
6341
|
}
|
|
5788
6342
|
}
|
|
5789
6343
|
}
|
|
@@ -5801,7 +6355,7 @@ var LLMGovernanceLogger = class {
|
|
|
5801
6355
|
}
|
|
5802
6356
|
/** Path to the JSONL log file. */
|
|
5803
6357
|
getLogPath() {
|
|
5804
|
-
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");
|
|
5805
6359
|
}
|
|
5806
6360
|
/**
|
|
5807
6361
|
* Append an entry to the governance log.
|
|
@@ -5817,8 +6371,8 @@ var LLMGovernanceLogger = class {
|
|
|
5817
6371
|
...entry
|
|
5818
6372
|
};
|
|
5819
6373
|
const logPath = this.getLogPath();
|
|
5820
|
-
|
|
5821
|
-
|
|
6374
|
+
fs24.mkdirSync(path31.dirname(logPath), { recursive: true });
|
|
6375
|
+
fs24.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
5822
6376
|
} catch {
|
|
5823
6377
|
}
|
|
5824
6378
|
}
|
|
@@ -5828,7 +6382,7 @@ var LLMGovernanceLogger = class {
|
|
|
5828
6382
|
*/
|
|
5829
6383
|
readLog(limit = 100) {
|
|
5830
6384
|
try {
|
|
5831
|
-
const raw =
|
|
6385
|
+
const raw = fs24.readFileSync(this.getLogPath(), "utf-8");
|
|
5832
6386
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
5833
6387
|
return lines.map((l) => JSON.parse(l));
|
|
5834
6388
|
} catch {
|
|
@@ -5972,17 +6526,17 @@ function traceFlow(entryId, graph, maxDepth = 10, maxBranching = 4) {
|
|
|
5972
6526
|
const queue = [{ nodeId: entryId, path: [entryId] }];
|
|
5973
6527
|
const visited = /* @__PURE__ */ new Set();
|
|
5974
6528
|
while (queue.length > 0 && flows.length < maxFlows) {
|
|
5975
|
-
const { nodeId, path:
|
|
5976
|
-
if (
|
|
6529
|
+
const { nodeId, path: path32 } = queue.shift();
|
|
6530
|
+
if (path32.length > maxDepth) continue;
|
|
5977
6531
|
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
5978
|
-
if (callEdges.length === 0 &&
|
|
5979
|
-
flows.push({ entryPointId: entryId, steps: [...
|
|
6532
|
+
if (callEdges.length === 0 && path32.length >= 3) {
|
|
6533
|
+
flows.push({ entryPointId: entryId, steps: [...path32] });
|
|
5980
6534
|
continue;
|
|
5981
6535
|
}
|
|
5982
6536
|
for (const edge of callEdges) {
|
|
5983
6537
|
if (visited.has(edge.target)) continue;
|
|
5984
6538
|
visited.add(edge.target);
|
|
5985
|
-
queue.push({ nodeId: edge.target, path: [...
|
|
6539
|
+
queue.push({ nodeId: edge.target, path: [...path32, edge.target] });
|
|
5986
6540
|
}
|
|
5987
6541
|
}
|
|
5988
6542
|
}
|
|
@@ -6216,7 +6770,7 @@ init_embedder();
|
|
|
6216
6770
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6217
6771
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50 } = options;
|
|
6218
6772
|
const bm25Promise = Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6219
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
6773
|
+
const hasVectorDb = Boolean(vectorDbPath && fs24.existsSync(vectorDbPath));
|
|
6220
6774
|
if (!hasVectorDb) {
|
|
6221
6775
|
const bm25Results2 = await bm25Promise;
|
|
6222
6776
|
return {
|
|
@@ -6273,7 +6827,7 @@ var DbManager = class {
|
|
|
6273
6827
|
this.dbPath = dbPath;
|
|
6274
6828
|
}
|
|
6275
6829
|
async init() {
|
|
6276
|
-
|
|
6830
|
+
fs24.mkdirSync(path31.dirname(this.dbPath), { recursive: true });
|
|
6277
6831
|
this.db = new Database(this.dbPath);
|
|
6278
6832
|
await this.db.init();
|
|
6279
6833
|
this.conn = new Connection(this.db);
|
|
@@ -6363,7 +6917,7 @@ function getCreateEdgeTableDDL() {
|
|
|
6363
6917
|
)`];
|
|
6364
6918
|
}
|
|
6365
6919
|
function writeNodeCSVs(graph, outputDir) {
|
|
6366
|
-
|
|
6920
|
+
fs24.mkdirSync(outputDir, { recursive: true });
|
|
6367
6921
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
6368
6922
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
6369
6923
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -6371,7 +6925,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
6371
6925
|
const table = NODE_TABLE_MAP[node.kind];
|
|
6372
6926
|
if (!tableBuffers.has(table)) {
|
|
6373
6927
|
tableBuffers.set(table, [header]);
|
|
6374
|
-
tableFilePaths.set(table,
|
|
6928
|
+
tableFilePaths.set(table, path31.join(outputDir, `${table}.csv`));
|
|
6375
6929
|
}
|
|
6376
6930
|
tableBuffers.get(table).push(
|
|
6377
6931
|
csvRow([
|
|
@@ -6391,12 +6945,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
6391
6945
|
);
|
|
6392
6946
|
}
|
|
6393
6947
|
for (const [table, lines] of tableBuffers) {
|
|
6394
|
-
|
|
6948
|
+
fs24.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
6395
6949
|
}
|
|
6396
6950
|
return tableFilePaths;
|
|
6397
6951
|
}
|
|
6398
6952
|
function writeEdgeCSV(graph, outputDir) {
|
|
6399
|
-
|
|
6953
|
+
fs24.mkdirSync(outputDir, { recursive: true });
|
|
6400
6954
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
6401
6955
|
const groups = /* @__PURE__ */ new Map();
|
|
6402
6956
|
for (const edge of graph.allEdges()) {
|
|
@@ -6407,7 +6961,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
6407
6961
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
6408
6962
|
const key = `${fromTable}->${toTable}`;
|
|
6409
6963
|
if (!groups.has(key)) {
|
|
6410
|
-
const filePath =
|
|
6964
|
+
const filePath = path31.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
6411
6965
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
6412
6966
|
}
|
|
6413
6967
|
groups.get(key).lines.push(
|
|
@@ -6422,7 +6976,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
6422
6976
|
}
|
|
6423
6977
|
const result = [];
|
|
6424
6978
|
for (const group of groups.values()) {
|
|
6425
|
-
|
|
6979
|
+
fs24.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
6426
6980
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
6427
6981
|
}
|
|
6428
6982
|
return result;
|
|
@@ -6450,7 +7004,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6450
7004
|
} catch {
|
|
6451
7005
|
}
|
|
6452
7006
|
}
|
|
6453
|
-
const tmpDir =
|
|
7007
|
+
const tmpDir = fs24.mkdtempSync(path31.join(os12.tmpdir(), "code-intel-csv-"));
|
|
6454
7008
|
try {
|
|
6455
7009
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
6456
7010
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -6469,8 +7023,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6469
7023
|
}
|
|
6470
7024
|
let nodeCount = 0;
|
|
6471
7025
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
6472
|
-
if (!
|
|
6473
|
-
const stat =
|
|
7026
|
+
if (!fs24.existsSync(csvPath)) continue;
|
|
7027
|
+
const stat = fs24.statSync(csvPath);
|
|
6474
7028
|
if (stat.size < 50) continue;
|
|
6475
7029
|
try {
|
|
6476
7030
|
await dbManager.execute(
|
|
@@ -6483,8 +7037,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6483
7037
|
}
|
|
6484
7038
|
let edgeCount = 0;
|
|
6485
7039
|
for (const group of edgeGroups) {
|
|
6486
|
-
if (!
|
|
6487
|
-
const stat =
|
|
7040
|
+
if (!fs24.existsSync(group.filePath)) continue;
|
|
7041
|
+
const stat = fs24.statSync(group.filePath);
|
|
6488
7042
|
if (stat.size < 50) continue;
|
|
6489
7043
|
try {
|
|
6490
7044
|
await dbManager.execute(
|
|
@@ -6498,7 +7052,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
6498
7052
|
return { nodeCount, edgeCount };
|
|
6499
7053
|
} finally {
|
|
6500
7054
|
try {
|
|
6501
|
-
|
|
7055
|
+
fs24.rmSync(tmpDir, { recursive: true, force: true });
|
|
6502
7056
|
} catch {
|
|
6503
7057
|
}
|
|
6504
7058
|
}
|
|
@@ -6550,19 +7104,19 @@ function buildNodeProps(node) {
|
|
|
6550
7104
|
function escCypher(s) {
|
|
6551
7105
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
6552
7106
|
}
|
|
6553
|
-
var GLOBAL_DIR =
|
|
6554
|
-
var REPOS_FILE =
|
|
7107
|
+
var GLOBAL_DIR = path31.join(os12.homedir(), ".code-intel");
|
|
7108
|
+
var REPOS_FILE = path31.join(GLOBAL_DIR, "repos.json");
|
|
6555
7109
|
function loadRegistry() {
|
|
6556
7110
|
try {
|
|
6557
|
-
const data =
|
|
7111
|
+
const data = fs24.readFileSync(REPOS_FILE, "utf-8");
|
|
6558
7112
|
return JSON.parse(data);
|
|
6559
7113
|
} catch {
|
|
6560
7114
|
return [];
|
|
6561
7115
|
}
|
|
6562
7116
|
}
|
|
6563
7117
|
function saveRegistry(entries) {
|
|
6564
|
-
|
|
6565
|
-
|
|
7118
|
+
fs24.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
7119
|
+
fs24.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
6566
7120
|
}
|
|
6567
7121
|
function upsertRepo(entry) {
|
|
6568
7122
|
const entries = loadRegistry();
|
|
@@ -6579,23 +7133,23 @@ function removeRepo(repoPath) {
|
|
|
6579
7133
|
saveRegistry(entries);
|
|
6580
7134
|
}
|
|
6581
7135
|
function saveMetadata(repoDir, metadata) {
|
|
6582
|
-
const metaDir =
|
|
6583
|
-
|
|
6584
|
-
|
|
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));
|
|
6585
7139
|
}
|
|
6586
7140
|
function loadMetadata(repoDir) {
|
|
6587
7141
|
try {
|
|
6588
|
-
const data =
|
|
7142
|
+
const data = fs24.readFileSync(path31.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
6589
7143
|
return JSON.parse(data);
|
|
6590
7144
|
} catch {
|
|
6591
7145
|
return null;
|
|
6592
7146
|
}
|
|
6593
7147
|
}
|
|
6594
7148
|
function getDbPath(repoDir) {
|
|
6595
|
-
return
|
|
7149
|
+
return path31.join(repoDir, ".code-intel", "graph.db");
|
|
6596
7150
|
}
|
|
6597
7151
|
function getVectorDbPath(repoDir) {
|
|
6598
|
-
return
|
|
7152
|
+
return path31.join(repoDir, ".code-intel", "vector.db");
|
|
6599
7153
|
}
|
|
6600
7154
|
|
|
6601
7155
|
// src/mcp-server/server.ts
|
|
@@ -6673,6 +7227,205 @@ async function loadGraphFromDB(graph, db) {
|
|
|
6673
7227
|
|
|
6674
7228
|
// src/multi-repo/group-sync.ts
|
|
6675
7229
|
init_logger();
|
|
7230
|
+
function scanForFiles(root, matcher, maxDepth = 2) {
|
|
7231
|
+
const results = [];
|
|
7232
|
+
function walk(dir, depth) {
|
|
7233
|
+
if (depth > maxDepth) return;
|
|
7234
|
+
let entries;
|
|
7235
|
+
try {
|
|
7236
|
+
entries = fs24.readdirSync(dir, { withFileTypes: true });
|
|
7237
|
+
} catch {
|
|
7238
|
+
return;
|
|
7239
|
+
}
|
|
7240
|
+
for (const entry of entries) {
|
|
7241
|
+
const full = path31.join(dir, entry.name);
|
|
7242
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
7243
|
+
walk(full, depth + 1);
|
|
7244
|
+
} else if (entry.isFile() && matcher(entry.name)) {
|
|
7245
|
+
results.push(full);
|
|
7246
|
+
}
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
walk(root, 0);
|
|
7250
|
+
return results;
|
|
7251
|
+
}
|
|
7252
|
+
|
|
7253
|
+
// src/multi-repo/schema-parsers/openapi-parser.ts
|
|
7254
|
+
var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
7255
|
+
"openapi.yaml",
|
|
7256
|
+
"openapi.json",
|
|
7257
|
+
"openapi.yml",
|
|
7258
|
+
"swagger.yaml",
|
|
7259
|
+
"swagger.json",
|
|
7260
|
+
"swagger.yml"
|
|
7261
|
+
]);
|
|
7262
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
7263
|
+
function tryParseFile(filePath) {
|
|
7264
|
+
const ext = path31.extname(filePath).toLowerCase();
|
|
7265
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
7266
|
+
if (ext === ".json") {
|
|
7267
|
+
try {
|
|
7268
|
+
return JSON.parse(content);
|
|
7269
|
+
} catch {
|
|
7270
|
+
return null;
|
|
7271
|
+
}
|
|
7272
|
+
}
|
|
7273
|
+
return null;
|
|
7274
|
+
}
|
|
7275
|
+
async function parseOpenAPIContracts(repoRoot) {
|
|
7276
|
+
const files = scanForFiles(repoRoot, (name) => OPENAPI_FILENAMES.has(name));
|
|
7277
|
+
const contracts = [];
|
|
7278
|
+
for (const filePath of files) {
|
|
7279
|
+
const spec = tryParseFile(filePath);
|
|
7280
|
+
if (!spec) continue;
|
|
7281
|
+
const paths = spec["paths"];
|
|
7282
|
+
if (!paths || typeof paths !== "object") continue;
|
|
7283
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
7284
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
7285
|
+
const ops = pathItem;
|
|
7286
|
+
for (const method of HTTP_METHODS) {
|
|
7287
|
+
if (!(method in ops)) continue;
|
|
7288
|
+
const operation = ops[method];
|
|
7289
|
+
if (!operation) continue;
|
|
7290
|
+
const requestBody = operation["requestBody"];
|
|
7291
|
+
const requestSchema = requestBody?.["content"] ? requestBody["content"]["application/json"]?.["schema"] : void 0;
|
|
7292
|
+
const responses = operation["responses"];
|
|
7293
|
+
const ok200 = responses?.["200"];
|
|
7294
|
+
const responseSchema = ok200?.["content"] ? ok200["content"]["application/json"]?.["schema"] : void 0;
|
|
7295
|
+
contracts.push({
|
|
7296
|
+
name: `${method.toUpperCase()} ${pathStr}`,
|
|
7297
|
+
kind: "route",
|
|
7298
|
+
method: method.toUpperCase(),
|
|
7299
|
+
path: pathStr,
|
|
7300
|
+
...requestSchema ? { requestSchema } : {},
|
|
7301
|
+
...responseSchema ? { responseSchema } : {},
|
|
7302
|
+
filePath
|
|
7303
|
+
});
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
return contracts;
|
|
7308
|
+
}
|
|
7309
|
+
function extractFieldNames(block) {
|
|
7310
|
+
return block.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
|
|
7311
|
+
const m = line.match(/^(\w+)\s*[(:]/);
|
|
7312
|
+
return m ? m[1] : null;
|
|
7313
|
+
}).filter((f) => f !== null);
|
|
7314
|
+
}
|
|
7315
|
+
async function parseGraphQLContracts(repoRoot) {
|
|
7316
|
+
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
7317
|
+
const contracts = [];
|
|
7318
|
+
for (const filePath of files) {
|
|
7319
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
7320
|
+
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
7321
|
+
let match;
|
|
7322
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
7323
|
+
const typeName = match[1];
|
|
7324
|
+
const body = match[2];
|
|
7325
|
+
const fields = extractFieldNames(body);
|
|
7326
|
+
const lcName = typeName.toLowerCase();
|
|
7327
|
+
if (lcName === "query") {
|
|
7328
|
+
for (const field of fields) {
|
|
7329
|
+
contracts.push({ name: `query.${field}`, kind: "graphql", operation: "query", fields, filePath });
|
|
7330
|
+
}
|
|
7331
|
+
} else if (lcName === "mutation") {
|
|
7332
|
+
for (const field of fields) {
|
|
7333
|
+
contracts.push({ name: `mutation.${field}`, kind: "graphql", operation: "mutation", fields, filePath });
|
|
7334
|
+
}
|
|
7335
|
+
} else if (lcName === "subscription") {
|
|
7336
|
+
for (const field of fields) {
|
|
7337
|
+
contracts.push({ name: `subscription.${field}`, kind: "graphql", operation: "subscription", fields, filePath });
|
|
7338
|
+
}
|
|
7339
|
+
} else {
|
|
7340
|
+
contracts.push({ name: `type.${typeName}`, kind: "graphql", operation: "type", fields, filePath });
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
return contracts;
|
|
7345
|
+
}
|
|
7346
|
+
async function parseProtoContracts(repoRoot) {
|
|
7347
|
+
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
7348
|
+
const contracts = [];
|
|
7349
|
+
for (const filePath of files) {
|
|
7350
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
7351
|
+
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
7352
|
+
let serviceMatch;
|
|
7353
|
+
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
7354
|
+
const serviceName = serviceMatch[1];
|
|
7355
|
+
const body = serviceMatch[2];
|
|
7356
|
+
const rpcRegex = /rpc\s+(\w+)\s*\((\w+)\)\s*returns\s*\((\w+)\)/g;
|
|
7357
|
+
let rpcMatch;
|
|
7358
|
+
while ((rpcMatch = rpcRegex.exec(body)) !== null) {
|
|
7359
|
+
const rpcName = rpcMatch[1];
|
|
7360
|
+
const inputType = rpcMatch[2];
|
|
7361
|
+
const outputType = rpcMatch[3];
|
|
7362
|
+
contracts.push({
|
|
7363
|
+
name: `${serviceName}.${rpcName}`,
|
|
7364
|
+
kind: "grpc",
|
|
7365
|
+
serviceName,
|
|
7366
|
+
rpcName,
|
|
7367
|
+
inputType,
|
|
7368
|
+
outputType,
|
|
7369
|
+
filePath
|
|
7370
|
+
});
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
return contracts;
|
|
7375
|
+
}
|
|
7376
|
+
|
|
7377
|
+
// src/multi-repo/type-similarity.ts
|
|
7378
|
+
function normalizeType(t) {
|
|
7379
|
+
return t.toLowerCase().replace(/[\[\]?<>\s]/g, "").trim();
|
|
7380
|
+
}
|
|
7381
|
+
function paramTypeSimilarity(paramsA, paramsB) {
|
|
7382
|
+
if (paramsA.length === 0 && paramsB.length === 0) return 1;
|
|
7383
|
+
if (paramsA.length === 0 || paramsB.length === 0) return 0;
|
|
7384
|
+
const setA = new Set(paramsA.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
|
|
7385
|
+
const setB = new Set(paramsB.map((p) => normalizeType(p.type ?? "")).filter(Boolean));
|
|
7386
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
7387
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
7388
|
+
const intersection = [...setA].filter((x) => setB.has(x)).length;
|
|
7389
|
+
const union = (/* @__PURE__ */ new Set([...setA, ...setB])).size;
|
|
7390
|
+
return intersection / union;
|
|
7391
|
+
}
|
|
7392
|
+
function returnTypeSimilarity(typeA, typeB) {
|
|
7393
|
+
if (!typeA || !typeB) return 0.5;
|
|
7394
|
+
const a = normalizeType(typeA);
|
|
7395
|
+
const b = normalizeType(typeB);
|
|
7396
|
+
if (a === b) return 1;
|
|
7397
|
+
const compatible = [
|
|
7398
|
+
["string", "str"],
|
|
7399
|
+
["number", "int"],
|
|
7400
|
+
["number", "float"],
|
|
7401
|
+
["number", "double"],
|
|
7402
|
+
["boolean", "bool"],
|
|
7403
|
+
["void", "unit"],
|
|
7404
|
+
["void", "none"]
|
|
7405
|
+
];
|
|
7406
|
+
for (const [x, y] of compatible) {
|
|
7407
|
+
if (a === x && b === y || a === y && b === x) return 0.8;
|
|
7408
|
+
}
|
|
7409
|
+
return 0;
|
|
7410
|
+
}
|
|
7411
|
+
function paramCountSimilarity(countA, countB) {
|
|
7412
|
+
const maxCount = Math.max(countA, countB, 1);
|
|
7413
|
+
return 1 - Math.abs(countA - countB) / maxCount;
|
|
7414
|
+
}
|
|
7415
|
+
function computeContractSimilarity(a, b, nameSim) {
|
|
7416
|
+
const paramsA = a.parameters ?? [];
|
|
7417
|
+
const paramsB = b.parameters ?? [];
|
|
7418
|
+
const ptSim = paramTypeSimilarity(paramsA, paramsB);
|
|
7419
|
+
const rtSim = returnTypeSimilarity(a.returnType, b.returnType);
|
|
7420
|
+
const pcSim = paramCountSimilarity(paramsA.length, paramsB.length);
|
|
7421
|
+
let score = 0.4 * nameSim + 0.3 * ptSim + 0.2 * rtSim + 0.1 * pcSim;
|
|
7422
|
+
if (ptSim > 0.8) {
|
|
7423
|
+
score = Math.min(1, score * 1.2);
|
|
7424
|
+
}
|
|
7425
|
+
return score;
|
|
7426
|
+
}
|
|
7427
|
+
|
|
7428
|
+
// src/multi-repo/group-sync.ts
|
|
6676
7429
|
function extractContracts(graph, repoName, repoPath) {
|
|
6677
7430
|
const contracts = [];
|
|
6678
7431
|
for (const node of graph.allNodes()) {
|
|
@@ -6685,7 +7438,10 @@ function extractContracts(graph, repoName, repoPath) {
|
|
|
6685
7438
|
nodeId: node.id,
|
|
6686
7439
|
nodeKind: node.kind,
|
|
6687
7440
|
filePath: node.filePath,
|
|
6688
|
-
signature: node.content?.split("\n")[0]?.trim()
|
|
7441
|
+
signature: node.content?.split("\n")[0]?.trim(),
|
|
7442
|
+
parameters: node.metadata?.parameters ?? node.metadata?.params,
|
|
7443
|
+
returnType: node.metadata?.returnType,
|
|
7444
|
+
exported: node.exported
|
|
6689
7445
|
});
|
|
6690
7446
|
}
|
|
6691
7447
|
if (node.kind === "route") {
|
|
@@ -6747,13 +7503,15 @@ function matchContracts(allContracts) {
|
|
|
6747
7503
|
const consumer = consumerByName.get(provider.name);
|
|
6748
7504
|
if (consumer) {
|
|
6749
7505
|
const sameKind = provider.kind === consumer.kind;
|
|
7506
|
+
const typedScore = computeContractSimilarity(provider, consumer, 1);
|
|
7507
|
+
const confidence = sameKind ? Math.max(typedScore, 0.9) : Math.max(typedScore, 0.6);
|
|
6750
7508
|
links.push({
|
|
6751
7509
|
providerRepo: provider.repoName,
|
|
6752
7510
|
providerContract: provider.name,
|
|
6753
7511
|
consumerRepo: consumer.repoName,
|
|
6754
7512
|
consumerContract: consumer.name,
|
|
6755
7513
|
matchKind: provider.kind === "route" ? "route-match" : "name-match",
|
|
6756
|
-
confidence:
|
|
7514
|
+
confidence: Math.min(1, confidence)
|
|
6757
7515
|
});
|
|
6758
7516
|
} else {
|
|
6759
7517
|
const providerLC = provider.name.toLowerCase();
|
|
@@ -6794,8 +7552,8 @@ async function syncGroup(group) {
|
|
|
6794
7552
|
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
6795
7553
|
continue;
|
|
6796
7554
|
}
|
|
6797
|
-
const dbPath =
|
|
6798
|
-
if (!
|
|
7555
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
7556
|
+
if (!fs24.existsSync(dbPath)) {
|
|
6799
7557
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
6800
7558
|
continue;
|
|
6801
7559
|
}
|
|
@@ -6811,6 +7569,44 @@ async function syncGroup(group) {
|
|
|
6811
7569
|
continue;
|
|
6812
7570
|
}
|
|
6813
7571
|
const contracts = extractContracts(graph, member.registryName, regEntry.path);
|
|
7572
|
+
const [openapiContracts, graphqlContracts, protoContracts] = await Promise.all([
|
|
7573
|
+
parseOpenAPIContracts(regEntry.path).catch(() => []),
|
|
7574
|
+
parseGraphQLContracts(regEntry.path).catch(() => []),
|
|
7575
|
+
parseProtoContracts(regEntry.path).catch(() => [])
|
|
7576
|
+
]);
|
|
7577
|
+
for (const c of openapiContracts) {
|
|
7578
|
+
contracts.push({
|
|
7579
|
+
repoName: member.registryName,
|
|
7580
|
+
repoPath: regEntry.path,
|
|
7581
|
+
kind: "route",
|
|
7582
|
+
name: c.name,
|
|
7583
|
+
nodeId: `openapi:${c.method}:${c.path}`,
|
|
7584
|
+
nodeKind: "route",
|
|
7585
|
+
filePath: c.filePath
|
|
7586
|
+
});
|
|
7587
|
+
}
|
|
7588
|
+
for (const c of graphqlContracts) {
|
|
7589
|
+
contracts.push({
|
|
7590
|
+
repoName: member.registryName,
|
|
7591
|
+
repoPath: regEntry.path,
|
|
7592
|
+
kind: "graphql",
|
|
7593
|
+
name: c.name,
|
|
7594
|
+
nodeId: `graphql:${c.name}`,
|
|
7595
|
+
nodeKind: "graphql",
|
|
7596
|
+
filePath: c.filePath
|
|
7597
|
+
});
|
|
7598
|
+
}
|
|
7599
|
+
for (const c of protoContracts) {
|
|
7600
|
+
contracts.push({
|
|
7601
|
+
repoName: member.registryName,
|
|
7602
|
+
repoPath: regEntry.path,
|
|
7603
|
+
kind: "grpc",
|
|
7604
|
+
name: c.name,
|
|
7605
|
+
nodeId: `grpc:${c.serviceName}:${c.rpcName}`,
|
|
7606
|
+
nodeKind: "grpc",
|
|
7607
|
+
filePath: c.filePath
|
|
7608
|
+
});
|
|
7609
|
+
}
|
|
6814
7610
|
logger_default.info(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
|
|
6815
7611
|
allContracts.push(...contracts);
|
|
6816
7612
|
}
|
|
@@ -6830,8 +7626,8 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
6830
7626
|
for (const member of group.members) {
|
|
6831
7627
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
6832
7628
|
if (!regEntry) continue;
|
|
6833
|
-
const dbPath =
|
|
6834
|
-
if (!
|
|
7629
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
7630
|
+
if (!fs24.existsSync(dbPath)) continue;
|
|
6835
7631
|
const graph = createKnowledgeGraph();
|
|
6836
7632
|
const db = new DbManager(dbPath);
|
|
6837
7633
|
try {
|
|
@@ -7331,22 +8127,22 @@ function suggestTests(graph, symbolName) {
|
|
|
7331
8127
|
const callPaths = [];
|
|
7332
8128
|
const pathQueue = [{ id: targetId, path: [symbolName], depth: 0 }];
|
|
7333
8129
|
while (pathQueue.length > 0 && callPaths.length < 5) {
|
|
7334
|
-
const { id, path:
|
|
7335
|
-
let
|
|
8130
|
+
const { id, path: path32, depth } = pathQueue.shift();
|
|
8131
|
+
let hasCallers2 = false;
|
|
7336
8132
|
for (const edge of graph.findEdgesTo(id)) {
|
|
7337
8133
|
if (edge.kind !== "calls") continue;
|
|
7338
8134
|
const callerNode = graph.getNode(edge.source);
|
|
7339
8135
|
if (!callerNode) continue;
|
|
7340
|
-
|
|
7341
|
-
const newPath = [callerNode.name, ...
|
|
8136
|
+
hasCallers2 = true;
|
|
8137
|
+
const newPath = [callerNode.name, ...path32];
|
|
7342
8138
|
if (depth + 1 >= 3 || callPaths.length >= 5) {
|
|
7343
8139
|
if (callPaths.length < 5) callPaths.push(newPath);
|
|
7344
8140
|
continue;
|
|
7345
8141
|
}
|
|
7346
8142
|
pathQueue.push({ id: edge.source, path: newPath, depth: depth + 1 });
|
|
7347
8143
|
}
|
|
7348
|
-
if (!
|
|
7349
|
-
callPaths.push(
|
|
8144
|
+
if (!hasCallers2 && path32.length > 1) {
|
|
8145
|
+
callPaths.push(path32);
|
|
7350
8146
|
}
|
|
7351
8147
|
}
|
|
7352
8148
|
if (callPaths.length === 0) {
|
|
@@ -7821,6 +8617,70 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
7821
8617
|
},
|
|
7822
8618
|
required: ["cluster"]
|
|
7823
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
|
+
}
|
|
7824
8684
|
}
|
|
7825
8685
|
]
|
|
7826
8686
|
}));
|
|
@@ -8244,7 +9104,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
8244
9104
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
8245
9105
|
for (const node of graph.allNodes()) {
|
|
8246
9106
|
if (!node.filePath) continue;
|
|
8247
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
9107
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path31.sep, "");
|
|
8248
9108
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
8249
9109
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
8250
9110
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -8517,6 +9377,63 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
8517
9377
|
const result = summarizeCluster(graph, cluster);
|
|
8518
9378
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
8519
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
|
+
}
|
|
8520
9437
|
default:
|
|
8521
9438
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
8522
9439
|
}
|
|
@@ -8610,7 +9527,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
8610
9527
|
var JobsDB = class {
|
|
8611
9528
|
db;
|
|
8612
9529
|
constructor(dbPath) {
|
|
8613
|
-
|
|
9530
|
+
fs24.mkdirSync(path31.dirname(dbPath), { recursive: true });
|
|
8614
9531
|
this.db = new Database3(dbPath);
|
|
8615
9532
|
this.db.pragma("journal_mode = WAL");
|
|
8616
9533
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -8752,7 +9669,7 @@ var JobsDB = class {
|
|
|
8752
9669
|
}
|
|
8753
9670
|
};
|
|
8754
9671
|
function getJobsDBPath() {
|
|
8755
|
-
return
|
|
9672
|
+
return path31.join(os12.homedir(), ".code-intel", "jobs.db");
|
|
8756
9673
|
}
|
|
8757
9674
|
var _jobsDB = null;
|
|
8758
9675
|
function getOrCreateJobsDB() {
|
|
@@ -8844,7 +9761,7 @@ var BACKUP_VERSION = "1.0";
|
|
|
8844
9761
|
var ALGORITHM = "aes-256-gcm";
|
|
8845
9762
|
var IV_LENGTH = 16;
|
|
8846
9763
|
function getBackupDir() {
|
|
8847
|
-
return
|
|
9764
|
+
return path31.join(os12.homedir(), ".code-intel", "backups");
|
|
8848
9765
|
}
|
|
8849
9766
|
function getBackupKey() {
|
|
8850
9767
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
@@ -8875,30 +9792,30 @@ var BackupService = class {
|
|
|
8875
9792
|
constructor(backupDir) {
|
|
8876
9793
|
this.backupDir = backupDir ?? getBackupDir();
|
|
8877
9794
|
this.key = getBackupKey();
|
|
8878
|
-
|
|
9795
|
+
fs24.mkdirSync(this.backupDir, { recursive: true });
|
|
8879
9796
|
}
|
|
8880
9797
|
/**
|
|
8881
9798
|
* Create a backup for a repository.
|
|
8882
9799
|
* Returns the backup entry.
|
|
8883
9800
|
*/
|
|
8884
9801
|
createBackup(repoPath) {
|
|
8885
|
-
const codeIntelDir =
|
|
9802
|
+
const codeIntelDir = path31.join(repoPath, ".code-intel");
|
|
8886
9803
|
const id = v4();
|
|
8887
9804
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8888
9805
|
const filesToBackup = [];
|
|
8889
9806
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
8890
9807
|
for (const f of candidates) {
|
|
8891
|
-
const fp =
|
|
8892
|
-
if (
|
|
9808
|
+
const fp = path31.join(codeIntelDir, f);
|
|
9809
|
+
if (fs24.existsSync(fp)) {
|
|
8893
9810
|
filesToBackup.push({ name: f, localPath: fp });
|
|
8894
9811
|
}
|
|
8895
9812
|
}
|
|
8896
|
-
const registryPath =
|
|
8897
|
-
if (
|
|
9813
|
+
const registryPath = path31.join(os12.homedir(), ".code-intel", "registry.json");
|
|
9814
|
+
if (fs24.existsSync(registryPath)) {
|
|
8898
9815
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
8899
9816
|
}
|
|
8900
|
-
const usersDbPath =
|
|
8901
|
-
if (
|
|
9817
|
+
const usersDbPath = path31.join(os12.homedir(), ".code-intel", "users.db");
|
|
9818
|
+
if (fs24.existsSync(usersDbPath)) {
|
|
8902
9819
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
8903
9820
|
}
|
|
8904
9821
|
if (filesToBackup.length === 0) {
|
|
@@ -8909,7 +9826,7 @@ var BackupService = class {
|
|
|
8909
9826
|
createdAt,
|
|
8910
9827
|
version: BACKUP_VERSION,
|
|
8911
9828
|
files: filesToBackup.map((f) => {
|
|
8912
|
-
const data =
|
|
9829
|
+
const data = fs24.readFileSync(f.localPath);
|
|
8913
9830
|
return {
|
|
8914
9831
|
name: f.name,
|
|
8915
9832
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -8923,7 +9840,7 @@ var BackupService = class {
|
|
|
8923
9840
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
8924
9841
|
parts.push(manifestLenBuf, manifestBuf);
|
|
8925
9842
|
for (const f of filesToBackup) {
|
|
8926
|
-
const data =
|
|
9843
|
+
const data = fs24.readFileSync(f.localPath);
|
|
8927
9844
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
8928
9845
|
const nameLenBuf = Buffer.alloc(2);
|
|
8929
9846
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -8934,8 +9851,8 @@ var BackupService = class {
|
|
|
8934
9851
|
const plaintext = Buffer.concat(parts);
|
|
8935
9852
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
8936
9853
|
const backupFileName = `backup-${id}.cib`;
|
|
8937
|
-
const backupPath =
|
|
8938
|
-
|
|
9854
|
+
const backupPath = path31.join(this.backupDir, backupFileName);
|
|
9855
|
+
fs24.writeFileSync(backupPath, encrypted);
|
|
8939
9856
|
const entry = {
|
|
8940
9857
|
id,
|
|
8941
9858
|
createdAt,
|
|
@@ -8962,9 +9879,9 @@ var BackupService = class {
|
|
|
8962
9879
|
async uploadToS3(entry) {
|
|
8963
9880
|
const cfg = getS3Config();
|
|
8964
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.");
|
|
8965
|
-
const fileName =
|
|
9882
|
+
const fileName = path31.basename(entry.path);
|
|
8966
9883
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
8967
|
-
const body =
|
|
9884
|
+
const body = fs24.readFileSync(entry.path);
|
|
8968
9885
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
8969
9886
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
8970
9887
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -8981,8 +9898,8 @@ var BackupService = class {
|
|
|
8981
9898
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
8982
9899
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
8983
9900
|
}
|
|
8984
|
-
|
|
8985
|
-
|
|
9901
|
+
fs24.mkdirSync(path31.dirname(destPath), { recursive: true });
|
|
9902
|
+
fs24.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
8986
9903
|
}
|
|
8987
9904
|
/**
|
|
8988
9905
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -9028,10 +9945,10 @@ var BackupService = class {
|
|
|
9028
9945
|
if (!entry) {
|
|
9029
9946
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
9030
9947
|
}
|
|
9031
|
-
if (!
|
|
9948
|
+
if (!fs24.existsSync(entry.path)) {
|
|
9032
9949
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
9033
9950
|
}
|
|
9034
|
-
const encrypted =
|
|
9951
|
+
const encrypted = fs24.readFileSync(entry.path);
|
|
9035
9952
|
let plaintext;
|
|
9036
9953
|
try {
|
|
9037
9954
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -9045,8 +9962,8 @@ var BackupService = class {
|
|
|
9045
9962
|
offset += manifestLen;
|
|
9046
9963
|
const manifest = JSON.parse(manifestStr);
|
|
9047
9964
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
9048
|
-
const codeIntelDir =
|
|
9049
|
-
|
|
9965
|
+
const codeIntelDir = path31.join(restoreBase, ".code-intel");
|
|
9966
|
+
fs24.mkdirSync(codeIntelDir, { recursive: true });
|
|
9050
9967
|
for (const fileEntry of manifest.files) {
|
|
9051
9968
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
9052
9969
|
offset += 2;
|
|
@@ -9063,18 +9980,18 @@ var BackupService = class {
|
|
|
9063
9980
|
}
|
|
9064
9981
|
let destPath;
|
|
9065
9982
|
if (name === "registry.json" || name === "users.db") {
|
|
9066
|
-
destPath =
|
|
9983
|
+
destPath = path31.join(os12.homedir(), ".code-intel", name);
|
|
9067
9984
|
} else {
|
|
9068
|
-
destPath =
|
|
9985
|
+
destPath = path31.join(codeIntelDir, name);
|
|
9069
9986
|
}
|
|
9070
|
-
|
|
9987
|
+
fs24.writeFileSync(destPath, data);
|
|
9071
9988
|
}
|
|
9072
9989
|
}
|
|
9073
9990
|
/**
|
|
9074
9991
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
9075
9992
|
*/
|
|
9076
9993
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
9077
|
-
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());
|
|
9078
9995
|
const keep = /* @__PURE__ */ new Set();
|
|
9079
9996
|
const now = /* @__PURE__ */ new Date();
|
|
9080
9997
|
const dailyCutoff = new Date(now);
|
|
@@ -9104,7 +10021,7 @@ var BackupService = class {
|
|
|
9104
10021
|
for (const e of entries) {
|
|
9105
10022
|
if (!keep.has(e.id)) {
|
|
9106
10023
|
try {
|
|
9107
|
-
|
|
10024
|
+
fs24.unlinkSync(e.path);
|
|
9108
10025
|
deleted++;
|
|
9109
10026
|
} catch {
|
|
9110
10027
|
}
|
|
@@ -9116,17 +10033,17 @@ var BackupService = class {
|
|
|
9116
10033
|
}
|
|
9117
10034
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
9118
10035
|
_indexPath() {
|
|
9119
|
-
return
|
|
10036
|
+
return path31.join(this.backupDir, "index.json");
|
|
9120
10037
|
}
|
|
9121
10038
|
_loadIndex() {
|
|
9122
10039
|
try {
|
|
9123
|
-
return JSON.parse(
|
|
10040
|
+
return JSON.parse(fs24.readFileSync(this._indexPath(), "utf-8"));
|
|
9124
10041
|
} catch {
|
|
9125
10042
|
return [];
|
|
9126
10043
|
}
|
|
9127
10044
|
}
|
|
9128
10045
|
_saveIndex(entries) {
|
|
9129
|
-
|
|
10046
|
+
fs24.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
9130
10047
|
}
|
|
9131
10048
|
_appendIndex(entry) {
|
|
9132
10049
|
const entries = this._loadIndex();
|
|
@@ -9785,6 +10702,30 @@ var openApiSpec = {
|
|
|
9785
10702
|
}
|
|
9786
10703
|
}
|
|
9787
10704
|
},
|
|
10705
|
+
"/groups/{name}/topology": {
|
|
10706
|
+
get: {
|
|
10707
|
+
tags: ["Groups"],
|
|
10708
|
+
summary: "Get the topology of repos and cross-repo contract edges for a group",
|
|
10709
|
+
parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" } }],
|
|
10710
|
+
responses: {
|
|
10711
|
+
"200": {
|
|
10712
|
+
description: "Repos and cross-repo edges",
|
|
10713
|
+
content: {
|
|
10714
|
+
"application/json": {
|
|
10715
|
+
schema: {
|
|
10716
|
+
type: "object",
|
|
10717
|
+
properties: {
|
|
10718
|
+
repos: { type: "array", items: { type: "object", properties: { name: { type: "string" }, groupPath: { type: "string" }, nodeCount: { type: "integer" }, edgeCount: { type: "integer" } } } },
|
|
10719
|
+
edges: { type: "array", items: { type: "object", properties: { source: { type: "string" }, target: { type: "string" }, contractName: { type: "string" }, confidence: { type: "number" }, kind: { type: "string" } } } }
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
},
|
|
10725
|
+
"404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
10726
|
+
}
|
|
10727
|
+
}
|
|
10728
|
+
},
|
|
9788
10729
|
"/query": {
|
|
9789
10730
|
post: {
|
|
9790
10731
|
tags: ["GQL"],
|
|
@@ -9895,11 +10836,11 @@ var openApiSpec = {
|
|
|
9895
10836
|
};
|
|
9896
10837
|
|
|
9897
10838
|
// src/http/app.ts
|
|
9898
|
-
var __dirname$1 =
|
|
10839
|
+
var __dirname$1 = path31.dirname(fileURLToPath(import.meta.url));
|
|
9899
10840
|
var WEB_DIST = (() => {
|
|
9900
|
-
const bundled =
|
|
9901
|
-
if (
|
|
9902
|
-
return
|
|
10841
|
+
const bundled = path31.resolve(__dirname$1, "..", "web");
|
|
10842
|
+
if (fs24.existsSync(bundled)) return bundled;
|
|
10843
|
+
return path31.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
9903
10844
|
})();
|
|
9904
10845
|
function getAllowedOrigins() {
|
|
9905
10846
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -10430,8 +11371,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10430
11371
|
const registry = loadRegistry();
|
|
10431
11372
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
10432
11373
|
if (!entry) return null;
|
|
10433
|
-
const dbPath =
|
|
10434
|
-
if (!
|
|
11374
|
+
const dbPath = path31.join(entry.path, ".code-intel", "graph.db");
|
|
11375
|
+
if (!fs24.existsSync(dbPath)) return null;
|
|
10435
11376
|
const repoGraph = createKnowledgeGraph();
|
|
10436
11377
|
const db = new DbManager(dbPath);
|
|
10437
11378
|
try {
|
|
@@ -10518,7 +11459,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10518
11459
|
return;
|
|
10519
11460
|
}
|
|
10520
11461
|
try {
|
|
10521
|
-
const content =
|
|
11462
|
+
const content = fs24.readFileSync(file_path, "utf-8");
|
|
10522
11463
|
res.json({ content });
|
|
10523
11464
|
} catch {
|
|
10524
11465
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -10776,8 +11717,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10776
11717
|
for (const member of group.members) {
|
|
10777
11718
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
10778
11719
|
if (!regEntry) continue;
|
|
10779
|
-
const dbPath =
|
|
10780
|
-
if (!
|
|
11720
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
11721
|
+
if (!fs24.existsSync(dbPath)) continue;
|
|
10781
11722
|
const db = new DbManager(dbPath);
|
|
10782
11723
|
try {
|
|
10783
11724
|
await db.init();
|
|
@@ -10789,6 +11730,45 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10789
11730
|
}
|
|
10790
11731
|
res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
|
|
10791
11732
|
});
|
|
11733
|
+
app.get("/api/v1/groups/:name/topology", requireAuth, requireRole("viewer"), async (req, res) => {
|
|
11734
|
+
const groupName = req.params["name"];
|
|
11735
|
+
const group = loadGroup(groupName);
|
|
11736
|
+
if (!group) {
|
|
11737
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
11738
|
+
return;
|
|
11739
|
+
}
|
|
11740
|
+
const syncResult = loadSyncResult(groupName);
|
|
11741
|
+
const registry = loadRegistry();
|
|
11742
|
+
const repos = await Promise.all(group.members.map(async (member) => {
|
|
11743
|
+
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
11744
|
+
let nodeCount = 0;
|
|
11745
|
+
let edgeCount = 0;
|
|
11746
|
+
if (regEntry) {
|
|
11747
|
+
const dbPath = path31.join(regEntry.path, ".code-intel", "graph.db");
|
|
11748
|
+
if (fs24.existsSync(dbPath)) {
|
|
11749
|
+
try {
|
|
11750
|
+
const db = new DbManager(dbPath);
|
|
11751
|
+
await db.init();
|
|
11752
|
+
const g = createKnowledgeGraph();
|
|
11753
|
+
await loadGraphFromDB(g, db);
|
|
11754
|
+
db.close();
|
|
11755
|
+
nodeCount = g.size.nodes;
|
|
11756
|
+
edgeCount = g.size.edges;
|
|
11757
|
+
} catch {
|
|
11758
|
+
}
|
|
11759
|
+
}
|
|
11760
|
+
}
|
|
11761
|
+
return { name: member.registryName, groupPath: member.groupPath, nodeCount, edgeCount };
|
|
11762
|
+
}));
|
|
11763
|
+
const edges = syncResult ? syncResult.links.map((link) => ({
|
|
11764
|
+
source: link.providerRepo,
|
|
11765
|
+
target: link.consumerRepo,
|
|
11766
|
+
contractName: link.providerContract,
|
|
11767
|
+
confidence: link.confidence,
|
|
11768
|
+
kind: "contract"
|
|
11769
|
+
})) : [];
|
|
11770
|
+
res.json({ repos, edges });
|
|
11771
|
+
});
|
|
10792
11772
|
app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
|
|
10793
11773
|
const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
|
|
10794
11774
|
if (!file) {
|
|
@@ -10814,14 +11794,14 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10814
11794
|
});
|
|
10815
11795
|
return;
|
|
10816
11796
|
}
|
|
10817
|
-
let rawResolved =
|
|
10818
|
-
if (!
|
|
10819
|
-
rawResolved =
|
|
11797
|
+
let rawResolved = path31.normalize(file);
|
|
11798
|
+
if (!path31.isAbsolute(rawResolved) && workspaceRoot) {
|
|
11799
|
+
rawResolved = path31.join(workspaceRoot, rawResolved);
|
|
10820
11800
|
}
|
|
10821
|
-
const resolvedFile =
|
|
11801
|
+
const resolvedFile = path31.resolve(rawResolved);
|
|
10822
11802
|
function isInsideDir(fileAbs, dir) {
|
|
10823
|
-
const rel =
|
|
10824
|
-
return !rel.startsWith("..") && !
|
|
11803
|
+
const rel = path31.relative(path31.resolve(dir), fileAbs);
|
|
11804
|
+
return !rel.startsWith("..") && !path31.isAbsolute(rel);
|
|
10825
11805
|
}
|
|
10826
11806
|
if (workspaceRoot) {
|
|
10827
11807
|
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
@@ -10858,7 +11838,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10858
11838
|
}
|
|
10859
11839
|
let fileContent;
|
|
10860
11840
|
try {
|
|
10861
|
-
fileContent =
|
|
11841
|
+
fileContent = fs24.readFileSync(resolvedFile, "utf-8");
|
|
10862
11842
|
} catch {
|
|
10863
11843
|
res.status(404).json({
|
|
10864
11844
|
error: {
|
|
@@ -10889,7 +11869,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
10889
11869
|
const contextStart = Math.max(1, startLine - 20);
|
|
10890
11870
|
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
10891
11871
|
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
10892
|
-
const ext =
|
|
11872
|
+
const ext = path31.extname(resolvedFile).toLowerCase();
|
|
10893
11873
|
const languageMap = {
|
|
10894
11874
|
".ts": "typescript",
|
|
10895
11875
|
".tsx": "typescript",
|
|
@@ -11024,10 +12004,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11024
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() } });
|
|
11025
12005
|
}
|
|
11026
12006
|
});
|
|
11027
|
-
if (
|
|
12007
|
+
if (fs24.existsSync(WEB_DIST)) {
|
|
11028
12008
|
app.use(express.static(WEB_DIST));
|
|
11029
12009
|
app.get("/{*path}", (_req, res) => {
|
|
11030
|
-
res.sendFile(
|
|
12010
|
+
res.sendFile(path31.join(WEB_DIST, "index.html"));
|
|
11031
12011
|
});
|
|
11032
12012
|
}
|
|
11033
12013
|
app.use("/admin", requireRole("admin"));
|