@vohongtho.infotech/code-intel 1.0.0 → 1.0.1
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 +4 -1
- package/dist/cli/main.js +883 -324
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +705 -529
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-DIfCC5I3.js → es-Bu8iwdFw.js} +1 -1
- package/dist/web/assets/index-C9M6YLlS.css +2 -0
- package/dist/web/assets/index-CKc3HEpe.js +354 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-QSOOiRQm.js +0 -352
- package/dist/web/assets/index-XjZQJMiV.css +0 -2
package/dist/index.js
CHANGED
|
@@ -6,12 +6,13 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
|
|
|
6
6
|
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
|
|
7
7
|
import winston from 'winston';
|
|
8
8
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
9
|
-
import
|
|
9
|
+
import fs26, { existsSync } from 'fs';
|
|
10
10
|
import path32 from 'path';
|
|
11
11
|
import os13 from 'os';
|
|
12
12
|
import { createRequire } from 'module';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
15
|
+
import { Database, Connection } from '@ladybugdb/core';
|
|
15
16
|
import { execSync } from 'child_process';
|
|
16
17
|
import Database2 from 'better-sqlite3';
|
|
17
18
|
import bcrypt from 'bcrypt';
|
|
@@ -24,7 +25,6 @@ import 'events';
|
|
|
24
25
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
25
26
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
26
27
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
27
|
-
import { Database, Connection } from '@ladybugdb/core';
|
|
28
28
|
import express from 'express';
|
|
29
29
|
import compression from 'compression';
|
|
30
30
|
import cors from 'cors';
|
|
@@ -56,6 +56,135 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
56
56
|
};
|
|
57
57
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
58
58
|
|
|
59
|
+
// src/graph/knowledge-graph.ts
|
|
60
|
+
var knowledge_graph_exports = {};
|
|
61
|
+
__export(knowledge_graph_exports, {
|
|
62
|
+
createKnowledgeGraph: () => createKnowledgeGraph
|
|
63
|
+
});
|
|
64
|
+
function createKnowledgeGraph() {
|
|
65
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
66
|
+
const edges = /* @__PURE__ */ new Map();
|
|
67
|
+
const edgesByKind = /* @__PURE__ */ new Map();
|
|
68
|
+
const edgesFromNode = /* @__PURE__ */ new Map();
|
|
69
|
+
const edgesToNode = /* @__PURE__ */ new Map();
|
|
70
|
+
function indexEdge(edge) {
|
|
71
|
+
let kindSet = edgesByKind.get(edge.kind);
|
|
72
|
+
if (!kindSet) {
|
|
73
|
+
kindSet = /* @__PURE__ */ new Set();
|
|
74
|
+
edgesByKind.set(edge.kind, kindSet);
|
|
75
|
+
}
|
|
76
|
+
kindSet.add(edge.id);
|
|
77
|
+
let fromSet = edgesFromNode.get(edge.source);
|
|
78
|
+
if (!fromSet) {
|
|
79
|
+
fromSet = /* @__PURE__ */ new Set();
|
|
80
|
+
edgesFromNode.set(edge.source, fromSet);
|
|
81
|
+
}
|
|
82
|
+
fromSet.add(edge.id);
|
|
83
|
+
let toSet = edgesToNode.get(edge.target);
|
|
84
|
+
if (!toSet) {
|
|
85
|
+
toSet = /* @__PURE__ */ new Set();
|
|
86
|
+
edgesToNode.set(edge.target, toSet);
|
|
87
|
+
}
|
|
88
|
+
toSet.add(edge.id);
|
|
89
|
+
}
|
|
90
|
+
function unindexEdge(edge) {
|
|
91
|
+
edgesByKind.get(edge.kind)?.delete(edge.id);
|
|
92
|
+
edgesFromNode.get(edge.source)?.delete(edge.id);
|
|
93
|
+
edgesToNode.get(edge.target)?.delete(edge.id);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
addNode(node) {
|
|
97
|
+
nodes.set(node.id, node);
|
|
98
|
+
},
|
|
99
|
+
addEdge(edge) {
|
|
100
|
+
edges.set(edge.id, edge);
|
|
101
|
+
indexEdge(edge);
|
|
102
|
+
},
|
|
103
|
+
getNode(id) {
|
|
104
|
+
return nodes.get(id);
|
|
105
|
+
},
|
|
106
|
+
getEdge(id) {
|
|
107
|
+
return edges.get(id);
|
|
108
|
+
},
|
|
109
|
+
*findEdgesByKind(kind) {
|
|
110
|
+
const ids = edgesByKind.get(kind);
|
|
111
|
+
if (!ids) return;
|
|
112
|
+
for (const id of ids) {
|
|
113
|
+
const edge = edges.get(id);
|
|
114
|
+
if (edge) yield edge;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
*findEdgesFrom(sourceId) {
|
|
118
|
+
const ids = edgesFromNode.get(sourceId);
|
|
119
|
+
if (!ids) return;
|
|
120
|
+
for (const id of ids) {
|
|
121
|
+
const edge = edges.get(id);
|
|
122
|
+
if (edge) yield edge;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
*findEdgesTo(targetId) {
|
|
126
|
+
const ids = edgesToNode.get(targetId);
|
|
127
|
+
if (!ids) return;
|
|
128
|
+
for (const id of ids) {
|
|
129
|
+
const edge = edges.get(id);
|
|
130
|
+
if (edge) yield edge;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
removeNodeCascade(id) {
|
|
134
|
+
const fromEdges = edgesFromNode.get(id);
|
|
135
|
+
if (fromEdges) {
|
|
136
|
+
for (const edgeId of [...fromEdges]) {
|
|
137
|
+
const edge = edges.get(edgeId);
|
|
138
|
+
if (edge) {
|
|
139
|
+
unindexEdge(edge);
|
|
140
|
+
edges.delete(edgeId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const toEdges = edgesToNode.get(id);
|
|
145
|
+
if (toEdges) {
|
|
146
|
+
for (const edgeId of [...toEdges]) {
|
|
147
|
+
const edge = edges.get(edgeId);
|
|
148
|
+
if (edge) {
|
|
149
|
+
unindexEdge(edge);
|
|
150
|
+
edges.delete(edgeId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
edgesFromNode.delete(id);
|
|
155
|
+
edgesToNode.delete(id);
|
|
156
|
+
nodes.delete(id);
|
|
157
|
+
},
|
|
158
|
+
removeEdge(id) {
|
|
159
|
+
const edge = edges.get(id);
|
|
160
|
+
if (edge) {
|
|
161
|
+
unindexEdge(edge);
|
|
162
|
+
edges.delete(id);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
*allNodes() {
|
|
166
|
+
yield* nodes.values();
|
|
167
|
+
},
|
|
168
|
+
*allEdges() {
|
|
169
|
+
yield* edges.values();
|
|
170
|
+
},
|
|
171
|
+
get size() {
|
|
172
|
+
return { nodes: nodes.size, edges: edges.size };
|
|
173
|
+
},
|
|
174
|
+
clear() {
|
|
175
|
+
nodes.clear();
|
|
176
|
+
edges.clear();
|
|
177
|
+
edgesByKind.clear();
|
|
178
|
+
edgesFromNode.clear();
|
|
179
|
+
edgesToNode.clear();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
var init_knowledge_graph = __esm({
|
|
184
|
+
"src/graph/knowledge-graph.ts"() {
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
59
188
|
// src/graph/id-generator.ts
|
|
60
189
|
function generateNodeId(kind, filePath, qualifiedName) {
|
|
61
190
|
return `${kind}:${filePath}:${qualifiedName}`;
|
|
@@ -68,6 +197,64 @@ var init_id_generator = __esm({
|
|
|
68
197
|
}
|
|
69
198
|
});
|
|
70
199
|
|
|
200
|
+
// src/storage/schema.ts
|
|
201
|
+
function getCreateNodeTableDDL(tableName) {
|
|
202
|
+
return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
|
|
203
|
+
id STRING,
|
|
204
|
+
name STRING,
|
|
205
|
+
file_path STRING,
|
|
206
|
+
start_line INT64,
|
|
207
|
+
end_line INT64,
|
|
208
|
+
exported BOOLEAN,
|
|
209
|
+
content STRING,
|
|
210
|
+
metadata STRING,
|
|
211
|
+
PRIMARY KEY (id)
|
|
212
|
+
)`;
|
|
213
|
+
}
|
|
214
|
+
function getCreateEdgeTableDDL() {
|
|
215
|
+
const uniqueTables = ALL_NODE_TABLES;
|
|
216
|
+
const fromToPairs = [];
|
|
217
|
+
for (const from of uniqueTables) {
|
|
218
|
+
for (const to of uniqueTables) {
|
|
219
|
+
fromToPairs.push(`FROM ${from} TO ${to}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
|
|
223
|
+
${fromToPairs.join(",\n ")},
|
|
224
|
+
kind STRING,
|
|
225
|
+
weight DOUBLE,
|
|
226
|
+
label STRING
|
|
227
|
+
)`];
|
|
228
|
+
}
|
|
229
|
+
var NODE_TABLE_MAP, ALL_NODE_TABLES;
|
|
230
|
+
var init_schema = __esm({
|
|
231
|
+
"src/storage/schema.ts"() {
|
|
232
|
+
NODE_TABLE_MAP = {
|
|
233
|
+
file: "file_nodes",
|
|
234
|
+
directory: "dir_nodes",
|
|
235
|
+
function: "func_nodes",
|
|
236
|
+
class: "class_nodes",
|
|
237
|
+
interface: "iface_nodes",
|
|
238
|
+
method: "method_nodes",
|
|
239
|
+
constructor: "ctor_nodes",
|
|
240
|
+
variable: "var_nodes",
|
|
241
|
+
property: "prop_nodes",
|
|
242
|
+
struct: "struct_nodes",
|
|
243
|
+
enum: "enum_nodes",
|
|
244
|
+
trait: "trait_nodes",
|
|
245
|
+
namespace: "ns_nodes",
|
|
246
|
+
module: "mod_nodes",
|
|
247
|
+
type_alias: "type_nodes",
|
|
248
|
+
constant: "const_nodes",
|
|
249
|
+
route: "route_nodes",
|
|
250
|
+
cluster: "cluster_nodes",
|
|
251
|
+
flow: "flow_nodes",
|
|
252
|
+
vulnerability: "vuln_nodes"
|
|
253
|
+
};
|
|
254
|
+
ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
71
258
|
// src/observability/tracing.ts
|
|
72
259
|
var tracing_exports = {};
|
|
73
260
|
__export(tracing_exports, {
|
|
@@ -344,8 +531,8 @@ var init_logger = __esm({
|
|
|
344
531
|
transports.push(new winston.transports.Console());
|
|
345
532
|
if (!isProduction) {
|
|
346
533
|
try {
|
|
347
|
-
if (!
|
|
348
|
-
|
|
534
|
+
if (!fs26.existsSync(_Logger.LOG_DIR)) {
|
|
535
|
+
fs26.mkdirSync(_Logger.LOG_DIR, { recursive: true });
|
|
349
536
|
}
|
|
350
537
|
transports.push(
|
|
351
538
|
new DailyRotateFile({
|
|
@@ -1961,7 +2148,7 @@ var init_parse_phase = __esm({
|
|
|
1961
2148
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
1962
2149
|
await Promise.all(batch.map(async (filePath) => {
|
|
1963
2150
|
try {
|
|
1964
|
-
const source = await
|
|
2151
|
+
const source = await fs26.promises.readFile(filePath, "utf-8");
|
|
1965
2152
|
context2.fileCache.set(filePath, source);
|
|
1966
2153
|
} catch {
|
|
1967
2154
|
}
|
|
@@ -2575,6 +2762,65 @@ var init_embedder = __esm({
|
|
|
2575
2762
|
}
|
|
2576
2763
|
});
|
|
2577
2764
|
|
|
2765
|
+
// src/storage/db-manager.ts
|
|
2766
|
+
var db_manager_exports = {};
|
|
2767
|
+
__export(db_manager_exports, {
|
|
2768
|
+
DbManager: () => DbManager
|
|
2769
|
+
});
|
|
2770
|
+
var DbManager;
|
|
2771
|
+
var init_db_manager = __esm({
|
|
2772
|
+
"src/storage/db-manager.ts"() {
|
|
2773
|
+
DbManager = class {
|
|
2774
|
+
db = null;
|
|
2775
|
+
conn = null;
|
|
2776
|
+
dbPath;
|
|
2777
|
+
readOnly;
|
|
2778
|
+
constructor(dbPath, readOnly = false) {
|
|
2779
|
+
this.dbPath = dbPath;
|
|
2780
|
+
this.readOnly = readOnly;
|
|
2781
|
+
}
|
|
2782
|
+
async init() {
|
|
2783
|
+
if (!this.readOnly) {
|
|
2784
|
+
fs26.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
2785
|
+
}
|
|
2786
|
+
this.db = new Database(this.dbPath, 0, true, this.readOnly);
|
|
2787
|
+
await this.db.init();
|
|
2788
|
+
this.conn = new Connection(this.db);
|
|
2789
|
+
await this.conn.init();
|
|
2790
|
+
}
|
|
2791
|
+
async query(cypher) {
|
|
2792
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
2793
|
+
const result = await this.conn.query(cypher);
|
|
2794
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
2795
|
+
const rows = await qr.getAll();
|
|
2796
|
+
qr.close();
|
|
2797
|
+
return rows;
|
|
2798
|
+
}
|
|
2799
|
+
async execute(cypher) {
|
|
2800
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
2801
|
+
const result = await this.conn.query(cypher);
|
|
2802
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
2803
|
+
qr.close();
|
|
2804
|
+
}
|
|
2805
|
+
close() {
|
|
2806
|
+
try {
|
|
2807
|
+
this.conn?.closeSync();
|
|
2808
|
+
} catch {
|
|
2809
|
+
}
|
|
2810
|
+
try {
|
|
2811
|
+
this.db?.closeSync();
|
|
2812
|
+
} catch {
|
|
2813
|
+
}
|
|
2814
|
+
this.conn = null;
|
|
2815
|
+
this.db = null;
|
|
2816
|
+
}
|
|
2817
|
+
get isOpen() {
|
|
2818
|
+
return this.conn !== null;
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
});
|
|
2823
|
+
|
|
2578
2824
|
// src/multi-repo/group-registry.ts
|
|
2579
2825
|
var group_registry_exports = {};
|
|
2580
2826
|
__export(group_registry_exports, {
|
|
@@ -2593,23 +2839,23 @@ function groupFile(name) {
|
|
|
2593
2839
|
}
|
|
2594
2840
|
function loadGroup(name) {
|
|
2595
2841
|
try {
|
|
2596
|
-
return JSON.parse(
|
|
2842
|
+
return JSON.parse(fs26.readFileSync(groupFile(name), "utf-8"));
|
|
2597
2843
|
} catch {
|
|
2598
2844
|
return null;
|
|
2599
2845
|
}
|
|
2600
2846
|
}
|
|
2601
2847
|
function saveGroup(group) {
|
|
2602
|
-
|
|
2603
|
-
|
|
2848
|
+
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2849
|
+
fs26.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
2604
2850
|
}
|
|
2605
2851
|
function listGroups() {
|
|
2606
2852
|
const groups = [];
|
|
2607
2853
|
try {
|
|
2608
|
-
for (const file of
|
|
2854
|
+
for (const file of fs26.readdirSync(GROUPS_DIR)) {
|
|
2609
2855
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
2610
2856
|
try {
|
|
2611
2857
|
const g = JSON.parse(
|
|
2612
|
-
|
|
2858
|
+
fs26.readFileSync(path32.join(GROUPS_DIR, file), "utf-8")
|
|
2613
2859
|
);
|
|
2614
2860
|
groups.push(g);
|
|
2615
2861
|
} catch {
|
|
@@ -2621,16 +2867,16 @@ function listGroups() {
|
|
|
2621
2867
|
}
|
|
2622
2868
|
function deleteGroup(name) {
|
|
2623
2869
|
try {
|
|
2624
|
-
|
|
2870
|
+
fs26.unlinkSync(groupFile(name));
|
|
2625
2871
|
} catch {
|
|
2626
2872
|
}
|
|
2627
2873
|
try {
|
|
2628
|
-
|
|
2874
|
+
fs26.unlinkSync(path32.join(GROUPS_DIR, `${name}.sync.json`));
|
|
2629
2875
|
} catch {
|
|
2630
2876
|
}
|
|
2631
2877
|
}
|
|
2632
2878
|
function groupExists(name) {
|
|
2633
|
-
return
|
|
2879
|
+
return fs26.existsSync(groupFile(name));
|
|
2634
2880
|
}
|
|
2635
2881
|
function addMember(groupName, member) {
|
|
2636
2882
|
const group = loadGroup(groupName);
|
|
@@ -2656,8 +2902,8 @@ function removeMember(groupName, groupPath) {
|
|
|
2656
2902
|
return group;
|
|
2657
2903
|
}
|
|
2658
2904
|
function saveSyncResult(result) {
|
|
2659
|
-
|
|
2660
|
-
|
|
2905
|
+
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2906
|
+
fs26.writeFileSync(
|
|
2661
2907
|
path32.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
2662
2908
|
JSON.stringify(result, null, 2) + "\n"
|
|
2663
2909
|
);
|
|
@@ -2665,7 +2911,7 @@ function saveSyncResult(result) {
|
|
|
2665
2911
|
function loadSyncResult(groupName) {
|
|
2666
2912
|
try {
|
|
2667
2913
|
return JSON.parse(
|
|
2668
|
-
|
|
2914
|
+
fs26.readFileSync(path32.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
2669
2915
|
);
|
|
2670
2916
|
} catch {
|
|
2671
2917
|
return null;
|
|
@@ -2678,6 +2924,86 @@ var init_group_registry = __esm({
|
|
|
2678
2924
|
}
|
|
2679
2925
|
});
|
|
2680
2926
|
|
|
2927
|
+
// src/multi-repo/graph-from-db.ts
|
|
2928
|
+
var graph_from_db_exports = {};
|
|
2929
|
+
__export(graph_from_db_exports, {
|
|
2930
|
+
loadGraphFromDB: () => loadGraphFromDB
|
|
2931
|
+
});
|
|
2932
|
+
function parseRow(row, kind) {
|
|
2933
|
+
return {
|
|
2934
|
+
id: String(row["id"] ?? ""),
|
|
2935
|
+
kind,
|
|
2936
|
+
name: String(row["name"] ?? ""),
|
|
2937
|
+
filePath: String(row["file_path"] ?? ""),
|
|
2938
|
+
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
2939
|
+
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
2940
|
+
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
2941
|
+
content: row["content"] ? String(row["content"]) : void 0,
|
|
2942
|
+
metadata: row["metadata"] ? (() => {
|
|
2943
|
+
try {
|
|
2944
|
+
return JSON.parse(String(row["metadata"]));
|
|
2945
|
+
} catch {
|
|
2946
|
+
return void 0;
|
|
2947
|
+
}
|
|
2948
|
+
})() : void 0
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
async function loadGraphFromDB(graph, db) {
|
|
2952
|
+
for (const table of ALL_NODE_TABLES) {
|
|
2953
|
+
const kind = TABLE_TO_KIND2[table];
|
|
2954
|
+
if (!kind) continue;
|
|
2955
|
+
let rows = [];
|
|
2956
|
+
try {
|
|
2957
|
+
rows = await db.query(`MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`);
|
|
2958
|
+
} catch {
|
|
2959
|
+
continue;
|
|
2960
|
+
}
|
|
2961
|
+
for (const row of rows) {
|
|
2962
|
+
const node = parseRow({
|
|
2963
|
+
id: row["n.id"],
|
|
2964
|
+
name: row["n.name"],
|
|
2965
|
+
file_path: row["n.file_path"],
|
|
2966
|
+
start_line: row["n.start_line"],
|
|
2967
|
+
end_line: row["n.end_line"],
|
|
2968
|
+
exported: row["n.exported"],
|
|
2969
|
+
content: row["n.content"],
|
|
2970
|
+
metadata: row["n.metadata"]
|
|
2971
|
+
}, kind);
|
|
2972
|
+
if (node.id && node.name) graph.addNode(node);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
try {
|
|
2976
|
+
const edgeRows = await db.query(
|
|
2977
|
+
`MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
|
|
2978
|
+
);
|
|
2979
|
+
for (const row of edgeRows) {
|
|
2980
|
+
const sourceId = String(row["a.id"] ?? "");
|
|
2981
|
+
const targetId = String(row["b.id"] ?? "");
|
|
2982
|
+
const kind = String(row["e.kind"] ?? "");
|
|
2983
|
+
if (!sourceId || !targetId || !kind) continue;
|
|
2984
|
+
const edge = {
|
|
2985
|
+
id: `${sourceId}::${kind}::${targetId}`,
|
|
2986
|
+
source: sourceId,
|
|
2987
|
+
target: targetId,
|
|
2988
|
+
kind,
|
|
2989
|
+
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
2990
|
+
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
2991
|
+
};
|
|
2992
|
+
graph.addEdge(edge);
|
|
2993
|
+
}
|
|
2994
|
+
} catch {
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
var TABLE_TO_KIND2;
|
|
2998
|
+
var init_graph_from_db = __esm({
|
|
2999
|
+
"src/multi-repo/graph-from-db.ts"() {
|
|
3000
|
+
init_schema();
|
|
3001
|
+
TABLE_TO_KIND2 = Object.fromEntries(
|
|
3002
|
+
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
3006
|
+
|
|
2681
3007
|
// src/health/dead-code.ts
|
|
2682
3008
|
function detectDeadCode(graph) {
|
|
2683
3009
|
const results = [];
|
|
@@ -4000,7 +4326,7 @@ var init_secret_scanner = __esm({
|
|
|
4000
4326
|
const ignorePatterns = [...options?.ignorePatterns ?? []];
|
|
4001
4327
|
if (options?.workspaceRoot) {
|
|
4002
4328
|
try {
|
|
4003
|
-
const raw =
|
|
4329
|
+
const raw = fs26.readFileSync(path32.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
|
|
4004
4330
|
for (const line of raw.split("\n")) {
|
|
4005
4331
|
const trimmed = line.trim();
|
|
4006
4332
|
if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
|
|
@@ -4288,10 +4614,10 @@ var init_codes = __esm({
|
|
|
4288
4614
|
}
|
|
4289
4615
|
});
|
|
4290
4616
|
function secureMkdir(dir) {
|
|
4291
|
-
|
|
4617
|
+
fs26.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
4292
4618
|
if (process.platform !== "win32") {
|
|
4293
4619
|
try {
|
|
4294
|
-
|
|
4620
|
+
fs26.chmodSync(dir, SECURE_DIR_MODE);
|
|
4295
4621
|
} catch {
|
|
4296
4622
|
}
|
|
4297
4623
|
}
|
|
@@ -4299,17 +4625,17 @@ function secureMkdir(dir) {
|
|
|
4299
4625
|
function secureChmodFile(file) {
|
|
4300
4626
|
if (process.platform === "win32") return;
|
|
4301
4627
|
try {
|
|
4302
|
-
|
|
4628
|
+
fs26.chmodSync(file, SECURE_FILE_MODE);
|
|
4303
4629
|
} catch {
|
|
4304
4630
|
}
|
|
4305
4631
|
}
|
|
4306
4632
|
function tightenDbFiles(dir) {
|
|
4307
4633
|
if (process.platform === "win32") return;
|
|
4308
|
-
if (!
|
|
4309
|
-
for (const name of
|
|
4634
|
+
if (!fs26.existsSync(dir)) return;
|
|
4635
|
+
for (const name of fs26.readdirSync(dir)) {
|
|
4310
4636
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
4311
4637
|
try {
|
|
4312
|
-
|
|
4638
|
+
fs26.chmodSync(path32.join(dir, name), SECURE_FILE_MODE);
|
|
4313
4639
|
} catch {
|
|
4314
4640
|
}
|
|
4315
4641
|
}
|
|
@@ -4647,8 +4973,8 @@ function decryptSecrets(encrypted) {
|
|
|
4647
4973
|
return JSON.parse(plaintext.toString("utf8"));
|
|
4648
4974
|
}
|
|
4649
4975
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
4650
|
-
if (!
|
|
4651
|
-
const blob =
|
|
4976
|
+
if (!fs26.existsSync(secretsPath)) return {};
|
|
4977
|
+
const blob = fs26.readFileSync(secretsPath);
|
|
4652
4978
|
return decryptSecrets(blob);
|
|
4653
4979
|
}
|
|
4654
4980
|
function getSecret(key, secretsPath = getSecretsPath()) {
|
|
@@ -4669,11 +4995,18 @@ function getSessionTtlMs() {
|
|
|
4669
4995
|
const hours = parseInt(process.env["CODE_INTEL_SESSION_TTL_HOURS"] ?? "8", 10);
|
|
4670
4996
|
return (isNaN(hours) ? 8 : hours) * 60 * 60 * 1e3;
|
|
4671
4997
|
}
|
|
4672
|
-
function createSession(user) {
|
|
4998
|
+
function createSession(user, rememberMe = false) {
|
|
4673
4999
|
const sessionId = v4();
|
|
4674
|
-
const
|
|
4675
|
-
|
|
4676
|
-
|
|
5000
|
+
const ttlMs = rememberMe ? REMEMBER_ME_TTL_MS : getSessionTtlMs();
|
|
5001
|
+
const expiresAt = Date.now() + ttlMs;
|
|
5002
|
+
sessionStore.set(sessionId, {
|
|
5003
|
+
userId: user.id,
|
|
5004
|
+
username: user.username,
|
|
5005
|
+
role: user.role,
|
|
5006
|
+
expiresAt,
|
|
5007
|
+
ttlMs
|
|
5008
|
+
});
|
|
5009
|
+
return { sessionId, ttlMs };
|
|
4677
5010
|
}
|
|
4678
5011
|
function getSession(sessionId) {
|
|
4679
5012
|
const entry = sessionStore.get(sessionId);
|
|
@@ -4682,10 +5015,9 @@ function getSession(sessionId) {
|
|
|
4682
5015
|
sessionStore.delete(sessionId);
|
|
4683
5016
|
return null;
|
|
4684
5017
|
}
|
|
4685
|
-
const ttlMs = getSessionTtlMs();
|
|
4686
5018
|
const remaining = entry.expiresAt - Date.now();
|
|
4687
|
-
if (remaining < ttlMs * 0.75) {
|
|
4688
|
-
entry.expiresAt = Date.now() + ttlMs;
|
|
5019
|
+
if (remaining < entry.ttlMs * 0.75) {
|
|
5020
|
+
entry.expiresAt = Date.now() + entry.ttlMs;
|
|
4689
5021
|
}
|
|
4690
5022
|
return entry;
|
|
4691
5023
|
}
|
|
@@ -4705,7 +5037,7 @@ function authMiddleware(req, res, next) {
|
|
|
4705
5037
|
const session = getSession(sessionId);
|
|
4706
5038
|
if (session) {
|
|
4707
5039
|
req.user = { id: session.userId, username: session.username, role: session.role, authMethod: "session" };
|
|
4708
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
5040
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, session.ttlMs));
|
|
4709
5041
|
next();
|
|
4710
5042
|
return;
|
|
4711
5043
|
}
|
|
@@ -4875,9 +5207,9 @@ function parseCookies(cookieHeader) {
|
|
|
4875
5207
|
}
|
|
4876
5208
|
return result;
|
|
4877
5209
|
}
|
|
4878
|
-
function buildSessionCookie(sessionId) {
|
|
5210
|
+
function buildSessionCookie(sessionId, ttlMs) {
|
|
4879
5211
|
const isProduction = process.env["NODE_ENV"] === "production";
|
|
4880
|
-
const maxAge = Math.floor(getSessionTtlMs() / 1e3);
|
|
5212
|
+
const maxAge = Math.floor((ttlMs ?? getSessionTtlMs()) / 1e3);
|
|
4881
5213
|
const parts = [
|
|
4882
5214
|
`${SESSION_COOKIE_NAME}=${encodeURIComponent(sessionId)}`,
|
|
4883
5215
|
`HttpOnly`,
|
|
@@ -4894,7 +5226,7 @@ function clearSessionCookie() {
|
|
|
4894
5226
|
async function verifyPassword(plain, hash) {
|
|
4895
5227
|
return bcrypt.compare(plain, hash);
|
|
4896
5228
|
}
|
|
4897
|
-
var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
|
|
5229
|
+
var sessionStore, SESSION_COOKIE_NAME, REMEMBER_ME_TTL_MS, ROLE_RANK;
|
|
4898
5230
|
var init_middleware = __esm({
|
|
4899
5231
|
"src/auth/middleware.ts"() {
|
|
4900
5232
|
init_users_db();
|
|
@@ -4902,6 +5234,7 @@ var init_middleware = __esm({
|
|
|
4902
5234
|
init_secret_store();
|
|
4903
5235
|
sessionStore = /* @__PURE__ */ new Map();
|
|
4904
5236
|
SESSION_COOKIE_NAME = "code_intel_session";
|
|
5237
|
+
REMEMBER_ME_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
4905
5238
|
ROLE_RANK = {
|
|
4906
5239
|
viewer: 1,
|
|
4907
5240
|
"repo-owner": 2,
|
|
@@ -4982,236 +5315,64 @@ var init_websocket_server = __esm({
|
|
|
4982
5315
|
wss;
|
|
4983
5316
|
clients = /* @__PURE__ */ new Set();
|
|
4984
5317
|
constructor(httpServer) {
|
|
4985
|
-
this.wss = new WebSocketServer({ server: httpServer, path: "/ws" });
|
|
4986
|
-
this.wss.on("connection", (ws, req) => {
|
|
4987
|
-
const user = verifyWebSocketHandshake(req);
|
|
4988
|
-
if (!user) {
|
|
4989
|
-
logger_default.warn("[ws] rejected unauthenticated connection");
|
|
4990
|
-
ws.close(4401, "Unauthorized");
|
|
4991
|
-
return;
|
|
4992
|
-
}
|
|
4993
|
-
logger_default.info(`[ws] client connected: ${user.username}`);
|
|
4994
|
-
this.clients.add(ws);
|
|
4995
|
-
ws.on("close", () => {
|
|
4996
|
-
this.clients.delete(ws);
|
|
4997
|
-
logger_default.info(`[ws] client disconnected: ${user.username}`);
|
|
4998
|
-
});
|
|
4999
|
-
ws.on("error", (err) => {
|
|
5000
|
-
logger_default.warn("[ws] client error:", err.message);
|
|
5001
|
-
this.clients.delete(ws);
|
|
5002
|
-
});
|
|
5003
|
-
});
|
|
5004
|
-
this.wss.on("error", (err) => {
|
|
5005
|
-
logger_default.warn("[ws] server error:", err.message);
|
|
5006
|
-
});
|
|
5007
|
-
}
|
|
5008
|
-
/** Broadcast a message to all authenticated connected clients. */
|
|
5009
|
-
broadcast(msg) {
|
|
5010
|
-
const payload = JSON.stringify(msg);
|
|
5011
|
-
let sent = 0;
|
|
5012
|
-
for (const client of this.clients) {
|
|
5013
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
5014
|
-
client.send(payload);
|
|
5015
|
-
sent++;
|
|
5016
|
-
}
|
|
5017
|
-
}
|
|
5018
|
-
logger_default.info(`[ws] broadcast \u2192 ${sent} client(s)`);
|
|
5019
|
-
}
|
|
5020
|
-
get clientCount() {
|
|
5021
|
-
return this.clients.size;
|
|
5022
|
-
}
|
|
5023
|
-
close() {
|
|
5024
|
-
for (const client of this.clients) {
|
|
5025
|
-
try {
|
|
5026
|
-
client.close();
|
|
5027
|
-
} catch {
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5030
|
-
this.clients.clear();
|
|
5031
|
-
this.wss.close();
|
|
5032
|
-
}
|
|
5033
|
-
};
|
|
5034
|
-
}
|
|
5035
|
-
});
|
|
5036
|
-
|
|
5037
|
-
// src/graph/knowledge-graph.ts
|
|
5038
|
-
function createKnowledgeGraph() {
|
|
5039
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
5040
|
-
const edges = /* @__PURE__ */ new Map();
|
|
5041
|
-
const edgesByKind = /* @__PURE__ */ new Map();
|
|
5042
|
-
const edgesFromNode = /* @__PURE__ */ new Map();
|
|
5043
|
-
const edgesToNode = /* @__PURE__ */ new Map();
|
|
5044
|
-
function indexEdge(edge) {
|
|
5045
|
-
let kindSet = edgesByKind.get(edge.kind);
|
|
5046
|
-
if (!kindSet) {
|
|
5047
|
-
kindSet = /* @__PURE__ */ new Set();
|
|
5048
|
-
edgesByKind.set(edge.kind, kindSet);
|
|
5049
|
-
}
|
|
5050
|
-
kindSet.add(edge.id);
|
|
5051
|
-
let fromSet = edgesFromNode.get(edge.source);
|
|
5052
|
-
if (!fromSet) {
|
|
5053
|
-
fromSet = /* @__PURE__ */ new Set();
|
|
5054
|
-
edgesFromNode.set(edge.source, fromSet);
|
|
5055
|
-
}
|
|
5056
|
-
fromSet.add(edge.id);
|
|
5057
|
-
let toSet = edgesToNode.get(edge.target);
|
|
5058
|
-
if (!toSet) {
|
|
5059
|
-
toSet = /* @__PURE__ */ new Set();
|
|
5060
|
-
edgesToNode.set(edge.target, toSet);
|
|
5061
|
-
}
|
|
5062
|
-
toSet.add(edge.id);
|
|
5063
|
-
}
|
|
5064
|
-
function unindexEdge(edge) {
|
|
5065
|
-
edgesByKind.get(edge.kind)?.delete(edge.id);
|
|
5066
|
-
edgesFromNode.get(edge.source)?.delete(edge.id);
|
|
5067
|
-
edgesToNode.get(edge.target)?.delete(edge.id);
|
|
5068
|
-
}
|
|
5069
|
-
return {
|
|
5070
|
-
addNode(node) {
|
|
5071
|
-
nodes.set(node.id, node);
|
|
5072
|
-
},
|
|
5073
|
-
addEdge(edge) {
|
|
5074
|
-
edges.set(edge.id, edge);
|
|
5075
|
-
indexEdge(edge);
|
|
5076
|
-
},
|
|
5077
|
-
getNode(id) {
|
|
5078
|
-
return nodes.get(id);
|
|
5079
|
-
},
|
|
5080
|
-
getEdge(id) {
|
|
5081
|
-
return edges.get(id);
|
|
5082
|
-
},
|
|
5083
|
-
*findEdgesByKind(kind) {
|
|
5084
|
-
const ids = edgesByKind.get(kind);
|
|
5085
|
-
if (!ids) return;
|
|
5086
|
-
for (const id of ids) {
|
|
5087
|
-
const edge = edges.get(id);
|
|
5088
|
-
if (edge) yield edge;
|
|
5089
|
-
}
|
|
5090
|
-
},
|
|
5091
|
-
*findEdgesFrom(sourceId) {
|
|
5092
|
-
const ids = edgesFromNode.get(sourceId);
|
|
5093
|
-
if (!ids) return;
|
|
5094
|
-
for (const id of ids) {
|
|
5095
|
-
const edge = edges.get(id);
|
|
5096
|
-
if (edge) yield edge;
|
|
5097
|
-
}
|
|
5098
|
-
},
|
|
5099
|
-
*findEdgesTo(targetId) {
|
|
5100
|
-
const ids = edgesToNode.get(targetId);
|
|
5101
|
-
if (!ids) return;
|
|
5102
|
-
for (const id of ids) {
|
|
5103
|
-
const edge = edges.get(id);
|
|
5104
|
-
if (edge) yield edge;
|
|
5318
|
+
this.wss = new WebSocketServer({ server: httpServer, path: "/ws" });
|
|
5319
|
+
this.wss.on("connection", (ws, req) => {
|
|
5320
|
+
const user = verifyWebSocketHandshake(req);
|
|
5321
|
+
if (!user) {
|
|
5322
|
+
logger_default.warn("[ws] rejected unauthenticated connection");
|
|
5323
|
+
ws.close(4401, "Unauthorized");
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
logger_default.info(`[ws] client connected: ${user.username}`);
|
|
5327
|
+
this.clients.add(ws);
|
|
5328
|
+
ws.on("close", () => {
|
|
5329
|
+
this.clients.delete(ws);
|
|
5330
|
+
logger_default.info(`[ws] client disconnected: ${user.username}`);
|
|
5331
|
+
});
|
|
5332
|
+
ws.on("error", (err) => {
|
|
5333
|
+
logger_default.warn("[ws] client error:", err.message);
|
|
5334
|
+
this.clients.delete(ws);
|
|
5335
|
+
});
|
|
5336
|
+
});
|
|
5337
|
+
this.wss.on("error", (err) => {
|
|
5338
|
+
logger_default.warn("[ws] server error:", err.message);
|
|
5339
|
+
});
|
|
5105
5340
|
}
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
for (const
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
edges.delete(edgeId);
|
|
5341
|
+
/** Broadcast a message to all authenticated connected clients. */
|
|
5342
|
+
broadcast(msg) {
|
|
5343
|
+
const payload = JSON.stringify(msg);
|
|
5344
|
+
let sent = 0;
|
|
5345
|
+
for (const client of this.clients) {
|
|
5346
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
5347
|
+
client.send(payload);
|
|
5348
|
+
sent++;
|
|
5115
5349
|
}
|
|
5116
5350
|
}
|
|
5351
|
+
logger_default.info(`[ws] broadcast \u2192 ${sent} client(s)`);
|
|
5117
5352
|
}
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5353
|
+
get clientCount() {
|
|
5354
|
+
return this.clients.size;
|
|
5355
|
+
}
|
|
5356
|
+
close() {
|
|
5357
|
+
for (const client of this.clients) {
|
|
5358
|
+
try {
|
|
5359
|
+
client.close();
|
|
5360
|
+
} catch {
|
|
5125
5361
|
}
|
|
5126
5362
|
}
|
|
5363
|
+
this.clients.clear();
|
|
5364
|
+
this.wss.close();
|
|
5127
5365
|
}
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
},
|
|
5132
|
-
removeEdge(id) {
|
|
5133
|
-
const edge = edges.get(id);
|
|
5134
|
-
if (edge) {
|
|
5135
|
-
unindexEdge(edge);
|
|
5136
|
-
edges.delete(id);
|
|
5137
|
-
}
|
|
5138
|
-
},
|
|
5139
|
-
*allNodes() {
|
|
5140
|
-
yield* nodes.values();
|
|
5141
|
-
},
|
|
5142
|
-
*allEdges() {
|
|
5143
|
-
yield* edges.values();
|
|
5144
|
-
},
|
|
5145
|
-
get size() {
|
|
5146
|
-
return { nodes: nodes.size, edges: edges.size };
|
|
5147
|
-
},
|
|
5148
|
-
clear() {
|
|
5149
|
-
nodes.clear();
|
|
5150
|
-
edges.clear();
|
|
5151
|
-
edgesByKind.clear();
|
|
5152
|
-
edgesFromNode.clear();
|
|
5153
|
-
edgesToNode.clear();
|
|
5154
|
-
}
|
|
5155
|
-
};
|
|
5156
|
-
}
|
|
5366
|
+
};
|
|
5367
|
+
}
|
|
5368
|
+
});
|
|
5157
5369
|
|
|
5158
5370
|
// src/graph/index.ts
|
|
5371
|
+
init_knowledge_graph();
|
|
5159
5372
|
init_id_generator();
|
|
5160
5373
|
|
|
5161
|
-
// src/storage/schema.ts
|
|
5162
|
-
var NODE_TABLE_MAP = {
|
|
5163
|
-
file: "file_nodes",
|
|
5164
|
-
directory: "dir_nodes",
|
|
5165
|
-
function: "func_nodes",
|
|
5166
|
-
class: "class_nodes",
|
|
5167
|
-
interface: "iface_nodes",
|
|
5168
|
-
method: "method_nodes",
|
|
5169
|
-
constructor: "ctor_nodes",
|
|
5170
|
-
variable: "var_nodes",
|
|
5171
|
-
property: "prop_nodes",
|
|
5172
|
-
struct: "struct_nodes",
|
|
5173
|
-
enum: "enum_nodes",
|
|
5174
|
-
trait: "trait_nodes",
|
|
5175
|
-
namespace: "ns_nodes",
|
|
5176
|
-
module: "mod_nodes",
|
|
5177
|
-
type_alias: "type_nodes",
|
|
5178
|
-
constant: "const_nodes",
|
|
5179
|
-
route: "route_nodes",
|
|
5180
|
-
cluster: "cluster_nodes",
|
|
5181
|
-
flow: "flow_nodes",
|
|
5182
|
-
vulnerability: "vuln_nodes"
|
|
5183
|
-
};
|
|
5184
|
-
var ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
|
|
5185
|
-
function getCreateNodeTableDDL(tableName) {
|
|
5186
|
-
return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
|
|
5187
|
-
id STRING,
|
|
5188
|
-
name STRING,
|
|
5189
|
-
file_path STRING,
|
|
5190
|
-
start_line INT64,
|
|
5191
|
-
end_line INT64,
|
|
5192
|
-
exported BOOLEAN,
|
|
5193
|
-
content STRING,
|
|
5194
|
-
metadata STRING,
|
|
5195
|
-
PRIMARY KEY (id)
|
|
5196
|
-
)`;
|
|
5197
|
-
}
|
|
5198
|
-
function getCreateEdgeTableDDL() {
|
|
5199
|
-
const uniqueTables = ALL_NODE_TABLES;
|
|
5200
|
-
const fromToPairs = [];
|
|
5201
|
-
for (const from of uniqueTables) {
|
|
5202
|
-
for (const to of uniqueTables) {
|
|
5203
|
-
fromToPairs.push(`FROM ${from} TO ${to}`);
|
|
5204
|
-
}
|
|
5205
|
-
}
|
|
5206
|
-
return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
|
|
5207
|
-
${fromToPairs.join(",\n ")},
|
|
5208
|
-
kind STRING,
|
|
5209
|
-
weight DOUBLE,
|
|
5210
|
-
label STRING
|
|
5211
|
-
)`];
|
|
5212
|
-
}
|
|
5213
|
-
|
|
5214
5374
|
// src/graph/lazy-knowledge-graph.ts
|
|
5375
|
+
init_schema();
|
|
5215
5376
|
init_logger();
|
|
5216
5377
|
Object.fromEntries(
|
|
5217
5378
|
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
@@ -6189,7 +6350,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
6189
6350
|
]);
|
|
6190
6351
|
function loadIgnorePatterns(workspaceRoot) {
|
|
6191
6352
|
try {
|
|
6192
|
-
const raw =
|
|
6353
|
+
const raw = fs26.readFileSync(path32.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
6193
6354
|
const extras = /* @__PURE__ */ new Set();
|
|
6194
6355
|
for (const line of raw.split("\n")) {
|
|
6195
6356
|
const trimmed = line.trim();
|
|
@@ -6213,7 +6374,7 @@ var scanPhase = {
|
|
|
6213
6374
|
function walk(dir) {
|
|
6214
6375
|
let entries;
|
|
6215
6376
|
try {
|
|
6216
|
-
entries =
|
|
6377
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
6217
6378
|
} catch {
|
|
6218
6379
|
return;
|
|
6219
6380
|
}
|
|
@@ -6230,7 +6391,7 @@ var scanPhase = {
|
|
|
6230
6391
|
if (!extensions.has(ext)) continue;
|
|
6231
6392
|
const fullPath = path32.join(dir, name);
|
|
6232
6393
|
try {
|
|
6233
|
-
const stat =
|
|
6394
|
+
const stat = fs26.statSync(fullPath);
|
|
6234
6395
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
6235
6396
|
} catch {
|
|
6236
6397
|
continue;
|
|
@@ -6452,8 +6613,8 @@ var LLMGovernanceLogger = class {
|
|
|
6452
6613
|
...entry
|
|
6453
6614
|
};
|
|
6454
6615
|
const logPath = this.getLogPath();
|
|
6455
|
-
|
|
6456
|
-
|
|
6616
|
+
fs26.mkdirSync(path32.dirname(logPath), { recursive: true });
|
|
6617
|
+
fs26.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
6457
6618
|
} catch {
|
|
6458
6619
|
}
|
|
6459
6620
|
}
|
|
@@ -6463,7 +6624,7 @@ var LLMGovernanceLogger = class {
|
|
|
6463
6624
|
*/
|
|
6464
6625
|
readLog(limit = 100) {
|
|
6465
6626
|
try {
|
|
6466
|
-
const raw =
|
|
6627
|
+
const raw = fs26.readFileSync(this.getLogPath(), "utf-8");
|
|
6467
6628
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
6468
6629
|
return lines.map((l) => JSON.parse(l));
|
|
6469
6630
|
} catch {
|
|
@@ -6854,7 +7015,7 @@ init_embedder();
|
|
|
6854
7015
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6855
7016
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
|
|
6856
7017
|
const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6857
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
7018
|
+
const hasVectorDb = Boolean(vectorDbPath && fs26.existsSync(vectorDbPath));
|
|
6858
7019
|
if (!hasVectorDb) {
|
|
6859
7020
|
const bm25Results2 = await bm25Promise;
|
|
6860
7021
|
return {
|
|
@@ -6919,6 +7080,41 @@ function nodeToDoc(node) {
|
|
|
6919
7080
|
(node.content ?? "").slice(0, 1e3)
|
|
6920
7081
|
].join(" ");
|
|
6921
7082
|
}
|
|
7083
|
+
function heapTopK(scores, k) {
|
|
7084
|
+
if (k <= 0) return [];
|
|
7085
|
+
const heap = [];
|
|
7086
|
+
function heapifyUp(i) {
|
|
7087
|
+
while (i > 0) {
|
|
7088
|
+
const parent = i - 1 >> 1;
|
|
7089
|
+
if (heap[parent][1] > heap[i][1]) {
|
|
7090
|
+
[heap[parent], heap[i]] = [heap[i], heap[parent]];
|
|
7091
|
+
i = parent;
|
|
7092
|
+
} else break;
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
function heapifyDown(i) {
|
|
7096
|
+
const n = heap.length;
|
|
7097
|
+
while (true) {
|
|
7098
|
+
let smallest = i;
|
|
7099
|
+
const l = 2 * i + 1, r = 2 * i + 2;
|
|
7100
|
+
if (l < n && heap[l][1] < heap[smallest][1]) smallest = l;
|
|
7101
|
+
if (r < n && heap[r][1] < heap[smallest][1]) smallest = r;
|
|
7102
|
+
if (smallest === i) break;
|
|
7103
|
+
[heap[smallest], heap[i]] = [heap[i], heap[smallest]];
|
|
7104
|
+
i = smallest;
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
for (const [nodeId, score] of scores) {
|
|
7108
|
+
if (heap.length < k) {
|
|
7109
|
+
heap.push([nodeId, score]);
|
|
7110
|
+
heapifyUp(heap.length - 1);
|
|
7111
|
+
} else if (score > heap[0][1]) {
|
|
7112
|
+
heap[0] = [nodeId, score];
|
|
7113
|
+
heapifyDown(0);
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
return heap.sort((a, b) => b[1] - a[1]);
|
|
7117
|
+
}
|
|
6922
7118
|
var Bm25Index = class {
|
|
6923
7119
|
constructor(dbPath) {
|
|
6924
7120
|
this.dbPath = dbPath;
|
|
@@ -6971,10 +7167,10 @@ var Bm25Index = class {
|
|
|
6971
7167
|
postings.push({ nodeId, tf: count });
|
|
6972
7168
|
}
|
|
6973
7169
|
}
|
|
6974
|
-
|
|
7170
|
+
fs26.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
6975
7171
|
for (const f of [this.dbPath, `${this.dbPath}-shm`, `${this.dbPath}-wal`]) {
|
|
6976
7172
|
try {
|
|
6977
|
-
if (
|
|
7173
|
+
if (fs26.existsSync(f)) fs26.unlinkSync(f);
|
|
6978
7174
|
} catch {
|
|
6979
7175
|
}
|
|
6980
7176
|
}
|
|
@@ -7012,7 +7208,7 @@ var Bm25Index = class {
|
|
|
7012
7208
|
* Called once on `serve` startup.
|
|
7013
7209
|
*/
|
|
7014
7210
|
load() {
|
|
7015
|
-
if (!
|
|
7211
|
+
if (!fs26.existsSync(this.dbPath)) return;
|
|
7016
7212
|
const db = new Database2(this.dbPath, { readonly: true });
|
|
7017
7213
|
try {
|
|
7018
7214
|
const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
|
|
@@ -7046,8 +7242,13 @@ var Bm25Index = class {
|
|
|
7046
7242
|
}
|
|
7047
7243
|
// ── Search ──────────────────────────────────────────────────────────────────
|
|
7048
7244
|
/**
|
|
7049
|
-
* BM25 search.
|
|
7050
|
-
*
|
|
7245
|
+
* BM25 search.
|
|
7246
|
+
*
|
|
7247
|
+
* Performance strategy:
|
|
7248
|
+
* 1. Skip ultra-high-df terms (df/N > 0.6) — near-zero IDF, dominate posting
|
|
7249
|
+
* lists for common words like "function", "return", "export" in large repos.
|
|
7250
|
+
* 2. Min-heap top-K selection — O(n log k) instead of full O(n log n) sort.
|
|
7251
|
+
* For k=10 and n=30,000 candidates this is ~10× faster than Array.sort.
|
|
7051
7252
|
*/
|
|
7052
7253
|
search(query, limit) {
|
|
7053
7254
|
if (!this._loaded || this.invertedIndex.size === 0) return [];
|
|
@@ -7060,6 +7261,7 @@ var Bm25Index = class {
|
|
|
7060
7261
|
const postings = this.invertedIndex.get(term);
|
|
7061
7262
|
if (!postings) continue;
|
|
7062
7263
|
const df = postings.length;
|
|
7264
|
+
if (N > 100 && df / N > 0.6) continue;
|
|
7063
7265
|
const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
|
|
7064
7266
|
for (const { nodeId, tf } of postings) {
|
|
7065
7267
|
const dl = this.docLengths.get(nodeId) ?? avgdl;
|
|
@@ -7067,10 +7269,9 @@ var Bm25Index = class {
|
|
|
7067
7269
|
scores.set(nodeId, (scores.get(nodeId) ?? 0) + score);
|
|
7068
7270
|
}
|
|
7069
7271
|
}
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
return topK.map(([nodeId, score]) => {
|
|
7272
|
+
if (scores.size === 0) return [];
|
|
7273
|
+
const topEntries = heapTopK(scores, limit);
|
|
7274
|
+
return topEntries.map(([nodeId, score]) => {
|
|
7074
7275
|
const meta = this.nodeMeta.get(nodeId);
|
|
7075
7276
|
return {
|
|
7076
7277
|
nodeId,
|
|
@@ -7089,7 +7290,7 @@ var Bm25Index = class {
|
|
|
7089
7290
|
* Works even if `load()` was not called (reads affected terms directly from DB).
|
|
7090
7291
|
*/
|
|
7091
7292
|
updateNodes(nodes) {
|
|
7092
|
-
if (!
|
|
7293
|
+
if (!fs26.existsSync(this.dbPath)) return;
|
|
7093
7294
|
if (nodes.length === 0) return;
|
|
7094
7295
|
const changedIds = new Set(nodes.map((n) => n.id));
|
|
7095
7296
|
const newTermFreqs = /* @__PURE__ */ new Map();
|
|
@@ -7165,56 +7366,15 @@ var Bm25Index = class {
|
|
|
7165
7366
|
function getBm25DbPath(workspaceRoot) {
|
|
7166
7367
|
return path32.join(workspaceRoot, ".code-intel", "bm25.db");
|
|
7167
7368
|
}
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
this.readOnly = readOnly;
|
|
7176
|
-
}
|
|
7177
|
-
async init() {
|
|
7178
|
-
if (!this.readOnly) {
|
|
7179
|
-
fs25.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
7180
|
-
}
|
|
7181
|
-
this.db = new Database(this.dbPath, 0, true, this.readOnly);
|
|
7182
|
-
await this.db.init();
|
|
7183
|
-
this.conn = new Connection(this.db);
|
|
7184
|
-
await this.conn.init();
|
|
7185
|
-
}
|
|
7186
|
-
async query(cypher) {
|
|
7187
|
-
if (!this.conn) throw new Error("Database not initialized");
|
|
7188
|
-
const result = await this.conn.query(cypher);
|
|
7189
|
-
const qr = Array.isArray(result) ? result[0] : result;
|
|
7190
|
-
const rows = await qr.getAll();
|
|
7191
|
-
qr.close();
|
|
7192
|
-
return rows;
|
|
7193
|
-
}
|
|
7194
|
-
async execute(cypher) {
|
|
7195
|
-
if (!this.conn) throw new Error("Database not initialized");
|
|
7196
|
-
const result = await this.conn.query(cypher);
|
|
7197
|
-
const qr = Array.isArray(result) ? result[0] : result;
|
|
7198
|
-
qr.close();
|
|
7199
|
-
}
|
|
7200
|
-
close() {
|
|
7201
|
-
try {
|
|
7202
|
-
this.conn?.closeSync();
|
|
7203
|
-
} catch {
|
|
7204
|
-
}
|
|
7205
|
-
try {
|
|
7206
|
-
this.db?.closeSync();
|
|
7207
|
-
} catch {
|
|
7208
|
-
}
|
|
7209
|
-
this.conn = null;
|
|
7210
|
-
this.db = null;
|
|
7211
|
-
}
|
|
7212
|
-
get isOpen() {
|
|
7213
|
-
return this.conn !== null;
|
|
7214
|
-
}
|
|
7215
|
-
};
|
|
7369
|
+
|
|
7370
|
+
// src/storage/index.ts
|
|
7371
|
+
init_db_manager();
|
|
7372
|
+
init_schema();
|
|
7373
|
+
|
|
7374
|
+
// src/storage/csv-writer.ts
|
|
7375
|
+
init_schema();
|
|
7216
7376
|
function writeNodeCSVs(graph, outputDir) {
|
|
7217
|
-
|
|
7377
|
+
fs26.mkdirSync(outputDir, { recursive: true });
|
|
7218
7378
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
7219
7379
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
7220
7380
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -7242,12 +7402,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
7242
7402
|
);
|
|
7243
7403
|
}
|
|
7244
7404
|
for (const [table, lines] of tableBuffers) {
|
|
7245
|
-
|
|
7405
|
+
fs26.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
7246
7406
|
}
|
|
7247
7407
|
return tableFilePaths;
|
|
7248
7408
|
}
|
|
7249
7409
|
function writeEdgeCSV(graph, outputDir) {
|
|
7250
|
-
|
|
7410
|
+
fs26.mkdirSync(outputDir, { recursive: true });
|
|
7251
7411
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
7252
7412
|
const groups = /* @__PURE__ */ new Map();
|
|
7253
7413
|
for (const edge of graph.allEdges()) {
|
|
@@ -7273,7 +7433,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
7273
7433
|
}
|
|
7274
7434
|
const result = [];
|
|
7275
7435
|
for (const group of groups.values()) {
|
|
7276
|
-
|
|
7436
|
+
fs26.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
7277
7437
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
7278
7438
|
}
|
|
7279
7439
|
return result;
|
|
@@ -7290,6 +7450,9 @@ function escapeNewlines(s) {
|
|
|
7290
7450
|
if (!s.includes("\n") && !s.includes("\r")) return s;
|
|
7291
7451
|
return s.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
7292
7452
|
}
|
|
7453
|
+
|
|
7454
|
+
// src/storage/graph-loader.ts
|
|
7455
|
+
init_schema();
|
|
7293
7456
|
async function loadGraphToDB(graph, dbManager) {
|
|
7294
7457
|
for (const table of ALL_NODE_TABLES) {
|
|
7295
7458
|
await dbManager.execute(getCreateNodeTableDDL(table));
|
|
@@ -7301,7 +7464,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7301
7464
|
} catch {
|
|
7302
7465
|
}
|
|
7303
7466
|
}
|
|
7304
|
-
const tmpDir =
|
|
7467
|
+
const tmpDir = fs26.mkdtempSync(path32.join(os13.tmpdir(), "code-intel-csv-"));
|
|
7305
7468
|
try {
|
|
7306
7469
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
7307
7470
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -7320,8 +7483,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7320
7483
|
}
|
|
7321
7484
|
let nodeCount = 0;
|
|
7322
7485
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
7323
|
-
if (!
|
|
7324
|
-
const stat =
|
|
7486
|
+
if (!fs26.existsSync(csvPath)) continue;
|
|
7487
|
+
const stat = fs26.statSync(csvPath);
|
|
7325
7488
|
if (stat.size < 50) continue;
|
|
7326
7489
|
try {
|
|
7327
7490
|
await dbManager.execute(
|
|
@@ -7334,8 +7497,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7334
7497
|
}
|
|
7335
7498
|
let edgeCount = 0;
|
|
7336
7499
|
for (const group of edgeGroups) {
|
|
7337
|
-
if (!
|
|
7338
|
-
const stat =
|
|
7500
|
+
if (!fs26.existsSync(group.filePath)) continue;
|
|
7501
|
+
const stat = fs26.statSync(group.filePath);
|
|
7339
7502
|
if (stat.size < 50) continue;
|
|
7340
7503
|
try {
|
|
7341
7504
|
await dbManager.execute(
|
|
@@ -7349,7 +7512,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7349
7512
|
return { nodeCount, edgeCount };
|
|
7350
7513
|
} finally {
|
|
7351
7514
|
try {
|
|
7352
|
-
|
|
7515
|
+
fs26.rmSync(tmpDir, { recursive: true, force: true });
|
|
7353
7516
|
} catch {
|
|
7354
7517
|
}
|
|
7355
7518
|
}
|
|
@@ -7405,15 +7568,15 @@ var GLOBAL_DIR = path32.join(os13.homedir(), ".code-intel");
|
|
|
7405
7568
|
var REPOS_FILE = path32.join(GLOBAL_DIR, "repos.json");
|
|
7406
7569
|
function loadRegistry() {
|
|
7407
7570
|
try {
|
|
7408
|
-
const data =
|
|
7571
|
+
const data = fs26.readFileSync(REPOS_FILE, "utf-8");
|
|
7409
7572
|
return JSON.parse(data);
|
|
7410
7573
|
} catch {
|
|
7411
7574
|
return [];
|
|
7412
7575
|
}
|
|
7413
7576
|
}
|
|
7414
7577
|
function saveRegistry(entries) {
|
|
7415
|
-
|
|
7416
|
-
|
|
7578
|
+
fs26.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
7579
|
+
fs26.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
7417
7580
|
}
|
|
7418
7581
|
function upsertRepo(entry) {
|
|
7419
7582
|
const entries = loadRegistry();
|
|
@@ -7431,12 +7594,12 @@ function removeRepo(repoPath) {
|
|
|
7431
7594
|
}
|
|
7432
7595
|
function saveMetadata(repoDir, metadata) {
|
|
7433
7596
|
const metaDir = path32.join(repoDir, ".code-intel");
|
|
7434
|
-
|
|
7435
|
-
|
|
7597
|
+
fs26.mkdirSync(metaDir, { recursive: true });
|
|
7598
|
+
fs26.writeFileSync(path32.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
7436
7599
|
}
|
|
7437
7600
|
function loadMetadata(repoDir) {
|
|
7438
7601
|
try {
|
|
7439
|
-
const data =
|
|
7602
|
+
const data = fs26.readFileSync(path32.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
7440
7603
|
return JSON.parse(data);
|
|
7441
7604
|
} catch {
|
|
7442
7605
|
return null;
|
|
@@ -7451,78 +7614,9 @@ function getVectorDbPath(repoDir) {
|
|
|
7451
7614
|
|
|
7452
7615
|
// src/mcp-server/server.ts
|
|
7453
7616
|
init_group_registry();
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
7458
|
-
);
|
|
7459
|
-
function parseRow(row, kind) {
|
|
7460
|
-
return {
|
|
7461
|
-
id: String(row["id"] ?? ""),
|
|
7462
|
-
kind,
|
|
7463
|
-
name: String(row["name"] ?? ""),
|
|
7464
|
-
filePath: String(row["file_path"] ?? ""),
|
|
7465
|
-
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
7466
|
-
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
7467
|
-
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
7468
|
-
content: row["content"] ? String(row["content"]) : void 0,
|
|
7469
|
-
metadata: row["metadata"] ? (() => {
|
|
7470
|
-
try {
|
|
7471
|
-
return JSON.parse(String(row["metadata"]));
|
|
7472
|
-
} catch {
|
|
7473
|
-
return void 0;
|
|
7474
|
-
}
|
|
7475
|
-
})() : void 0
|
|
7476
|
-
};
|
|
7477
|
-
}
|
|
7478
|
-
async function loadGraphFromDB(graph, db) {
|
|
7479
|
-
for (const table of ALL_NODE_TABLES) {
|
|
7480
|
-
const kind = TABLE_TO_KIND2[table];
|
|
7481
|
-
if (!kind) continue;
|
|
7482
|
-
let rows = [];
|
|
7483
|
-
try {
|
|
7484
|
-
rows = await db.query(`MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`);
|
|
7485
|
-
} catch {
|
|
7486
|
-
continue;
|
|
7487
|
-
}
|
|
7488
|
-
for (const row of rows) {
|
|
7489
|
-
const node = parseRow({
|
|
7490
|
-
id: row["n.id"],
|
|
7491
|
-
name: row["n.name"],
|
|
7492
|
-
file_path: row["n.file_path"],
|
|
7493
|
-
start_line: row["n.start_line"],
|
|
7494
|
-
end_line: row["n.end_line"],
|
|
7495
|
-
exported: row["n.exported"],
|
|
7496
|
-
content: row["n.content"],
|
|
7497
|
-
metadata: row["n.metadata"]
|
|
7498
|
-
}, kind);
|
|
7499
|
-
if (node.id && node.name) graph.addNode(node);
|
|
7500
|
-
}
|
|
7501
|
-
}
|
|
7502
|
-
try {
|
|
7503
|
-
const edgeRows = await db.query(
|
|
7504
|
-
`MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
|
|
7505
|
-
);
|
|
7506
|
-
for (const row of edgeRows) {
|
|
7507
|
-
const sourceId = String(row["a.id"] ?? "");
|
|
7508
|
-
const targetId = String(row["b.id"] ?? "");
|
|
7509
|
-
const kind = String(row["e.kind"] ?? "");
|
|
7510
|
-
if (!sourceId || !targetId || !kind) continue;
|
|
7511
|
-
const edge = {
|
|
7512
|
-
id: `${sourceId}::${kind}::${targetId}`,
|
|
7513
|
-
source: sourceId,
|
|
7514
|
-
target: targetId,
|
|
7515
|
-
kind,
|
|
7516
|
-
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
7517
|
-
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
7518
|
-
};
|
|
7519
|
-
graph.addEdge(edge);
|
|
7520
|
-
}
|
|
7521
|
-
} catch {
|
|
7522
|
-
}
|
|
7523
|
-
}
|
|
7524
|
-
|
|
7525
|
-
// src/multi-repo/group-sync.ts
|
|
7617
|
+
init_db_manager();
|
|
7618
|
+
init_knowledge_graph();
|
|
7619
|
+
init_graph_from_db();
|
|
7526
7620
|
init_logger();
|
|
7527
7621
|
function scanForFiles(root, matcher, maxDepth = 2) {
|
|
7528
7622
|
const results = [];
|
|
@@ -7530,7 +7624,7 @@ function scanForFiles(root, matcher, maxDepth = 2) {
|
|
|
7530
7624
|
if (depth > maxDepth) return;
|
|
7531
7625
|
let entries;
|
|
7532
7626
|
try {
|
|
7533
|
-
entries =
|
|
7627
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
7534
7628
|
} catch {
|
|
7535
7629
|
return;
|
|
7536
7630
|
}
|
|
@@ -7559,7 +7653,7 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
7559
7653
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
7560
7654
|
function tryParseFile(filePath) {
|
|
7561
7655
|
const ext = path32.extname(filePath).toLowerCase();
|
|
7562
|
-
const content =
|
|
7656
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
7563
7657
|
if (ext === ".json") {
|
|
7564
7658
|
try {
|
|
7565
7659
|
return JSON.parse(content);
|
|
@@ -7613,7 +7707,7 @@ async function parseGraphQLContracts(repoRoot) {
|
|
|
7613
7707
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
7614
7708
|
const contracts = [];
|
|
7615
7709
|
for (const filePath of files) {
|
|
7616
|
-
const content =
|
|
7710
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
7617
7711
|
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
7618
7712
|
let match;
|
|
7619
7713
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
@@ -7644,7 +7738,7 @@ async function parseProtoContracts(repoRoot) {
|
|
|
7644
7738
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
7645
7739
|
const contracts = [];
|
|
7646
7740
|
for (const filePath of files) {
|
|
7647
|
-
const content =
|
|
7741
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
7648
7742
|
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
7649
7743
|
let serviceMatch;
|
|
7650
7744
|
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
@@ -7850,7 +7944,7 @@ async function syncGroup(group) {
|
|
|
7850
7944
|
continue;
|
|
7851
7945
|
}
|
|
7852
7946
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
7853
|
-
if (!
|
|
7947
|
+
if (!fs26.existsSync(dbPath)) {
|
|
7854
7948
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
7855
7949
|
continue;
|
|
7856
7950
|
}
|
|
@@ -7916,6 +8010,9 @@ async function syncGroup(group) {
|
|
|
7916
8010
|
links
|
|
7917
8011
|
};
|
|
7918
8012
|
}
|
|
8013
|
+
init_db_manager();
|
|
8014
|
+
init_knowledge_graph();
|
|
8015
|
+
init_graph_from_db();
|
|
7919
8016
|
async function queryGroup(group, query, limit = 20) {
|
|
7920
8017
|
const registry = loadRegistry();
|
|
7921
8018
|
const perRepo = [];
|
|
@@ -7924,7 +8021,7 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7924
8021
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7925
8022
|
if (!regEntry) continue;
|
|
7926
8023
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
7927
|
-
if (!
|
|
8024
|
+
if (!fs26.existsSync(dbPath)) continue;
|
|
7928
8025
|
const graph = createKnowledgeGraph();
|
|
7929
8026
|
const db = new DbManager(dbPath, true);
|
|
7930
8027
|
try {
|
|
@@ -8572,11 +8669,30 @@ function summarizeCluster(graph, cluster) {
|
|
|
8572
8669
|
}
|
|
8573
8670
|
|
|
8574
8671
|
// src/mcp-server/server.ts
|
|
8672
|
+
function compact(obj) {
|
|
8673
|
+
return JSON.stringify(obj, (_key, value) => value === null || value === void 0 ? void 0 : value);
|
|
8674
|
+
}
|
|
8575
8675
|
function createMcpServer(graph, repoName, workspaceRoot) {
|
|
8576
8676
|
const server = new Server(
|
|
8577
8677
|
{ name: "code-intel", version: "0.1.0" },
|
|
8578
8678
|
{ capabilities: { tools: {}, resources: {} } }
|
|
8579
8679
|
);
|
|
8680
|
+
let bm25Index = null;
|
|
8681
|
+
function ensureBm25Index() {
|
|
8682
|
+
if (bm25Index) return bm25Index;
|
|
8683
|
+
if (!workspaceRoot) return null;
|
|
8684
|
+
try {
|
|
8685
|
+
const idx = new Bm25Index(getBm25DbPath(workspaceRoot));
|
|
8686
|
+
idx.load();
|
|
8687
|
+
bm25Index = idx;
|
|
8688
|
+
return bm25Index;
|
|
8689
|
+
} catch {
|
|
8690
|
+
return null;
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
if (workspaceRoot) {
|
|
8694
|
+
setImmediate(() => ensureBm25Index());
|
|
8695
|
+
}
|
|
8580
8696
|
const _tokenProp = {
|
|
8581
8697
|
_token: { type: "string", description: "Required if CODE_INTEL_TOKEN is configured" }
|
|
8582
8698
|
};
|
|
@@ -8596,13 +8712,15 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8596
8712
|
// ── Search & inspect ─────────────────────────────────────────────────
|
|
8597
8713
|
{
|
|
8598
8714
|
name: "search",
|
|
8599
|
-
description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc.",
|
|
8715
|
+
description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc. Optionally scope to a specific repo or group.",
|
|
8600
8716
|
inputSchema: {
|
|
8601
8717
|
type: "object",
|
|
8602
8718
|
properties: {
|
|
8603
8719
|
query: { type: "string", description: "Search query (symbol name, keyword, or partial match)" },
|
|
8604
8720
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8605
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
8721
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
8722
|
+
repo: { type: "string", description: "Scope search to a specific indexed repo name (optional; defaults to current repo)" },
|
|
8723
|
+
group: { type: "string", description: "Scope search across all repos in a group via cross-repo RRF merge (optional; overrides repo)" },
|
|
8606
8724
|
..._tokenProp
|
|
8607
8725
|
},
|
|
8608
8726
|
required: ["query"]
|
|
@@ -8632,7 +8750,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8632
8750
|
enum: ["callers", "callees", "both"],
|
|
8633
8751
|
description: "Which direction to trace \u2014 callers (who depends on it), callees (what it depends on), or both (default: both)"
|
|
8634
8752
|
},
|
|
8635
|
-
max_hops: { type: "number", description: "Maximum traversal depth (default:
|
|
8753
|
+
max_hops: { type: "number", description: "Maximum traversal depth (default: 2, max: 10)" },
|
|
8636
8754
|
..._tokenProp
|
|
8637
8755
|
},
|
|
8638
8756
|
required: ["target"]
|
|
@@ -8646,7 +8764,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8646
8764
|
properties: {
|
|
8647
8765
|
file_path: { type: "string", description: 'File path (partial match is supported, e.g. "auth/login.ts")' },
|
|
8648
8766
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8649
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
8767
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
8650
8768
|
..._tokenProp
|
|
8651
8769
|
},
|
|
8652
8770
|
required: ["file_path"]
|
|
@@ -8677,7 +8795,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8677
8795
|
description: "Filter by node kind: function | class | interface | method | type_alias | constant | enum (optional)"
|
|
8678
8796
|
},
|
|
8679
8797
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8680
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
8798
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
8681
8799
|
..._tokenProp
|
|
8682
8800
|
}
|
|
8683
8801
|
}
|
|
@@ -8695,7 +8813,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8695
8813
|
type: "object",
|
|
8696
8814
|
properties: {
|
|
8697
8815
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8698
|
-
limit: { type: "number", description: "Max clusters per page (default:
|
|
8816
|
+
limit: { type: "number", description: "Max clusters per page (default: 10, max: 500)" },
|
|
8699
8817
|
..._tokenProp
|
|
8700
8818
|
}
|
|
8701
8819
|
}
|
|
@@ -8707,7 +8825,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8707
8825
|
type: "object",
|
|
8708
8826
|
properties: {
|
|
8709
8827
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8710
|
-
limit: { type: "number", description: "Max flows per page (default:
|
|
8828
|
+
limit: { type: "number", description: "Max flows per page (default: 10, max: 500)" },
|
|
8711
8829
|
..._tokenProp
|
|
8712
8830
|
}
|
|
8713
8831
|
}
|
|
@@ -8861,7 +8979,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8861
8979
|
},
|
|
8862
8980
|
maxHops: {
|
|
8863
8981
|
type: "number",
|
|
8864
|
-
description: "Maximum BFS depth for blast radius (default:
|
|
8982
|
+
description: "Maximum BFS depth for blast radius (default: 2, max: 10)"
|
|
8865
8983
|
},
|
|
8866
8984
|
..._tokenProp
|
|
8867
8985
|
}
|
|
@@ -8989,13 +9107,13 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8989
9107
|
const providedToken = a._token;
|
|
8990
9108
|
if (providedToken !== expectedToken) {
|
|
8991
9109
|
return {
|
|
8992
|
-
content: [{ type: "text", text:
|
|
9110
|
+
content: [{ type: "text", text: compact({ error: "Unauthorized: invalid or missing CODE_INTEL_TOKEN" }) }],
|
|
8993
9111
|
isError: true
|
|
8994
9112
|
};
|
|
8995
9113
|
}
|
|
8996
9114
|
}
|
|
8997
9115
|
const startMs = Date.now();
|
|
8998
|
-
const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot);
|
|
9116
|
+
const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot, ensureBm25Index);
|
|
8999
9117
|
const MCP_TIMEOUT_MS = parseInt(process.env["CODE_INTEL_MCP_TIMEOUT_MS"] ?? "30000", 10);
|
|
9000
9118
|
let timeoutHandle = null;
|
|
9001
9119
|
let timedOut = false;
|
|
@@ -9027,7 +9145,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9027
9145
|
mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
|
|
9028
9146
|
if (timedOut) {
|
|
9029
9147
|
return {
|
|
9030
|
-
content: [{ type: "text", text:
|
|
9148
|
+
content: [{ type: "text", text: compact({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
|
|
9031
9149
|
isError: false
|
|
9032
9150
|
};
|
|
9033
9151
|
}
|
|
@@ -9042,7 +9160,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9042
9160
|
registerResources(server, graph, repoName);
|
|
9043
9161
|
return server;
|
|
9044
9162
|
}
|
|
9045
|
-
async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
9163
|
+
async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolver) {
|
|
9046
9164
|
switch (name) {
|
|
9047
9165
|
// ── repos ──────────────────────────────────────────────────────────────
|
|
9048
9166
|
case "repos": {
|
|
@@ -9050,10 +9168,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9050
9168
|
return {
|
|
9051
9169
|
content: [{
|
|
9052
9170
|
type: "text",
|
|
9053
|
-
text:
|
|
9054
|
-
registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
|
|
9055
|
-
null,
|
|
9056
|
-
2
|
|
9171
|
+
text: compact(
|
|
9172
|
+
registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
|
|
9057
9173
|
)
|
|
9058
9174
|
}]
|
|
9059
9175
|
};
|
|
@@ -9081,13 +9197,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9081
9197
|
return {
|
|
9082
9198
|
content: [{
|
|
9083
9199
|
type: "text",
|
|
9084
|
-
text:
|
|
9200
|
+
text: compact({
|
|
9085
9201
|
repo: repoName,
|
|
9086
9202
|
stats: graph.size,
|
|
9087
9203
|
nodeCounts: kindCounts,
|
|
9088
9204
|
edgeCounts,
|
|
9089
9205
|
health
|
|
9090
|
-
}
|
|
9206
|
+
})
|
|
9091
9207
|
}]
|
|
9092
9208
|
};
|
|
9093
9209
|
}
|
|
@@ -9095,15 +9211,59 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9095
9211
|
case "search": {
|
|
9096
9212
|
const query = a.query;
|
|
9097
9213
|
const offset = a.offset ?? 0;
|
|
9098
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9214
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9215
|
+
if (a.group) {
|
|
9216
|
+
const grp = loadGroup(a.group);
|
|
9217
|
+
if (!grp) {
|
|
9218
|
+
return { content: [{ type: "text", text: `Group "${a.group}" not found. Use list_groups to see available groups.` }] };
|
|
9219
|
+
}
|
|
9220
|
+
const { perRepo, merged } = await queryGroup(grp, query, effectiveLimit + offset);
|
|
9221
|
+
const paged = merged.slice(offset, offset + effectiveLimit);
|
|
9222
|
+
return {
|
|
9223
|
+
content: [{
|
|
9224
|
+
type: "text",
|
|
9225
|
+
text: compact({
|
|
9226
|
+
results: paged,
|
|
9227
|
+
perRepo,
|
|
9228
|
+
searchMode: "bm25-cross-repo",
|
|
9229
|
+
group: a.group,
|
|
9230
|
+
total: merged.length,
|
|
9231
|
+
offset,
|
|
9232
|
+
limit: effectiveLimit,
|
|
9233
|
+
hasMore: offset + effectiveLimit < merged.length
|
|
9234
|
+
})
|
|
9235
|
+
}]
|
|
9236
|
+
};
|
|
9237
|
+
}
|
|
9238
|
+
const repoGraph = a.repo ? await (async () => {
|
|
9239
|
+
const registry = loadRegistry();
|
|
9240
|
+
const entry = registry.find((r) => r.name === a.repo || r.path === a.repo);
|
|
9241
|
+
if (!entry) return graph;
|
|
9242
|
+
const { DbManager: DbMgr } = await Promise.resolve().then(() => (init_db_manager(), db_manager_exports));
|
|
9243
|
+
const { loadGraphFromDB: loadG } = await Promise.resolve().then(() => (init_graph_from_db(), graph_from_db_exports));
|
|
9244
|
+
const { createKnowledgeGraph: createG } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
|
|
9245
|
+
const dbPath = path32.join(entry.path, ".code-intel", "graph.db");
|
|
9246
|
+
if (!fs26.existsSync(dbPath)) return graph;
|
|
9247
|
+
const db = new DbMgr(dbPath, true);
|
|
9248
|
+
await db.init();
|
|
9249
|
+
const g = createG();
|
|
9250
|
+
await loadG(g, db);
|
|
9251
|
+
db.close();
|
|
9252
|
+
return g;
|
|
9253
|
+
})() : graph;
|
|
9099
9254
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
9100
9255
|
const fetchLimit = Math.min(offset + effectiveLimit, 500);
|
|
9101
|
-
const
|
|
9256
|
+
const bm25 = !a.repo || a.repo === repoName ? bm25Resolver ? bm25Resolver() : null : null;
|
|
9257
|
+
const bm25Results = bm25 ? bm25.search(query, fetchLimit * 3) : void 0;
|
|
9258
|
+
const { results: allResults, searchMode } = await hybridSearch(repoGraph, query, fetchLimit, {
|
|
9259
|
+
vectorDbPath: vdbPath,
|
|
9260
|
+
bm25Results: bm25Results ?? void 0
|
|
9261
|
+
});
|
|
9102
9262
|
const total = allResults.length;
|
|
9103
9263
|
const results = allResults.slice(offset, offset + effectiveLimit);
|
|
9104
9264
|
const hasMore = offset + effectiveLimit < total;
|
|
9105
9265
|
const suggestNextTools = [];
|
|
9106
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
9266
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
9107
9267
|
if (suggestEnabled && results.length > 0) {
|
|
9108
9268
|
const topName = results[0].name;
|
|
9109
9269
|
suggestNextTools.push(
|
|
@@ -9114,15 +9274,16 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9114
9274
|
return {
|
|
9115
9275
|
content: [{
|
|
9116
9276
|
type: "text",
|
|
9117
|
-
text:
|
|
9277
|
+
text: compact({
|
|
9118
9278
|
results,
|
|
9119
9279
|
searchMode,
|
|
9280
|
+
repo: a.repo ?? repoName,
|
|
9120
9281
|
total,
|
|
9121
9282
|
offset,
|
|
9122
9283
|
limit: effectiveLimit,
|
|
9123
9284
|
hasMore,
|
|
9124
9285
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9125
|
-
}
|
|
9286
|
+
})
|
|
9126
9287
|
}]
|
|
9127
9288
|
};
|
|
9128
9289
|
}
|
|
@@ -9144,7 +9305,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9144
9305
|
file: graph.getNode(e.target)?.filePath
|
|
9145
9306
|
}));
|
|
9146
9307
|
const cluster = incoming.filter((e) => e.kind === "belongs_to").map((e) => graph.getNode(e.target)?.name)[0];
|
|
9147
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
9308
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
9148
9309
|
const suggestNextTools = [];
|
|
9149
9310
|
if (suggestEnabled) {
|
|
9150
9311
|
const topCallerName = callers[0]?.name;
|
|
@@ -9156,7 +9317,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9156
9317
|
return {
|
|
9157
9318
|
content: [{
|
|
9158
9319
|
type: "text",
|
|
9159
|
-
text:
|
|
9320
|
+
text: compact({
|
|
9160
9321
|
node: {
|
|
9161
9322
|
id: node.id,
|
|
9162
9323
|
kind: node.kind,
|
|
@@ -9179,7 +9340,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9179
9340
|
cluster,
|
|
9180
9341
|
content: node.content?.slice(0, 500),
|
|
9181
9342
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9182
|
-
}
|
|
9343
|
+
})
|
|
9183
9344
|
}]
|
|
9184
9345
|
};
|
|
9185
9346
|
}
|
|
@@ -9187,7 +9348,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9187
9348
|
case "blast_radius": {
|
|
9188
9349
|
const target = a.target;
|
|
9189
9350
|
const direction = a.direction ?? "both";
|
|
9190
|
-
const maxHops = a.max_hops ??
|
|
9351
|
+
const maxHops = a.max_hops ?? 2;
|
|
9191
9352
|
const node = findNodeByName(graph, target);
|
|
9192
9353
|
if (!node) return { content: [{ type: "text", text: `Symbol "${target}" not found.` }] };
|
|
9193
9354
|
const affected = /* @__PURE__ */ new Set();
|
|
@@ -9214,7 +9375,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9214
9375
|
return n ? { id, name: n.name, kind: n.kind, filePath: n.filePath } : { id };
|
|
9215
9376
|
});
|
|
9216
9377
|
const risk = affected.size > 10 ? "HIGH" : affected.size > 5 ? "MEDIUM" : "LOW";
|
|
9217
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
9378
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
9218
9379
|
const suggestNextTools = [];
|
|
9219
9380
|
if (suggestEnabled) {
|
|
9220
9381
|
const highestRiskSymbol = node.name;
|
|
@@ -9227,13 +9388,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9227
9388
|
return {
|
|
9228
9389
|
content: [{
|
|
9229
9390
|
type: "text",
|
|
9230
|
-
text:
|
|
9391
|
+
text: compact({
|
|
9231
9392
|
target: node.name,
|
|
9232
9393
|
affectedCount: affected.size,
|
|
9233
9394
|
riskLevel: risk,
|
|
9234
9395
|
affected: affectedDetails,
|
|
9235
9396
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9236
|
-
}
|
|
9397
|
+
})
|
|
9237
9398
|
}]
|
|
9238
9399
|
};
|
|
9239
9400
|
}
|
|
@@ -9241,7 +9402,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9241
9402
|
case "file_symbols": {
|
|
9242
9403
|
const filePath = a.file_path;
|
|
9243
9404
|
const offset = a.offset ?? 0;
|
|
9244
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9405
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9245
9406
|
const allMatches = [];
|
|
9246
9407
|
for (const node of graph.allNodes()) {
|
|
9247
9408
|
if (node.filePath && node.filePath.includes(filePath)) {
|
|
@@ -9258,7 +9419,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9258
9419
|
return {
|
|
9259
9420
|
content: [{
|
|
9260
9421
|
type: "text",
|
|
9261
|
-
text:
|
|
9422
|
+
text: compact({ symbols: matches, total, offset, limit: effectiveLimit, hasMore })
|
|
9262
9423
|
}]
|
|
9263
9424
|
};
|
|
9264
9425
|
}
|
|
@@ -9299,7 +9460,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9299
9460
|
return {
|
|
9300
9461
|
content: [{
|
|
9301
9462
|
type: "text",
|
|
9302
|
-
text:
|
|
9463
|
+
text: compact({ from: fromName, to: toName, hops: foundPath.length - 1, path: pathDetails })
|
|
9303
9464
|
}]
|
|
9304
9465
|
};
|
|
9305
9466
|
}
|
|
@@ -9307,7 +9468,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9307
9468
|
case "list_exports": {
|
|
9308
9469
|
const kindFilter = a.kind;
|
|
9309
9470
|
const offset = a.offset ?? 0;
|
|
9310
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9471
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9311
9472
|
const allExports = [];
|
|
9312
9473
|
for (const node of graph.allNodes()) {
|
|
9313
9474
|
if (!node.exported) continue;
|
|
@@ -9320,7 +9481,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9320
9481
|
return {
|
|
9321
9482
|
content: [{
|
|
9322
9483
|
type: "text",
|
|
9323
|
-
text:
|
|
9484
|
+
text: compact({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore })
|
|
9324
9485
|
}]
|
|
9325
9486
|
};
|
|
9326
9487
|
}
|
|
@@ -9332,12 +9493,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9332
9493
|
routes.push({ name: node.name, filePath: node.filePath, startLine: node.startLine });
|
|
9333
9494
|
}
|
|
9334
9495
|
}
|
|
9335
|
-
return { content: [{ type: "text", text:
|
|
9496
|
+
return { content: [{ type: "text", text: compact(routes) }] };
|
|
9336
9497
|
}
|
|
9337
9498
|
// ── clusters ───────────────────────────────────────────────────────────
|
|
9338
9499
|
case "clusters": {
|
|
9339
9500
|
const offset = a.offset ?? 0;
|
|
9340
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9501
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9341
9502
|
const allClusters = [];
|
|
9342
9503
|
for (const node of graph.allNodes()) {
|
|
9343
9504
|
if (node.kind === "cluster") {
|
|
@@ -9364,14 +9525,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9364
9525
|
return {
|
|
9365
9526
|
content: [{
|
|
9366
9527
|
type: "text",
|
|
9367
|
-
text:
|
|
9528
|
+
text: compact({ clusters, total, offset, limit: effectiveLimit, hasMore })
|
|
9368
9529
|
}]
|
|
9369
9530
|
};
|
|
9370
9531
|
}
|
|
9371
9532
|
// ── flows ──────────────────────────────────────────────────────────────
|
|
9372
9533
|
case "flows": {
|
|
9373
9534
|
const offset = a.offset ?? 0;
|
|
9374
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9535
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9375
9536
|
const allFlows = [];
|
|
9376
9537
|
for (const node of graph.allNodes()) {
|
|
9377
9538
|
if (node.kind === "flow") {
|
|
@@ -9391,7 +9552,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9391
9552
|
return {
|
|
9392
9553
|
content: [{
|
|
9393
9554
|
type: "text",
|
|
9394
|
-
text:
|
|
9555
|
+
text: compact({ flows, total, offset, limit: effectiveLimit, hasMore })
|
|
9395
9556
|
}]
|
|
9396
9557
|
};
|
|
9397
9558
|
}
|
|
@@ -9459,14 +9620,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9459
9620
|
return {
|
|
9460
9621
|
content: [{
|
|
9461
9622
|
type: "text",
|
|
9462
|
-
text:
|
|
9623
|
+
text: compact({
|
|
9463
9624
|
baseRef,
|
|
9464
9625
|
changedFiles: changedFiles.map((f) => f.filePath),
|
|
9465
9626
|
directlyChangedSymbols: changedSymbols,
|
|
9466
9627
|
transitivelyAffectedSymbols: affectedSymbols,
|
|
9467
9628
|
totalAffected: allAffected.size,
|
|
9468
9629
|
riskLevel: risk
|
|
9469
|
-
}
|
|
9630
|
+
})
|
|
9470
9631
|
}]
|
|
9471
9632
|
};
|
|
9472
9633
|
}
|
|
@@ -9474,14 +9635,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9474
9635
|
case "query": {
|
|
9475
9636
|
const gqlInput = a.gql;
|
|
9476
9637
|
if (!gqlInput) {
|
|
9477
|
-
return { content: [{ type: "text", text:
|
|
9638
|
+
return { content: [{ type: "text", text: compact({ error: "Missing required parameter: gql" }) }], isError: true };
|
|
9478
9639
|
}
|
|
9479
9640
|
const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
|
|
9480
9641
|
const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
|
|
9481
9642
|
const ast = parseGQL2(gqlInput);
|
|
9482
9643
|
if (isGQLParseError2(ast)) {
|
|
9483
9644
|
return {
|
|
9484
|
-
content: [{ type: "text", text:
|
|
9645
|
+
content: [{ type: "text", text: compact({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
|
|
9485
9646
|
isError: true
|
|
9486
9647
|
};
|
|
9487
9648
|
}
|
|
@@ -9492,7 +9653,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9492
9653
|
return {
|
|
9493
9654
|
content: [{
|
|
9494
9655
|
type: "text",
|
|
9495
|
-
text:
|
|
9656
|
+
text: compact({
|
|
9496
9657
|
nodes: result.nodes,
|
|
9497
9658
|
edges: result.edges,
|
|
9498
9659
|
groups: result.groups,
|
|
@@ -9500,7 +9661,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9500
9661
|
executionTimeMs: result.executionTimeMs,
|
|
9501
9662
|
truncated: result.truncated,
|
|
9502
9663
|
totalCount: result.totalCount
|
|
9503
|
-
}
|
|
9664
|
+
})
|
|
9504
9665
|
}]
|
|
9505
9666
|
};
|
|
9506
9667
|
}
|
|
@@ -9514,7 +9675,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9514
9675
|
for (const node of graph.allNodes()) {
|
|
9515
9676
|
if (node.name === nameMatch[1]) results.push(node);
|
|
9516
9677
|
}
|
|
9517
|
-
return { content: [{ type: "text", text:
|
|
9678
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
|
|
9518
9679
|
}
|
|
9519
9680
|
const kindMatch = q?.match(/:\s*(\w+)/);
|
|
9520
9681
|
if (kindMatch) {
|
|
@@ -9523,9 +9684,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9523
9684
|
if (node.kind === kindMatch[1]) results.push(node);
|
|
9524
9685
|
if (results.length >= 50) break;
|
|
9525
9686
|
}
|
|
9526
|
-
return { content: [{ type: "text", text:
|
|
9687
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
|
|
9527
9688
|
}
|
|
9528
|
-
return { content: [{ type: "text", text:
|
|
9689
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, error: "Query not recognized. Use name='X' or :kind syntax. Or use the query tool with GQL instead." }) }] };
|
|
9529
9690
|
}
|
|
9530
9691
|
// ── group_list ─────────────────────────────────────────────────────────
|
|
9531
9692
|
case "group_list": {
|
|
@@ -9533,16 +9694,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9533
9694
|
if (groupName) {
|
|
9534
9695
|
const group = loadGroup(groupName);
|
|
9535
9696
|
if (!group) return { content: [{ type: "text", text: `Group "${groupName}" not found.` }] };
|
|
9536
|
-
return { content: [{ type: "text", text:
|
|
9697
|
+
return { content: [{ type: "text", text: compact(group) }] };
|
|
9537
9698
|
}
|
|
9538
9699
|
const groups = listGroups();
|
|
9539
9700
|
return {
|
|
9540
9701
|
content: [{
|
|
9541
9702
|
type: "text",
|
|
9542
|
-
text:
|
|
9543
|
-
groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members }))
|
|
9544
|
-
null,
|
|
9545
|
-
2
|
|
9703
|
+
text: compact(
|
|
9704
|
+
groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members }))
|
|
9546
9705
|
)
|
|
9547
9706
|
}]
|
|
9548
9707
|
};
|
|
@@ -9560,14 +9719,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9560
9719
|
return {
|
|
9561
9720
|
content: [{
|
|
9562
9721
|
type: "text",
|
|
9563
|
-
text:
|
|
9722
|
+
text: compact({
|
|
9564
9723
|
groupName: result.groupName,
|
|
9565
9724
|
syncedAt: result.syncedAt,
|
|
9566
9725
|
memberCount: result.memberCount,
|
|
9567
9726
|
contractCount: result.contracts.length,
|
|
9568
9727
|
linkCount: result.links.length,
|
|
9569
9728
|
topLinks: result.links.slice(0, 20)
|
|
9570
|
-
}
|
|
9729
|
+
})
|
|
9571
9730
|
}]
|
|
9572
9731
|
};
|
|
9573
9732
|
}
|
|
@@ -9587,7 +9746,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9587
9746
|
return {
|
|
9588
9747
|
content: [{
|
|
9589
9748
|
type: "text",
|
|
9590
|
-
text:
|
|
9749
|
+
text: compact({ syncedAt: result.syncedAt, contracts, links })
|
|
9591
9750
|
}]
|
|
9592
9751
|
};
|
|
9593
9752
|
}
|
|
@@ -9602,7 +9761,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9602
9761
|
return {
|
|
9603
9762
|
content: [{
|
|
9604
9763
|
type: "text",
|
|
9605
|
-
text:
|
|
9764
|
+
text: compact({ query, merged, perRepo })
|
|
9606
9765
|
}]
|
|
9607
9766
|
};
|
|
9608
9767
|
}
|
|
@@ -9634,12 +9793,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9634
9793
|
return {
|
|
9635
9794
|
content: [{
|
|
9636
9795
|
type: "text",
|
|
9637
|
-
text:
|
|
9796
|
+
text: compact({
|
|
9638
9797
|
group: groupName,
|
|
9639
9798
|
lastSync: group.lastSync ?? null,
|
|
9640
9799
|
syncAgeMinutes: syncAge,
|
|
9641
9800
|
members: memberStatus
|
|
9642
|
-
}
|
|
9801
|
+
})
|
|
9643
9802
|
}]
|
|
9644
9803
|
};
|
|
9645
9804
|
}
|
|
@@ -9648,11 +9807,11 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9648
9807
|
const fromName = a.from;
|
|
9649
9808
|
const toName = a.to;
|
|
9650
9809
|
const result = explainRelationship(graph, fromName, toName);
|
|
9651
|
-
return { content: [{ type: "text", text:
|
|
9810
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9652
9811
|
}
|
|
9653
9812
|
// ── pr_impact ──────────────────────────────────────────────────────────
|
|
9654
9813
|
case "pr_impact": {
|
|
9655
|
-
const maxHops = a.maxHops ??
|
|
9814
|
+
const maxHops = a.maxHops ?? 2;
|
|
9656
9815
|
let changedFiles = a.changedFiles ?? [];
|
|
9657
9816
|
if (a.diff && typeof a.diff === "string") {
|
|
9658
9817
|
const diffFiles = parseDiffFiles(a.diff);
|
|
@@ -9662,37 +9821,37 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9662
9821
|
return {
|
|
9663
9822
|
content: [{
|
|
9664
9823
|
type: "text",
|
|
9665
|
-
text:
|
|
9824
|
+
text: compact({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
|
|
9666
9825
|
}]
|
|
9667
9826
|
};
|
|
9668
9827
|
}
|
|
9669
9828
|
const result = computePRImpact(graph, changedFiles, maxHops);
|
|
9670
|
-
return { content: [{ type: "text", text:
|
|
9829
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9671
9830
|
}
|
|
9672
9831
|
// ── similar_symbols ────────────────────────────────────────────────────
|
|
9673
9832
|
case "similar_symbols": {
|
|
9674
9833
|
const symbolName = a.symbol;
|
|
9675
9834
|
const limit = a.limit ?? 10;
|
|
9676
9835
|
const result = findSimilarSymbols(graph, symbolName, limit);
|
|
9677
|
-
return { content: [{ type: "text", text:
|
|
9836
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9678
9837
|
}
|
|
9679
9838
|
// ── health_report ──────────────────────────────────────────────────────
|
|
9680
9839
|
case "health_report": {
|
|
9681
9840
|
const scope = a.scope ?? ".";
|
|
9682
9841
|
const result = computeHealthReport(graph, scope);
|
|
9683
|
-
return { content: [{ type: "text", text:
|
|
9842
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9684
9843
|
}
|
|
9685
9844
|
// ── suggest_tests ──────────────────────────────────────────────────────
|
|
9686
9845
|
case "suggest_tests": {
|
|
9687
9846
|
const sym = a.symbol;
|
|
9688
9847
|
const result = suggestTests(graph, sym);
|
|
9689
|
-
return { content: [{ type: "text", text:
|
|
9848
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9690
9849
|
}
|
|
9691
9850
|
// ── cluster_summary ────────────────────────────────────────────────────
|
|
9692
9851
|
case "cluster_summary": {
|
|
9693
9852
|
const cluster = a.cluster;
|
|
9694
9853
|
const result = summarizeCluster(graph, cluster);
|
|
9695
|
-
return { content: [{ type: "text", text:
|
|
9854
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9696
9855
|
}
|
|
9697
9856
|
case "deprecated_usage": {
|
|
9698
9857
|
const scope = a.scope;
|
|
@@ -9700,7 +9859,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9700
9859
|
const detector = new DeprecatedDetector2();
|
|
9701
9860
|
detector.tagDeprecated(graph);
|
|
9702
9861
|
const findings = detector.detect(graph, scope);
|
|
9703
|
-
return { content: [{ type: "text", text:
|
|
9862
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
9704
9863
|
}
|
|
9705
9864
|
// ── complexity_hotspots ────────────────────────────────────────────────
|
|
9706
9865
|
case "complexity_hotspots": {
|
|
@@ -9708,7 +9867,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9708
9867
|
const scope = a.scope;
|
|
9709
9868
|
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
9710
9869
|
const hotspots = computeComplexity2(graph, scope).slice(0, limit);
|
|
9711
|
-
return { content: [{ type: "text", text:
|
|
9870
|
+
return { content: [{ type: "text", text: compact({ hotspots, total: hotspots.length }) }] };
|
|
9712
9871
|
}
|
|
9713
9872
|
// ── coverage_gaps ──────────────────────────────────────────────────────
|
|
9714
9873
|
case "coverage_gaps": {
|
|
@@ -9720,12 +9879,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9720
9879
|
return {
|
|
9721
9880
|
content: [{
|
|
9722
9881
|
type: "text",
|
|
9723
|
-
text:
|
|
9882
|
+
text: compact({
|
|
9724
9883
|
untestedByRisk,
|
|
9725
9884
|
coveragePct: summary.coveragePct,
|
|
9726
9885
|
totalExported: summary.totalExported,
|
|
9727
9886
|
testedExported: summary.testedExported
|
|
9728
|
-
}
|
|
9887
|
+
})
|
|
9729
9888
|
}]
|
|
9730
9889
|
};
|
|
9731
9890
|
}
|
|
@@ -9736,7 +9895,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9736
9895
|
const scope = a.scope;
|
|
9737
9896
|
const includeTestFiles = a.includeTestFiles ?? false;
|
|
9738
9897
|
const findings = scanner.scan(graph, { scope, includeTestFiles });
|
|
9739
|
-
return { content: [{ type: "text", text:
|
|
9898
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
9740
9899
|
}
|
|
9741
9900
|
// ── vulnerability_scan ─────────────────────────────────────────────────
|
|
9742
9901
|
case "vulnerability_scan": {
|
|
@@ -9749,7 +9908,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9749
9908
|
const minRank = sevRank[minSev] ?? 1;
|
|
9750
9909
|
let findings = detector.detect(graph, { scope, types });
|
|
9751
9910
|
findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
|
|
9752
|
-
return { content: [{ type: "text", text:
|
|
9911
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
9753
9912
|
}
|
|
9754
9913
|
default:
|
|
9755
9914
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
@@ -9770,21 +9929,21 @@ function registerResources(server, graph, repoName) {
|
|
|
9770
9929
|
for (const node of graph.allNodes()) {
|
|
9771
9930
|
kindCounts[node.kind] = (kindCounts[node.kind] ?? 0) + 1;
|
|
9772
9931
|
}
|
|
9773
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
9932
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
|
|
9774
9933
|
}
|
|
9775
9934
|
if (uri.endsWith("/clusters")) {
|
|
9776
9935
|
const clusters = [];
|
|
9777
9936
|
for (const node of graph.allNodes()) {
|
|
9778
9937
|
if (node.kind === "cluster") clusters.push({ id: node.id, name: node.name, memberCount: node.metadata?.memberCount });
|
|
9779
9938
|
}
|
|
9780
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
9939
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact(clusters) }] };
|
|
9781
9940
|
}
|
|
9782
9941
|
if (uri.endsWith("/flows")) {
|
|
9783
9942
|
const flows = [];
|
|
9784
9943
|
for (const node of graph.allNodes()) {
|
|
9785
9944
|
if (node.kind === "flow") flows.push({ id: node.id, name: node.name, steps: node.metadata?.steps, entryPoint: node.metadata?.entryPoint });
|
|
9786
9945
|
}
|
|
9787
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
9946
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact(flows) }] };
|
|
9788
9947
|
}
|
|
9789
9948
|
throw new Error(`Unknown resource: ${uri}`);
|
|
9790
9949
|
});
|
|
@@ -9834,6 +9993,8 @@ function parseDiff(diffText) {
|
|
|
9834
9993
|
return result;
|
|
9835
9994
|
}
|
|
9836
9995
|
init_group_registry();
|
|
9996
|
+
init_knowledge_graph();
|
|
9997
|
+
init_graph_from_db();
|
|
9837
9998
|
init_logger();
|
|
9838
9999
|
init_codes();
|
|
9839
10000
|
init_middleware();
|
|
@@ -9844,7 +10005,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
9844
10005
|
var JobsDB = class {
|
|
9845
10006
|
db;
|
|
9846
10007
|
constructor(dbPath) {
|
|
9847
|
-
|
|
10008
|
+
fs26.mkdirSync(path32.dirname(dbPath), { recursive: true });
|
|
9848
10009
|
this.db = new Database2(dbPath);
|
|
9849
10010
|
this.db.pragma("journal_mode = WAL");
|
|
9850
10011
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -10109,7 +10270,7 @@ var BackupService = class {
|
|
|
10109
10270
|
constructor(backupDir) {
|
|
10110
10271
|
this.backupDir = backupDir ?? getBackupDir();
|
|
10111
10272
|
this.key = getBackupKey();
|
|
10112
|
-
|
|
10273
|
+
fs26.mkdirSync(this.backupDir, { recursive: true });
|
|
10113
10274
|
}
|
|
10114
10275
|
/**
|
|
10115
10276
|
* Create a backup for a repository.
|
|
@@ -10123,16 +10284,16 @@ var BackupService = class {
|
|
|
10123
10284
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
10124
10285
|
for (const f of candidates) {
|
|
10125
10286
|
const fp = path32.join(codeIntelDir, f);
|
|
10126
|
-
if (
|
|
10287
|
+
if (fs26.existsSync(fp)) {
|
|
10127
10288
|
filesToBackup.push({ name: f, localPath: fp });
|
|
10128
10289
|
}
|
|
10129
10290
|
}
|
|
10130
10291
|
const registryPath = path32.join(os13.homedir(), ".code-intel", "registry.json");
|
|
10131
|
-
if (
|
|
10292
|
+
if (fs26.existsSync(registryPath)) {
|
|
10132
10293
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
10133
10294
|
}
|
|
10134
10295
|
const usersDbPath = path32.join(os13.homedir(), ".code-intel", "users.db");
|
|
10135
|
-
if (
|
|
10296
|
+
if (fs26.existsSync(usersDbPath)) {
|
|
10136
10297
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
10137
10298
|
}
|
|
10138
10299
|
if (filesToBackup.length === 0) {
|
|
@@ -10143,7 +10304,7 @@ var BackupService = class {
|
|
|
10143
10304
|
createdAt,
|
|
10144
10305
|
version: BACKUP_VERSION,
|
|
10145
10306
|
files: filesToBackup.map((f) => {
|
|
10146
|
-
const data =
|
|
10307
|
+
const data = fs26.readFileSync(f.localPath);
|
|
10147
10308
|
return {
|
|
10148
10309
|
name: f.name,
|
|
10149
10310
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -10157,7 +10318,7 @@ var BackupService = class {
|
|
|
10157
10318
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
10158
10319
|
parts.push(manifestLenBuf, manifestBuf);
|
|
10159
10320
|
for (const f of filesToBackup) {
|
|
10160
|
-
const data =
|
|
10321
|
+
const data = fs26.readFileSync(f.localPath);
|
|
10161
10322
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
10162
10323
|
const nameLenBuf = Buffer.alloc(2);
|
|
10163
10324
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -10169,7 +10330,7 @@ var BackupService = class {
|
|
|
10169
10330
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
10170
10331
|
const backupFileName = `backup-${id}.cib`;
|
|
10171
10332
|
const backupPath = path32.join(this.backupDir, backupFileName);
|
|
10172
|
-
|
|
10333
|
+
fs26.writeFileSync(backupPath, encrypted);
|
|
10173
10334
|
const entry = {
|
|
10174
10335
|
id,
|
|
10175
10336
|
createdAt,
|
|
@@ -10198,7 +10359,7 @@ var BackupService = class {
|
|
|
10198
10359
|
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.");
|
|
10199
10360
|
const fileName = path32.basename(entry.path);
|
|
10200
10361
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
10201
|
-
const body =
|
|
10362
|
+
const body = fs26.readFileSync(entry.path);
|
|
10202
10363
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
10203
10364
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
10204
10365
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -10215,8 +10376,8 @@ var BackupService = class {
|
|
|
10215
10376
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
10216
10377
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
10217
10378
|
}
|
|
10218
|
-
|
|
10219
|
-
|
|
10379
|
+
fs26.mkdirSync(path32.dirname(destPath), { recursive: true });
|
|
10380
|
+
fs26.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
10220
10381
|
}
|
|
10221
10382
|
/**
|
|
10222
10383
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -10262,10 +10423,10 @@ var BackupService = class {
|
|
|
10262
10423
|
if (!entry) {
|
|
10263
10424
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
10264
10425
|
}
|
|
10265
|
-
if (!
|
|
10426
|
+
if (!fs26.existsSync(entry.path)) {
|
|
10266
10427
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
10267
10428
|
}
|
|
10268
|
-
const encrypted =
|
|
10429
|
+
const encrypted = fs26.readFileSync(entry.path);
|
|
10269
10430
|
let plaintext;
|
|
10270
10431
|
try {
|
|
10271
10432
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -10280,7 +10441,7 @@ var BackupService = class {
|
|
|
10280
10441
|
const manifest = JSON.parse(manifestStr);
|
|
10281
10442
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
10282
10443
|
const codeIntelDir = path32.join(restoreBase, ".code-intel");
|
|
10283
|
-
|
|
10444
|
+
fs26.mkdirSync(codeIntelDir, { recursive: true });
|
|
10284
10445
|
for (const fileEntry of manifest.files) {
|
|
10285
10446
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
10286
10447
|
offset += 2;
|
|
@@ -10301,14 +10462,14 @@ var BackupService = class {
|
|
|
10301
10462
|
} else {
|
|
10302
10463
|
destPath = path32.join(codeIntelDir, name);
|
|
10303
10464
|
}
|
|
10304
|
-
|
|
10465
|
+
fs26.writeFileSync(destPath, data);
|
|
10305
10466
|
}
|
|
10306
10467
|
}
|
|
10307
10468
|
/**
|
|
10308
10469
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
10309
10470
|
*/
|
|
10310
10471
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
10311
|
-
const entries = this._loadIndex().filter((e) =>
|
|
10472
|
+
const entries = this._loadIndex().filter((e) => fs26.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
10312
10473
|
const keep = /* @__PURE__ */ new Set();
|
|
10313
10474
|
const now = /* @__PURE__ */ new Date();
|
|
10314
10475
|
const dailyCutoff = new Date(now);
|
|
@@ -10338,7 +10499,7 @@ var BackupService = class {
|
|
|
10338
10499
|
for (const e of entries) {
|
|
10339
10500
|
if (!keep.has(e.id)) {
|
|
10340
10501
|
try {
|
|
10341
|
-
|
|
10502
|
+
fs26.unlinkSync(e.path);
|
|
10342
10503
|
deleted++;
|
|
10343
10504
|
} catch {
|
|
10344
10505
|
}
|
|
@@ -10354,13 +10515,13 @@ var BackupService = class {
|
|
|
10354
10515
|
}
|
|
10355
10516
|
_loadIndex() {
|
|
10356
10517
|
try {
|
|
10357
|
-
return JSON.parse(
|
|
10518
|
+
return JSON.parse(fs26.readFileSync(this._indexPath(), "utf-8"));
|
|
10358
10519
|
} catch {
|
|
10359
10520
|
return [];
|
|
10360
10521
|
}
|
|
10361
10522
|
}
|
|
10362
10523
|
_saveIndex(entries) {
|
|
10363
|
-
|
|
10524
|
+
fs26.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
10364
10525
|
}
|
|
10365
10526
|
_appendIndex(entry) {
|
|
10366
10527
|
const entries = this._loadIndex();
|
|
@@ -11156,7 +11317,7 @@ var openApiSpec = {
|
|
|
11156
11317
|
var __dirname$1 = path32.dirname(fileURLToPath(import.meta.url));
|
|
11157
11318
|
var WEB_DIST = (() => {
|
|
11158
11319
|
const bundled = path32.resolve(__dirname$1, "..", "web");
|
|
11159
|
-
if (
|
|
11320
|
+
if (fs26.existsSync(bundled)) return bundled;
|
|
11160
11321
|
return path32.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
11161
11322
|
})();
|
|
11162
11323
|
function getAllowedOrigins() {
|
|
@@ -11241,8 +11402,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11241
11402
|
const metaFilePath = path32.join(workspaceRoot, ".code-intel", "meta.json");
|
|
11242
11403
|
let metaOk = false;
|
|
11243
11404
|
try {
|
|
11244
|
-
if (
|
|
11245
|
-
const raw =
|
|
11405
|
+
if (fs26.existsSync(metaFilePath)) {
|
|
11406
|
+
const raw = fs26.readFileSync(metaFilePath, "utf-8");
|
|
11246
11407
|
const meta = JSON.parse(raw);
|
|
11247
11408
|
if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
|
|
11248
11409
|
}
|
|
@@ -11433,12 +11594,12 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11433
11594
|
return;
|
|
11434
11595
|
}
|
|
11435
11596
|
const user = db.createUser(username, password, "admin");
|
|
11436
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11437
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11597
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11598
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11438
11599
|
res.status(201).json({ user: { id: user.id, username: user.username, role: user.role } });
|
|
11439
11600
|
});
|
|
11440
11601
|
app.post("/auth/login", async (req, res) => {
|
|
11441
|
-
const { username, password } = req.body;
|
|
11602
|
+
const { username, password, rememberMe } = req.body;
|
|
11442
11603
|
if (!username || !password) {
|
|
11443
11604
|
res.status(400).json({
|
|
11444
11605
|
error: {
|
|
@@ -11482,10 +11643,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11482
11643
|
});
|
|
11483
11644
|
return;
|
|
11484
11645
|
}
|
|
11485
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11646
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role }, rememberMe === true);
|
|
11486
11647
|
db.logAccess(user.id, "/auth/login", "login", "allow", req.ip ?? "unknown");
|
|
11487
11648
|
authAttemptsTotal.inc({ method: "local", outcome: "success" });
|
|
11488
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11649
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11489
11650
|
res.json({ user: { id: user.id, username: user.username, role: user.role } });
|
|
11490
11651
|
});
|
|
11491
11652
|
app.post("/auth/logout", (req, res) => {
|
|
@@ -11607,9 +11768,9 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11607
11768
|
authAttemptsTotal.inc({ method: "oidc", outcome: "success" });
|
|
11608
11769
|
logger_default.info(`[oidc] Auto-provisioned new user: ${finalUsername} (${cfg.defaultRole})`);
|
|
11609
11770
|
}
|
|
11610
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11771
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11611
11772
|
db.logAccess(user.id, "/auth/callback", "oidc-login", "allow", req.ip ?? "unknown");
|
|
11612
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11773
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11613
11774
|
res.redirect(302, "/");
|
|
11614
11775
|
} catch (err) {
|
|
11615
11776
|
logger_default.warn("[oidc] Callback failed:", err instanceof Error ? err.message : err);
|
|
@@ -11727,7 +11888,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11727
11888
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
11728
11889
|
if (!entry) return null;
|
|
11729
11890
|
const dbPath = path32.join(entry.path, ".code-intel", "graph.db");
|
|
11730
|
-
if (!
|
|
11891
|
+
if (!fs26.existsSync(dbPath)) return null;
|
|
11731
11892
|
const repoGraph = createKnowledgeGraph();
|
|
11732
11893
|
const db = new DbManager(dbPath, true);
|
|
11733
11894
|
try {
|
|
@@ -11749,7 +11910,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11749
11910
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
11750
11911
|
if (!regEntry) continue;
|
|
11751
11912
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
11752
|
-
if (!
|
|
11913
|
+
if (!fs26.existsSync(dbPath)) continue;
|
|
11753
11914
|
const db = new DbManager(dbPath, true);
|
|
11754
11915
|
try {
|
|
11755
11916
|
await db.init();
|
|
@@ -11831,7 +11992,21 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11831
11992
|
});
|
|
11832
11993
|
});
|
|
11833
11994
|
app.post("/api/v1/search", requireToolScope("search"), async (req, res) => {
|
|
11834
|
-
const { query, limit, repo } = req.body;
|
|
11995
|
+
const { query, limit, repo, group } = req.body;
|
|
11996
|
+
if (group) {
|
|
11997
|
+
const grp = loadGroup(group);
|
|
11998
|
+
if (!grp) {
|
|
11999
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: `Group '${group}' not found`, hint: "Use /api/v1/groups to list available groups" } });
|
|
12000
|
+
return;
|
|
12001
|
+
}
|
|
12002
|
+
try {
|
|
12003
|
+
const { perRepo, merged } = await queryGroup(grp, query ?? "", limit ?? 20);
|
|
12004
|
+
res.json({ results: merged, perRepo, searchMode: "bm25", group });
|
|
12005
|
+
} catch (err) {
|
|
12006
|
+
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
|
|
12007
|
+
}
|
|
12008
|
+
return;
|
|
12009
|
+
}
|
|
11835
12010
|
const g = await getGraphForRepo(repo);
|
|
11836
12011
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
11837
12012
|
const bm25 = !repo || repo === repoName ? ensureBm25Index() : null;
|
|
@@ -11840,7 +12015,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11840
12015
|
vectorDbPath: vdbPath,
|
|
11841
12016
|
bm25Results: bm25Results ?? void 0
|
|
11842
12017
|
});
|
|
11843
|
-
res.json({ results, searchMode });
|
|
12018
|
+
res.json({ results, searchMode, repo: repo ?? repoName });
|
|
11844
12019
|
});
|
|
11845
12020
|
app.post("/api/v1/vector-search", async (req, res) => {
|
|
11846
12021
|
const { query, limit = 10 } = req.body;
|
|
@@ -11882,7 +12057,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11882
12057
|
return;
|
|
11883
12058
|
}
|
|
11884
12059
|
try {
|
|
11885
|
-
const content =
|
|
12060
|
+
const content = fs26.readFileSync(file_path, "utf-8");
|
|
11886
12061
|
res.json({ content });
|
|
11887
12062
|
} catch {
|
|
11888
12063
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -12161,7 +12336,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12161
12336
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
12162
12337
|
if (!regEntry) continue;
|
|
12163
12338
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
12164
|
-
if (!
|
|
12339
|
+
if (!fs26.existsSync(dbPath)) continue;
|
|
12165
12340
|
const db = new DbManager(dbPath, true);
|
|
12166
12341
|
try {
|
|
12167
12342
|
await db.init();
|
|
@@ -12188,7 +12363,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12188
12363
|
let edgeCount = 0;
|
|
12189
12364
|
if (regEntry) {
|
|
12190
12365
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
12191
|
-
if (
|
|
12366
|
+
if (fs26.existsSync(dbPath)) {
|
|
12192
12367
|
try {
|
|
12193
12368
|
const db = new DbManager(dbPath, true);
|
|
12194
12369
|
await db.init();
|
|
@@ -12251,7 +12426,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12251
12426
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
12252
12427
|
if (!regEntry) continue;
|
|
12253
12428
|
const candidate = path32.resolve(path32.join(regEntry.path, normalizedFile));
|
|
12254
|
-
if (
|
|
12429
|
+
if (fs26.existsSync(candidate)) {
|
|
12255
12430
|
baseDir = regEntry.path;
|
|
12256
12431
|
break;
|
|
12257
12432
|
}
|
|
@@ -12303,7 +12478,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12303
12478
|
}
|
|
12304
12479
|
let fileContent;
|
|
12305
12480
|
try {
|
|
12306
|
-
fileContent =
|
|
12481
|
+
fileContent = fs26.readFileSync(resolvedFile, "utf-8");
|
|
12307
12482
|
} catch {
|
|
12308
12483
|
res.status(404).json({
|
|
12309
12484
|
error: {
|
|
@@ -12469,7 +12644,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12469
12644
|
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() } });
|
|
12470
12645
|
}
|
|
12471
12646
|
});
|
|
12472
|
-
if (
|
|
12647
|
+
if (fs26.existsSync(WEB_DIST)) {
|
|
12473
12648
|
app.use(express.static(WEB_DIST));
|
|
12474
12649
|
app.get("/{*path}", (_req, res) => {
|
|
12475
12650
|
res.sendFile(path32.join(WEB_DIST, "index.html"));
|
|
@@ -12626,6 +12801,7 @@ async function startHttpServer(graph, repoName, port = 4747, workspaceRoot, watc
|
|
|
12626
12801
|
|
|
12627
12802
|
// src/multi-repo/index.ts
|
|
12628
12803
|
init_group_registry();
|
|
12804
|
+
init_graph_from_db();
|
|
12629
12805
|
|
|
12630
12806
|
// src/multi-repo/cross-repo-search.ts
|
|
12631
12807
|
function mergeSearchResults(...perRepoResults) {
|