@vohongtho.infotech/code-intel 1.0.0 → 1.0.2
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 +28 -4
- package/dist/cli/hook.js +348 -0
- package/dist/cli/hook.js.map +1 -0
- package/dist/cli/main.js +1921 -369
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +19 -3
- package/dist/index.js +862 -569
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-DIfCC5I3.js → es-DiINqj58.js} +1 -1
- package/dist/web/assets/index-CzWucUxe.js +354 -0
- package/dist/web/assets/index-D3zJQH9-.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
- 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, {
|
|
@@ -341,11 +528,11 @@ var init_logger = __esm({
|
|
|
341
528
|
const isProduction = process.env.NODE_ENV === "production";
|
|
342
529
|
const logLevel = process.env.LOG_LEVEL ?? "info";
|
|
343
530
|
const transports = [];
|
|
344
|
-
transports.push(new winston.transports.Console());
|
|
531
|
+
transports.push(new winston.transports.Console({ stderrLevels: ["error", "warn", "info", "http", "verbose", "debug", "silly"] }));
|
|
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
|
}
|
|
@@ -2448,6 +2635,49 @@ var init_anthropic = __esm({
|
|
|
2448
2635
|
}
|
|
2449
2636
|
});
|
|
2450
2637
|
|
|
2638
|
+
// src/llm/providers/custom.ts
|
|
2639
|
+
var custom_exports = {};
|
|
2640
|
+
__export(custom_exports, {
|
|
2641
|
+
CustomProvider: () => CustomProvider
|
|
2642
|
+
});
|
|
2643
|
+
var CustomProvider;
|
|
2644
|
+
var init_custom = __esm({
|
|
2645
|
+
"src/llm/providers/custom.ts"() {
|
|
2646
|
+
CustomProvider = class {
|
|
2647
|
+
modelName;
|
|
2648
|
+
baseUrl;
|
|
2649
|
+
apiKey;
|
|
2650
|
+
constructor(baseUrl, model, apiKey = "") {
|
|
2651
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
2652
|
+
this.modelName = model;
|
|
2653
|
+
this.apiKey = apiKey;
|
|
2654
|
+
}
|
|
2655
|
+
async summarize(prompt) {
|
|
2656
|
+
const url = `${this.baseUrl}/chat/completions`;
|
|
2657
|
+
const headers = {
|
|
2658
|
+
"Content-Type": "application/json"
|
|
2659
|
+
};
|
|
2660
|
+
if (this.apiKey) {
|
|
2661
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2662
|
+
}
|
|
2663
|
+
const body = JSON.stringify({
|
|
2664
|
+
model: this.modelName,
|
|
2665
|
+
messages: [{ role: "user", content: prompt }],
|
|
2666
|
+
max_tokens: 200,
|
|
2667
|
+
temperature: 0.3
|
|
2668
|
+
});
|
|
2669
|
+
const res = await fetch(url, { method: "POST", headers, body });
|
|
2670
|
+
if (!res.ok) {
|
|
2671
|
+
const text = await res.text().catch(() => res.statusText);
|
|
2672
|
+
throw new Error(`Custom LLM API error ${res.status}: ${text}`);
|
|
2673
|
+
}
|
|
2674
|
+
const data = await res.json();
|
|
2675
|
+
return data.choices?.[0]?.message?.content?.trim() ?? "";
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2451
2681
|
// src/llm/providers/ollama.ts
|
|
2452
2682
|
var ollama_exports = {};
|
|
2453
2683
|
__export(ollama_exports, {
|
|
@@ -2491,7 +2721,7 @@ __export(factory_exports, {
|
|
|
2491
2721
|
createLLMProvider: () => createLLMProvider
|
|
2492
2722
|
});
|
|
2493
2723
|
async function createLLMProvider(config = {}) {
|
|
2494
|
-
const { provider = "ollama", model } = config;
|
|
2724
|
+
const { provider = "ollama", model, baseUrl, apiKey } = config;
|
|
2495
2725
|
switch (provider) {
|
|
2496
2726
|
case "openai": {
|
|
2497
2727
|
const { OpenAIProvider: OpenAIProvider2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
|
|
@@ -2501,6 +2731,13 @@ async function createLLMProvider(config = {}) {
|
|
|
2501
2731
|
const { AnthropicProvider: AnthropicProvider2 } = await Promise.resolve().then(() => (init_anthropic(), anthropic_exports));
|
|
2502
2732
|
return new AnthropicProvider2(model);
|
|
2503
2733
|
}
|
|
2734
|
+
case "custom": {
|
|
2735
|
+
const { CustomProvider: CustomProvider2 } = await Promise.resolve().then(() => (init_custom(), custom_exports));
|
|
2736
|
+
const url = baseUrl ?? "http://localhost:1234/v1";
|
|
2737
|
+
const mdl = model ?? "default";
|
|
2738
|
+
const key = apiKey ?? "";
|
|
2739
|
+
return new CustomProvider2(url, mdl, key);
|
|
2740
|
+
}
|
|
2504
2741
|
case "ollama":
|
|
2505
2742
|
default: {
|
|
2506
2743
|
const { OllamaProvider: OllamaProvider2 } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
|
|
@@ -2575,106 +2812,142 @@ var init_embedder = __esm({
|
|
|
2575
2812
|
}
|
|
2576
2813
|
});
|
|
2577
2814
|
|
|
2578
|
-
// src/
|
|
2579
|
-
var
|
|
2580
|
-
__export(
|
|
2581
|
-
|
|
2582
|
-
deleteGroup: () => deleteGroup,
|
|
2583
|
-
groupExists: () => groupExists,
|
|
2584
|
-
listGroups: () => listGroups,
|
|
2585
|
-
loadGroup: () => loadGroup,
|
|
2586
|
-
loadSyncResult: () => loadSyncResult,
|
|
2587
|
-
removeMember: () => removeMember,
|
|
2588
|
-
saveGroup: () => saveGroup,
|
|
2589
|
-
saveSyncResult: () => saveSyncResult
|
|
2815
|
+
// src/storage/db-manager.ts
|
|
2816
|
+
var db_manager_exports = {};
|
|
2817
|
+
__export(db_manager_exports, {
|
|
2818
|
+
DbManager: () => DbManager
|
|
2590
2819
|
});
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2820
|
+
var DbManager;
|
|
2821
|
+
var init_db_manager = __esm({
|
|
2822
|
+
"src/storage/db-manager.ts"() {
|
|
2823
|
+
DbManager = class {
|
|
2824
|
+
db = null;
|
|
2825
|
+
conn = null;
|
|
2826
|
+
dbPath;
|
|
2827
|
+
readOnly;
|
|
2828
|
+
constructor(dbPath, readOnly = false) {
|
|
2829
|
+
this.dbPath = dbPath;
|
|
2830
|
+
this.readOnly = readOnly;
|
|
2831
|
+
}
|
|
2832
|
+
async init() {
|
|
2833
|
+
if (!this.readOnly) {
|
|
2834
|
+
fs26.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
2835
|
+
}
|
|
2836
|
+
this.db = new Database(this.dbPath, 0, true, this.readOnly);
|
|
2837
|
+
await this.db.init();
|
|
2838
|
+
this.conn = new Connection(this.db);
|
|
2839
|
+
await this.conn.init();
|
|
2840
|
+
}
|
|
2841
|
+
async query(cypher) {
|
|
2842
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
2843
|
+
const result = await this.conn.query(cypher);
|
|
2844
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
2845
|
+
const rows = await qr.getAll();
|
|
2846
|
+
qr.close();
|
|
2847
|
+
return rows;
|
|
2848
|
+
}
|
|
2849
|
+
async execute(cypher) {
|
|
2850
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
2851
|
+
const result = await this.conn.query(cypher);
|
|
2852
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
2853
|
+
qr.close();
|
|
2854
|
+
}
|
|
2855
|
+
close() {
|
|
2856
|
+
try {
|
|
2857
|
+
this.conn?.closeSync();
|
|
2858
|
+
} catch {
|
|
2859
|
+
}
|
|
2860
|
+
try {
|
|
2861
|
+
this.db?.closeSync();
|
|
2862
|
+
} catch {
|
|
2863
|
+
}
|
|
2864
|
+
this.conn = null;
|
|
2865
|
+
this.db = null;
|
|
2866
|
+
}
|
|
2867
|
+
get isOpen() {
|
|
2868
|
+
return this.conn !== null;
|
|
2869
|
+
}
|
|
2870
|
+
};
|
|
2599
2871
|
}
|
|
2600
|
-
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2872
|
+
});
|
|
2873
|
+
|
|
2874
|
+
// src/multi-repo/graph-from-db.ts
|
|
2875
|
+
var graph_from_db_exports = {};
|
|
2876
|
+
__export(graph_from_db_exports, {
|
|
2877
|
+
loadGraphFromDB: () => loadGraphFromDB
|
|
2878
|
+
});
|
|
2879
|
+
function parseRow(row, kind) {
|
|
2880
|
+
return {
|
|
2881
|
+
id: String(row["id"] ?? ""),
|
|
2882
|
+
kind,
|
|
2883
|
+
name: String(row["name"] ?? ""),
|
|
2884
|
+
filePath: String(row["file_path"] ?? ""),
|
|
2885
|
+
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
2886
|
+
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
2887
|
+
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
2888
|
+
content: row["content"] ? String(row["content"]) : void 0,
|
|
2889
|
+
metadata: row["metadata"] ? (() => {
|
|
2610
2890
|
try {
|
|
2611
|
-
|
|
2612
|
-
fs25.readFileSync(path32.join(GROUPS_DIR, file), "utf-8")
|
|
2613
|
-
);
|
|
2614
|
-
groups.push(g);
|
|
2891
|
+
return JSON.parse(String(row["metadata"]));
|
|
2615
2892
|
} catch {
|
|
2893
|
+
return void 0;
|
|
2616
2894
|
}
|
|
2617
|
-
}
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
return groups;
|
|
2621
|
-
}
|
|
2622
|
-
function deleteGroup(name) {
|
|
2623
|
-
try {
|
|
2624
|
-
fs25.unlinkSync(groupFile(name));
|
|
2625
|
-
} catch {
|
|
2626
|
-
}
|
|
2627
|
-
try {
|
|
2628
|
-
fs25.unlinkSync(path32.join(GROUPS_DIR, `${name}.sync.json`));
|
|
2629
|
-
} catch {
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
function groupExists(name) {
|
|
2633
|
-
return fs25.existsSync(groupFile(name));
|
|
2634
|
-
}
|
|
2635
|
-
function addMember(groupName, member) {
|
|
2636
|
-
const group = loadGroup(groupName);
|
|
2637
|
-
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
2638
|
-
const idx = group.members.findIndex((m) => m.groupPath === member.groupPath);
|
|
2639
|
-
if (idx >= 0) {
|
|
2640
|
-
group.members[idx] = member;
|
|
2641
|
-
} else {
|
|
2642
|
-
group.members.push(member);
|
|
2643
|
-
}
|
|
2644
|
-
saveGroup(group);
|
|
2645
|
-
return group;
|
|
2895
|
+
})() : void 0
|
|
2896
|
+
};
|
|
2646
2897
|
}
|
|
2647
|
-
function
|
|
2648
|
-
const
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2898
|
+
async function loadGraphFromDB(graph, db) {
|
|
2899
|
+
for (const table of ALL_NODE_TABLES) {
|
|
2900
|
+
const kind = TABLE_TO_KIND2[table];
|
|
2901
|
+
if (!kind) continue;
|
|
2902
|
+
let rows = [];
|
|
2903
|
+
try {
|
|
2904
|
+
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`);
|
|
2905
|
+
} catch {
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
for (const row of rows) {
|
|
2909
|
+
const node = parseRow({
|
|
2910
|
+
id: row["n.id"],
|
|
2911
|
+
name: row["n.name"],
|
|
2912
|
+
file_path: row["n.file_path"],
|
|
2913
|
+
start_line: row["n.start_line"],
|
|
2914
|
+
end_line: row["n.end_line"],
|
|
2915
|
+
exported: row["n.exported"],
|
|
2916
|
+
content: row["n.content"],
|
|
2917
|
+
metadata: row["n.metadata"]
|
|
2918
|
+
}, kind);
|
|
2919
|
+
if (node.id && node.name) graph.addNode(node);
|
|
2920
|
+
}
|
|
2654
2921
|
}
|
|
2655
|
-
saveGroup(group);
|
|
2656
|
-
return group;
|
|
2657
|
-
}
|
|
2658
|
-
function saveSyncResult(result) {
|
|
2659
|
-
fs25.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2660
|
-
fs25.writeFileSync(
|
|
2661
|
-
path32.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
2662
|
-
JSON.stringify(result, null, 2) + "\n"
|
|
2663
|
-
);
|
|
2664
|
-
}
|
|
2665
|
-
function loadSyncResult(groupName) {
|
|
2666
2922
|
try {
|
|
2667
|
-
|
|
2668
|
-
|
|
2923
|
+
const edgeRows = await db.query(
|
|
2924
|
+
`MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
|
|
2669
2925
|
);
|
|
2926
|
+
for (const row of edgeRows) {
|
|
2927
|
+
const sourceId = String(row["a.id"] ?? "");
|
|
2928
|
+
const targetId = String(row["b.id"] ?? "");
|
|
2929
|
+
const kind = String(row["e.kind"] ?? "");
|
|
2930
|
+
if (!sourceId || !targetId || !kind) continue;
|
|
2931
|
+
const edge = {
|
|
2932
|
+
id: `${sourceId}::${kind}::${targetId}`,
|
|
2933
|
+
source: sourceId,
|
|
2934
|
+
target: targetId,
|
|
2935
|
+
kind,
|
|
2936
|
+
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
2937
|
+
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
2938
|
+
};
|
|
2939
|
+
graph.addEdge(edge);
|
|
2940
|
+
}
|
|
2670
2941
|
} catch {
|
|
2671
|
-
return null;
|
|
2672
2942
|
}
|
|
2673
2943
|
}
|
|
2674
|
-
var
|
|
2675
|
-
var
|
|
2676
|
-
"src/multi-repo/
|
|
2677
|
-
|
|
2944
|
+
var TABLE_TO_KIND2;
|
|
2945
|
+
var init_graph_from_db = __esm({
|
|
2946
|
+
"src/multi-repo/graph-from-db.ts"() {
|
|
2947
|
+
init_schema();
|
|
2948
|
+
TABLE_TO_KIND2 = Object.fromEntries(
|
|
2949
|
+
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
2950
|
+
);
|
|
2678
2951
|
}
|
|
2679
2952
|
});
|
|
2680
2953
|
|
|
@@ -4000,7 +4273,7 @@ var init_secret_scanner = __esm({
|
|
|
4000
4273
|
const ignorePatterns = [...options?.ignorePatterns ?? []];
|
|
4001
4274
|
if (options?.workspaceRoot) {
|
|
4002
4275
|
try {
|
|
4003
|
-
const raw =
|
|
4276
|
+
const raw = fs26.readFileSync(path32.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
|
|
4004
4277
|
for (const line of raw.split("\n")) {
|
|
4005
4278
|
const trimmed = line.trim();
|
|
4006
4279
|
if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
|
|
@@ -4259,6 +4532,7 @@ var init_codes = __esm({
|
|
|
4259
4532
|
RATE_LIMIT_EXCEEDED: "CI-1100",
|
|
4260
4533
|
PAYLOAD_TOO_LARGE: "CI-1101",
|
|
4261
4534
|
INVALID_REQUEST: "CI-1200",
|
|
4535
|
+
CONFLICT: "CI-1409",
|
|
4262
4536
|
// Config (CI-2xxx)
|
|
4263
4537
|
CONFIG_INVALID: "CI-2000",
|
|
4264
4538
|
CONFIG_NOT_FOUND: "CI-2001",
|
|
@@ -4288,10 +4562,10 @@ var init_codes = __esm({
|
|
|
4288
4562
|
}
|
|
4289
4563
|
});
|
|
4290
4564
|
function secureMkdir(dir) {
|
|
4291
|
-
|
|
4565
|
+
fs26.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
4292
4566
|
if (process.platform !== "win32") {
|
|
4293
4567
|
try {
|
|
4294
|
-
|
|
4568
|
+
fs26.chmodSync(dir, SECURE_DIR_MODE);
|
|
4295
4569
|
} catch {
|
|
4296
4570
|
}
|
|
4297
4571
|
}
|
|
@@ -4299,17 +4573,17 @@ function secureMkdir(dir) {
|
|
|
4299
4573
|
function secureChmodFile(file) {
|
|
4300
4574
|
if (process.platform === "win32") return;
|
|
4301
4575
|
try {
|
|
4302
|
-
|
|
4576
|
+
fs26.chmodSync(file, SECURE_FILE_MODE);
|
|
4303
4577
|
} catch {
|
|
4304
4578
|
}
|
|
4305
4579
|
}
|
|
4306
4580
|
function tightenDbFiles(dir) {
|
|
4307
4581
|
if (process.platform === "win32") return;
|
|
4308
|
-
if (!
|
|
4309
|
-
for (const name of
|
|
4582
|
+
if (!fs26.existsSync(dir)) return;
|
|
4583
|
+
for (const name of fs26.readdirSync(dir)) {
|
|
4310
4584
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
4311
4585
|
try {
|
|
4312
|
-
|
|
4586
|
+
fs26.chmodSync(path32.join(dir, name), SECURE_FILE_MODE);
|
|
4313
4587
|
} catch {
|
|
4314
4588
|
}
|
|
4315
4589
|
}
|
|
@@ -4647,8 +4921,8 @@ function decryptSecrets(encrypted) {
|
|
|
4647
4921
|
return JSON.parse(plaintext.toString("utf8"));
|
|
4648
4922
|
}
|
|
4649
4923
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
4650
|
-
if (!
|
|
4651
|
-
const blob =
|
|
4924
|
+
if (!fs26.existsSync(secretsPath)) return {};
|
|
4925
|
+
const blob = fs26.readFileSync(secretsPath);
|
|
4652
4926
|
return decryptSecrets(blob);
|
|
4653
4927
|
}
|
|
4654
4928
|
function getSecret(key, secretsPath = getSecretsPath()) {
|
|
@@ -4669,11 +4943,18 @@ function getSessionTtlMs() {
|
|
|
4669
4943
|
const hours = parseInt(process.env["CODE_INTEL_SESSION_TTL_HOURS"] ?? "8", 10);
|
|
4670
4944
|
return (isNaN(hours) ? 8 : hours) * 60 * 60 * 1e3;
|
|
4671
4945
|
}
|
|
4672
|
-
function createSession(user) {
|
|
4946
|
+
function createSession(user, rememberMe = false) {
|
|
4673
4947
|
const sessionId = v4();
|
|
4674
|
-
const
|
|
4675
|
-
|
|
4676
|
-
|
|
4948
|
+
const ttlMs = rememberMe ? REMEMBER_ME_TTL_MS : getSessionTtlMs();
|
|
4949
|
+
const expiresAt = Date.now() + ttlMs;
|
|
4950
|
+
sessionStore.set(sessionId, {
|
|
4951
|
+
userId: user.id,
|
|
4952
|
+
username: user.username,
|
|
4953
|
+
role: user.role,
|
|
4954
|
+
expiresAt,
|
|
4955
|
+
ttlMs
|
|
4956
|
+
});
|
|
4957
|
+
return { sessionId, ttlMs };
|
|
4677
4958
|
}
|
|
4678
4959
|
function getSession(sessionId) {
|
|
4679
4960
|
const entry = sessionStore.get(sessionId);
|
|
@@ -4682,10 +4963,9 @@ function getSession(sessionId) {
|
|
|
4682
4963
|
sessionStore.delete(sessionId);
|
|
4683
4964
|
return null;
|
|
4684
4965
|
}
|
|
4685
|
-
const ttlMs = getSessionTtlMs();
|
|
4686
4966
|
const remaining = entry.expiresAt - Date.now();
|
|
4687
|
-
if (remaining < ttlMs * 0.75) {
|
|
4688
|
-
entry.expiresAt = Date.now() + ttlMs;
|
|
4967
|
+
if (remaining < entry.ttlMs * 0.75) {
|
|
4968
|
+
entry.expiresAt = Date.now() + entry.ttlMs;
|
|
4689
4969
|
}
|
|
4690
4970
|
return entry;
|
|
4691
4971
|
}
|
|
@@ -4705,7 +4985,7 @@ function authMiddleware(req, res, next) {
|
|
|
4705
4985
|
const session = getSession(sessionId);
|
|
4706
4986
|
if (session) {
|
|
4707
4987
|
req.user = { id: session.userId, username: session.username, role: session.role, authMethod: "session" };
|
|
4708
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
4988
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, session.ttlMs));
|
|
4709
4989
|
next();
|
|
4710
4990
|
return;
|
|
4711
4991
|
}
|
|
@@ -4875,9 +5155,9 @@ function parseCookies(cookieHeader) {
|
|
|
4875
5155
|
}
|
|
4876
5156
|
return result;
|
|
4877
5157
|
}
|
|
4878
|
-
function buildSessionCookie(sessionId) {
|
|
5158
|
+
function buildSessionCookie(sessionId, ttlMs) {
|
|
4879
5159
|
const isProduction = process.env["NODE_ENV"] === "production";
|
|
4880
|
-
const maxAge = Math.floor(getSessionTtlMs() / 1e3);
|
|
5160
|
+
const maxAge = Math.floor((ttlMs ?? getSessionTtlMs()) / 1e3);
|
|
4881
5161
|
const parts = [
|
|
4882
5162
|
`${SESSION_COOKIE_NAME}=${encodeURIComponent(sessionId)}`,
|
|
4883
5163
|
`HttpOnly`,
|
|
@@ -4894,7 +5174,7 @@ function clearSessionCookie() {
|
|
|
4894
5174
|
async function verifyPassword(plain, hash) {
|
|
4895
5175
|
return bcrypt.compare(plain, hash);
|
|
4896
5176
|
}
|
|
4897
|
-
var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
|
|
5177
|
+
var sessionStore, SESSION_COOKIE_NAME, REMEMBER_ME_TTL_MS, ROLE_RANK;
|
|
4898
5178
|
var init_middleware = __esm({
|
|
4899
5179
|
"src/auth/middleware.ts"() {
|
|
4900
5180
|
init_users_db();
|
|
@@ -4902,6 +5182,7 @@ var init_middleware = __esm({
|
|
|
4902
5182
|
init_secret_store();
|
|
4903
5183
|
sessionStore = /* @__PURE__ */ new Map();
|
|
4904
5184
|
SESSION_COOKIE_NAME = "code_intel_session";
|
|
5185
|
+
REMEMBER_ME_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
4905
5186
|
ROLE_RANK = {
|
|
4906
5187
|
viewer: 1,
|
|
4907
5188
|
"repo-owner": 2,
|
|
@@ -5027,191 +5308,19 @@ var init_websocket_server = __esm({
|
|
|
5027
5308
|
} catch {
|
|
5028
5309
|
}
|
|
5029
5310
|
}
|
|
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;
|
|
5105
|
-
}
|
|
5106
|
-
},
|
|
5107
|
-
removeNodeCascade(id) {
|
|
5108
|
-
const fromEdges = edgesFromNode.get(id);
|
|
5109
|
-
if (fromEdges) {
|
|
5110
|
-
for (const edgeId of [...fromEdges]) {
|
|
5111
|
-
const edge = edges.get(edgeId);
|
|
5112
|
-
if (edge) {
|
|
5113
|
-
unindexEdge(edge);
|
|
5114
|
-
edges.delete(edgeId);
|
|
5115
|
-
}
|
|
5116
|
-
}
|
|
5117
|
-
}
|
|
5118
|
-
const toEdges = edgesToNode.get(id);
|
|
5119
|
-
if (toEdges) {
|
|
5120
|
-
for (const edgeId of [...toEdges]) {
|
|
5121
|
-
const edge = edges.get(edgeId);
|
|
5122
|
-
if (edge) {
|
|
5123
|
-
unindexEdge(edge);
|
|
5124
|
-
edges.delete(edgeId);
|
|
5125
|
-
}
|
|
5126
|
-
}
|
|
5127
|
-
}
|
|
5128
|
-
edgesFromNode.delete(id);
|
|
5129
|
-
edgesToNode.delete(id);
|
|
5130
|
-
nodes.delete(id);
|
|
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
|
-
}
|
|
5157
|
-
|
|
5158
|
-
// src/graph/index.ts
|
|
5159
|
-
init_id_generator();
|
|
5160
|
-
|
|
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
|
-
}
|
|
5311
|
+
this.clients.clear();
|
|
5312
|
+
this.wss.close();
|
|
5313
|
+
}
|
|
5314
|
+
};
|
|
5205
5315
|
}
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
)`];
|
|
5212
|
-
}
|
|
5316
|
+
});
|
|
5317
|
+
|
|
5318
|
+
// src/graph/index.ts
|
|
5319
|
+
init_knowledge_graph();
|
|
5320
|
+
init_id_generator();
|
|
5213
5321
|
|
|
5214
5322
|
// src/graph/lazy-knowledge-graph.ts
|
|
5323
|
+
init_schema();
|
|
5215
5324
|
init_logger();
|
|
5216
5325
|
Object.fromEntries(
|
|
5217
5326
|
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
@@ -6189,7 +6298,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
6189
6298
|
]);
|
|
6190
6299
|
function loadIgnorePatterns(workspaceRoot) {
|
|
6191
6300
|
try {
|
|
6192
|
-
const raw =
|
|
6301
|
+
const raw = fs26.readFileSync(path32.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
6193
6302
|
const extras = /* @__PURE__ */ new Set();
|
|
6194
6303
|
for (const line of raw.split("\n")) {
|
|
6195
6304
|
const trimmed = line.trim();
|
|
@@ -6213,7 +6322,7 @@ var scanPhase = {
|
|
|
6213
6322
|
function walk(dir) {
|
|
6214
6323
|
let entries;
|
|
6215
6324
|
try {
|
|
6216
|
-
entries =
|
|
6325
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
6217
6326
|
} catch {
|
|
6218
6327
|
return;
|
|
6219
6328
|
}
|
|
@@ -6230,7 +6339,7 @@ var scanPhase = {
|
|
|
6230
6339
|
if (!extensions.has(ext)) continue;
|
|
6231
6340
|
const fullPath = path32.join(dir, name);
|
|
6232
6341
|
try {
|
|
6233
|
-
const stat =
|
|
6342
|
+
const stat = fs26.statSync(fullPath);
|
|
6234
6343
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
6235
6344
|
} catch {
|
|
6236
6345
|
continue;
|
|
@@ -6452,8 +6561,8 @@ var LLMGovernanceLogger = class {
|
|
|
6452
6561
|
...entry
|
|
6453
6562
|
};
|
|
6454
6563
|
const logPath = this.getLogPath();
|
|
6455
|
-
|
|
6456
|
-
|
|
6564
|
+
fs26.mkdirSync(path32.dirname(logPath), { recursive: true });
|
|
6565
|
+
fs26.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
6457
6566
|
} catch {
|
|
6458
6567
|
}
|
|
6459
6568
|
}
|
|
@@ -6463,7 +6572,7 @@ var LLMGovernanceLogger = class {
|
|
|
6463
6572
|
*/
|
|
6464
6573
|
readLog(limit = 100) {
|
|
6465
6574
|
try {
|
|
6466
|
-
const raw =
|
|
6575
|
+
const raw = fs26.readFileSync(this.getLogPath(), "utf-8");
|
|
6467
6576
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
6468
6577
|
return lines.map((l) => JSON.parse(l));
|
|
6469
6578
|
} catch {
|
|
@@ -6854,7 +6963,7 @@ init_embedder();
|
|
|
6854
6963
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
6855
6964
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
|
|
6856
6965
|
const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
6857
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
6966
|
+
const hasVectorDb = Boolean(vectorDbPath && fs26.existsSync(vectorDbPath));
|
|
6858
6967
|
if (!hasVectorDb) {
|
|
6859
6968
|
const bm25Results2 = await bm25Promise;
|
|
6860
6969
|
return {
|
|
@@ -6919,6 +7028,41 @@ function nodeToDoc(node) {
|
|
|
6919
7028
|
(node.content ?? "").slice(0, 1e3)
|
|
6920
7029
|
].join(" ");
|
|
6921
7030
|
}
|
|
7031
|
+
function heapTopK(scores, k) {
|
|
7032
|
+
if (k <= 0) return [];
|
|
7033
|
+
const heap = [];
|
|
7034
|
+
function heapifyUp(i) {
|
|
7035
|
+
while (i > 0) {
|
|
7036
|
+
const parent = i - 1 >> 1;
|
|
7037
|
+
if (heap[parent][1] > heap[i][1]) {
|
|
7038
|
+
[heap[parent], heap[i]] = [heap[i], heap[parent]];
|
|
7039
|
+
i = parent;
|
|
7040
|
+
} else break;
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
function heapifyDown(i) {
|
|
7044
|
+
const n = heap.length;
|
|
7045
|
+
while (true) {
|
|
7046
|
+
let smallest = i;
|
|
7047
|
+
const l = 2 * i + 1, r = 2 * i + 2;
|
|
7048
|
+
if (l < n && heap[l][1] < heap[smallest][1]) smallest = l;
|
|
7049
|
+
if (r < n && heap[r][1] < heap[smallest][1]) smallest = r;
|
|
7050
|
+
if (smallest === i) break;
|
|
7051
|
+
[heap[smallest], heap[i]] = [heap[i], heap[smallest]];
|
|
7052
|
+
i = smallest;
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
for (const [nodeId, score] of scores) {
|
|
7056
|
+
if (heap.length < k) {
|
|
7057
|
+
heap.push([nodeId, score]);
|
|
7058
|
+
heapifyUp(heap.length - 1);
|
|
7059
|
+
} else if (score > heap[0][1]) {
|
|
7060
|
+
heap[0] = [nodeId, score];
|
|
7061
|
+
heapifyDown(0);
|
|
7062
|
+
}
|
|
7063
|
+
}
|
|
7064
|
+
return heap.sort((a, b) => b[1] - a[1]);
|
|
7065
|
+
}
|
|
6922
7066
|
var Bm25Index = class {
|
|
6923
7067
|
constructor(dbPath) {
|
|
6924
7068
|
this.dbPath = dbPath;
|
|
@@ -6971,10 +7115,10 @@ var Bm25Index = class {
|
|
|
6971
7115
|
postings.push({ nodeId, tf: count });
|
|
6972
7116
|
}
|
|
6973
7117
|
}
|
|
6974
|
-
|
|
7118
|
+
fs26.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
6975
7119
|
for (const f of [this.dbPath, `${this.dbPath}-shm`, `${this.dbPath}-wal`]) {
|
|
6976
7120
|
try {
|
|
6977
|
-
if (
|
|
7121
|
+
if (fs26.existsSync(f)) fs26.unlinkSync(f);
|
|
6978
7122
|
} catch {
|
|
6979
7123
|
}
|
|
6980
7124
|
}
|
|
@@ -7012,7 +7156,7 @@ var Bm25Index = class {
|
|
|
7012
7156
|
* Called once on `serve` startup.
|
|
7013
7157
|
*/
|
|
7014
7158
|
load() {
|
|
7015
|
-
if (!
|
|
7159
|
+
if (!fs26.existsSync(this.dbPath)) return;
|
|
7016
7160
|
const db = new Database2(this.dbPath, { readonly: true });
|
|
7017
7161
|
try {
|
|
7018
7162
|
const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
|
|
@@ -7046,8 +7190,13 @@ var Bm25Index = class {
|
|
|
7046
7190
|
}
|
|
7047
7191
|
// ── Search ──────────────────────────────────────────────────────────────────
|
|
7048
7192
|
/**
|
|
7049
|
-
* BM25 search.
|
|
7050
|
-
*
|
|
7193
|
+
* BM25 search.
|
|
7194
|
+
*
|
|
7195
|
+
* Performance strategy:
|
|
7196
|
+
* 1. Skip ultra-high-df terms (df/N > 0.6) — near-zero IDF, dominate posting
|
|
7197
|
+
* lists for common words like "function", "return", "export" in large repos.
|
|
7198
|
+
* 2. Min-heap top-K selection — O(n log k) instead of full O(n log n) sort.
|
|
7199
|
+
* For k=10 and n=30,000 candidates this is ~10× faster than Array.sort.
|
|
7051
7200
|
*/
|
|
7052
7201
|
search(query, limit) {
|
|
7053
7202
|
if (!this._loaded || this.invertedIndex.size === 0) return [];
|
|
@@ -7060,6 +7209,7 @@ var Bm25Index = class {
|
|
|
7060
7209
|
const postings = this.invertedIndex.get(term);
|
|
7061
7210
|
if (!postings) continue;
|
|
7062
7211
|
const df = postings.length;
|
|
7212
|
+
if (N > 100 && df / N > 0.6) continue;
|
|
7063
7213
|
const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
|
|
7064
7214
|
for (const { nodeId, tf } of postings) {
|
|
7065
7215
|
const dl = this.docLengths.get(nodeId) ?? avgdl;
|
|
@@ -7067,10 +7217,9 @@ var Bm25Index = class {
|
|
|
7067
7217
|
scores.set(nodeId, (scores.get(nodeId) ?? 0) + score);
|
|
7068
7218
|
}
|
|
7069
7219
|
}
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
return topK.map(([nodeId, score]) => {
|
|
7220
|
+
if (scores.size === 0) return [];
|
|
7221
|
+
const topEntries = heapTopK(scores, limit);
|
|
7222
|
+
return topEntries.map(([nodeId, score]) => {
|
|
7074
7223
|
const meta = this.nodeMeta.get(nodeId);
|
|
7075
7224
|
return {
|
|
7076
7225
|
nodeId,
|
|
@@ -7089,7 +7238,7 @@ var Bm25Index = class {
|
|
|
7089
7238
|
* Works even if `load()` was not called (reads affected terms directly from DB).
|
|
7090
7239
|
*/
|
|
7091
7240
|
updateNodes(nodes) {
|
|
7092
|
-
if (!
|
|
7241
|
+
if (!fs26.existsSync(this.dbPath)) return;
|
|
7093
7242
|
if (nodes.length === 0) return;
|
|
7094
7243
|
const changedIds = new Set(nodes.map((n) => n.id));
|
|
7095
7244
|
const newTermFreqs = /* @__PURE__ */ new Map();
|
|
@@ -7165,56 +7314,15 @@ var Bm25Index = class {
|
|
|
7165
7314
|
function getBm25DbPath(workspaceRoot) {
|
|
7166
7315
|
return path32.join(workspaceRoot, ".code-intel", "bm25.db");
|
|
7167
7316
|
}
|
|
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
|
-
};
|
|
7317
|
+
|
|
7318
|
+
// src/storage/index.ts
|
|
7319
|
+
init_db_manager();
|
|
7320
|
+
init_schema();
|
|
7321
|
+
|
|
7322
|
+
// src/storage/csv-writer.ts
|
|
7323
|
+
init_schema();
|
|
7216
7324
|
function writeNodeCSVs(graph, outputDir) {
|
|
7217
|
-
|
|
7325
|
+
fs26.mkdirSync(outputDir, { recursive: true });
|
|
7218
7326
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
7219
7327
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
7220
7328
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -7242,12 +7350,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
7242
7350
|
);
|
|
7243
7351
|
}
|
|
7244
7352
|
for (const [table, lines] of tableBuffers) {
|
|
7245
|
-
|
|
7353
|
+
fs26.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
7246
7354
|
}
|
|
7247
7355
|
return tableFilePaths;
|
|
7248
7356
|
}
|
|
7249
7357
|
function writeEdgeCSV(graph, outputDir) {
|
|
7250
|
-
|
|
7358
|
+
fs26.mkdirSync(outputDir, { recursive: true });
|
|
7251
7359
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
7252
7360
|
const groups = /* @__PURE__ */ new Map();
|
|
7253
7361
|
for (const edge of graph.allEdges()) {
|
|
@@ -7273,7 +7381,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
7273
7381
|
}
|
|
7274
7382
|
const result = [];
|
|
7275
7383
|
for (const group of groups.values()) {
|
|
7276
|
-
|
|
7384
|
+
fs26.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
7277
7385
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
7278
7386
|
}
|
|
7279
7387
|
return result;
|
|
@@ -7290,6 +7398,9 @@ function escapeNewlines(s) {
|
|
|
7290
7398
|
if (!s.includes("\n") && !s.includes("\r")) return s;
|
|
7291
7399
|
return s.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
7292
7400
|
}
|
|
7401
|
+
|
|
7402
|
+
// src/storage/graph-loader.ts
|
|
7403
|
+
init_schema();
|
|
7293
7404
|
async function loadGraphToDB(graph, dbManager) {
|
|
7294
7405
|
for (const table of ALL_NODE_TABLES) {
|
|
7295
7406
|
await dbManager.execute(getCreateNodeTableDDL(table));
|
|
@@ -7301,7 +7412,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7301
7412
|
} catch {
|
|
7302
7413
|
}
|
|
7303
7414
|
}
|
|
7304
|
-
const tmpDir =
|
|
7415
|
+
const tmpDir = fs26.mkdtempSync(path32.join(os13.tmpdir(), "code-intel-csv-"));
|
|
7305
7416
|
try {
|
|
7306
7417
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
7307
7418
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -7320,8 +7431,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7320
7431
|
}
|
|
7321
7432
|
let nodeCount = 0;
|
|
7322
7433
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
7323
|
-
if (!
|
|
7324
|
-
const stat =
|
|
7434
|
+
if (!fs26.existsSync(csvPath)) continue;
|
|
7435
|
+
const stat = fs26.statSync(csvPath);
|
|
7325
7436
|
if (stat.size < 50) continue;
|
|
7326
7437
|
try {
|
|
7327
7438
|
await dbManager.execute(
|
|
@@ -7334,8 +7445,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7334
7445
|
}
|
|
7335
7446
|
let edgeCount = 0;
|
|
7336
7447
|
for (const group of edgeGroups) {
|
|
7337
|
-
if (!
|
|
7338
|
-
const stat =
|
|
7448
|
+
if (!fs26.existsSync(group.filePath)) continue;
|
|
7449
|
+
const stat = fs26.statSync(group.filePath);
|
|
7339
7450
|
if (stat.size < 50) continue;
|
|
7340
7451
|
try {
|
|
7341
7452
|
await dbManager.execute(
|
|
@@ -7349,7 +7460,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
7349
7460
|
return { nodeCount, edgeCount };
|
|
7350
7461
|
} finally {
|
|
7351
7462
|
try {
|
|
7352
|
-
|
|
7463
|
+
fs26.rmSync(tmpDir, { recursive: true, force: true });
|
|
7353
7464
|
} catch {
|
|
7354
7465
|
}
|
|
7355
7466
|
}
|
|
@@ -7405,15 +7516,15 @@ var GLOBAL_DIR = path32.join(os13.homedir(), ".code-intel");
|
|
|
7405
7516
|
var REPOS_FILE = path32.join(GLOBAL_DIR, "repos.json");
|
|
7406
7517
|
function loadRegistry() {
|
|
7407
7518
|
try {
|
|
7408
|
-
const data =
|
|
7519
|
+
const data = fs26.readFileSync(REPOS_FILE, "utf-8");
|
|
7409
7520
|
return JSON.parse(data);
|
|
7410
7521
|
} catch {
|
|
7411
7522
|
return [];
|
|
7412
7523
|
}
|
|
7413
7524
|
}
|
|
7414
7525
|
function saveRegistry(entries) {
|
|
7415
|
-
|
|
7416
|
-
|
|
7526
|
+
fs26.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
7527
|
+
fs26.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
7417
7528
|
}
|
|
7418
7529
|
function upsertRepo(entry) {
|
|
7419
7530
|
const entries = loadRegistry();
|
|
@@ -7431,12 +7542,12 @@ function removeRepo(repoPath) {
|
|
|
7431
7542
|
}
|
|
7432
7543
|
function saveMetadata(repoDir, metadata) {
|
|
7433
7544
|
const metaDir = path32.join(repoDir, ".code-intel");
|
|
7434
|
-
|
|
7435
|
-
|
|
7545
|
+
fs26.mkdirSync(metaDir, { recursive: true });
|
|
7546
|
+
fs26.writeFileSync(path32.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
7436
7547
|
}
|
|
7437
7548
|
function loadMetadata(repoDir) {
|
|
7438
7549
|
try {
|
|
7439
|
-
const data =
|
|
7550
|
+
const data = fs26.readFileSync(path32.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
7440
7551
|
return JSON.parse(data);
|
|
7441
7552
|
} catch {
|
|
7442
7553
|
return null;
|
|
@@ -7448,81 +7559,93 @@ function getDbPath(repoDir) {
|
|
|
7448
7559
|
function getVectorDbPath(repoDir) {
|
|
7449
7560
|
return path32.join(repoDir, ".code-intel", "vector.db");
|
|
7450
7561
|
}
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7562
|
+
var GROUPS_DIR = path32.join(os13.homedir(), ".code-intel", "groups");
|
|
7563
|
+
function groupFile(name) {
|
|
7564
|
+
return path32.join(GROUPS_DIR, `${name}.json`);
|
|
7565
|
+
}
|
|
7566
|
+
function loadGroup(name) {
|
|
7567
|
+
try {
|
|
7568
|
+
return JSON.parse(fs26.readFileSync(groupFile(name), "utf-8"));
|
|
7569
|
+
} catch {
|
|
7570
|
+
return null;
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
function saveGroup(group) {
|
|
7574
|
+
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
7575
|
+
fs26.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
7576
|
+
}
|
|
7577
|
+
function listGroups() {
|
|
7578
|
+
const groups = [];
|
|
7579
|
+
try {
|
|
7580
|
+
for (const file of fs26.readdirSync(GROUPS_DIR)) {
|
|
7581
|
+
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
7470
7582
|
try {
|
|
7471
|
-
|
|
7583
|
+
const g = JSON.parse(
|
|
7584
|
+
fs26.readFileSync(path32.join(GROUPS_DIR, file), "utf-8")
|
|
7585
|
+
);
|
|
7586
|
+
groups.push(g);
|
|
7472
7587
|
} catch {
|
|
7473
|
-
return void 0;
|
|
7474
7588
|
}
|
|
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
7589
|
}
|
|
7590
|
+
} catch {
|
|
7591
|
+
}
|
|
7592
|
+
return groups;
|
|
7593
|
+
}
|
|
7594
|
+
function deleteGroup(name) {
|
|
7595
|
+
try {
|
|
7596
|
+
fs26.unlinkSync(groupFile(name));
|
|
7597
|
+
} catch {
|
|
7501
7598
|
}
|
|
7502
7599
|
try {
|
|
7503
|
-
|
|
7504
|
-
|
|
7600
|
+
fs26.unlinkSync(path32.join(GROUPS_DIR, `${name}.sync.json`));
|
|
7601
|
+
} catch {
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
function groupExists(name) {
|
|
7605
|
+
return fs26.existsSync(groupFile(name));
|
|
7606
|
+
}
|
|
7607
|
+
function addMember(groupName, member) {
|
|
7608
|
+
const group = loadGroup(groupName);
|
|
7609
|
+
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
7610
|
+
const idx = group.members.findIndex((m) => m.groupPath === member.groupPath);
|
|
7611
|
+
if (idx >= 0) {
|
|
7612
|
+
group.members[idx] = member;
|
|
7613
|
+
} else {
|
|
7614
|
+
group.members.push(member);
|
|
7615
|
+
}
|
|
7616
|
+
saveGroup(group);
|
|
7617
|
+
return group;
|
|
7618
|
+
}
|
|
7619
|
+
function removeMember(groupName, groupPath) {
|
|
7620
|
+
const group = loadGroup(groupName);
|
|
7621
|
+
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
7622
|
+
const before = group.members.length;
|
|
7623
|
+
group.members = group.members.filter((m) => m.groupPath !== groupPath);
|
|
7624
|
+
if (group.members.length === before) {
|
|
7625
|
+
throw new Error(`No member at path "${groupPath}" in group "${groupName}".`);
|
|
7626
|
+
}
|
|
7627
|
+
saveGroup(group);
|
|
7628
|
+
return group;
|
|
7629
|
+
}
|
|
7630
|
+
function saveSyncResult(result) {
|
|
7631
|
+
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
7632
|
+
fs26.writeFileSync(
|
|
7633
|
+
path32.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
7634
|
+
JSON.stringify(result, null, 2) + "\n"
|
|
7635
|
+
);
|
|
7636
|
+
}
|
|
7637
|
+
function loadSyncResult(groupName) {
|
|
7638
|
+
try {
|
|
7639
|
+
return JSON.parse(
|
|
7640
|
+
fs26.readFileSync(path32.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
7505
7641
|
);
|
|
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
7642
|
} catch {
|
|
7643
|
+
return null;
|
|
7522
7644
|
}
|
|
7523
7645
|
}
|
|
7524
|
-
|
|
7525
|
-
|
|
7646
|
+
init_db_manager();
|
|
7647
|
+
init_knowledge_graph();
|
|
7648
|
+
init_graph_from_db();
|
|
7526
7649
|
init_logger();
|
|
7527
7650
|
function scanForFiles(root, matcher, maxDepth = 2) {
|
|
7528
7651
|
const results = [];
|
|
@@ -7530,7 +7653,7 @@ function scanForFiles(root, matcher, maxDepth = 2) {
|
|
|
7530
7653
|
if (depth > maxDepth) return;
|
|
7531
7654
|
let entries;
|
|
7532
7655
|
try {
|
|
7533
|
-
entries =
|
|
7656
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
7534
7657
|
} catch {
|
|
7535
7658
|
return;
|
|
7536
7659
|
}
|
|
@@ -7559,7 +7682,7 @@ var OPENAPI_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
7559
7682
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
7560
7683
|
function tryParseFile(filePath) {
|
|
7561
7684
|
const ext = path32.extname(filePath).toLowerCase();
|
|
7562
|
-
const content =
|
|
7685
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
7563
7686
|
if (ext === ".json") {
|
|
7564
7687
|
try {
|
|
7565
7688
|
return JSON.parse(content);
|
|
@@ -7613,7 +7736,7 @@ async function parseGraphQLContracts(repoRoot) {
|
|
|
7613
7736
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
7614
7737
|
const contracts = [];
|
|
7615
7738
|
for (const filePath of files) {
|
|
7616
|
-
const content =
|
|
7739
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
7617
7740
|
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
7618
7741
|
let match;
|
|
7619
7742
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
@@ -7644,7 +7767,7 @@ async function parseProtoContracts(repoRoot) {
|
|
|
7644
7767
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
7645
7768
|
const contracts = [];
|
|
7646
7769
|
for (const filePath of files) {
|
|
7647
|
-
const content =
|
|
7770
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
7648
7771
|
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
7649
7772
|
let serviceMatch;
|
|
7650
7773
|
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
@@ -7850,7 +7973,7 @@ async function syncGroup(group) {
|
|
|
7850
7973
|
continue;
|
|
7851
7974
|
}
|
|
7852
7975
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
7853
|
-
if (!
|
|
7976
|
+
if (!fs26.existsSync(dbPath)) {
|
|
7854
7977
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
7855
7978
|
continue;
|
|
7856
7979
|
}
|
|
@@ -7916,6 +8039,9 @@ async function syncGroup(group) {
|
|
|
7916
8039
|
links
|
|
7917
8040
|
};
|
|
7918
8041
|
}
|
|
8042
|
+
init_db_manager();
|
|
8043
|
+
init_knowledge_graph();
|
|
8044
|
+
init_graph_from_db();
|
|
7919
8045
|
async function queryGroup(group, query, limit = 20) {
|
|
7920
8046
|
const registry = loadRegistry();
|
|
7921
8047
|
const perRepo = [];
|
|
@@ -7924,7 +8050,7 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
7924
8050
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
7925
8051
|
if (!regEntry) continue;
|
|
7926
8052
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
7927
|
-
if (!
|
|
8053
|
+
if (!fs26.existsSync(dbPath)) continue;
|
|
7928
8054
|
const graph = createKnowledgeGraph();
|
|
7929
8055
|
const db = new DbManager(dbPath, true);
|
|
7930
8056
|
try {
|
|
@@ -8572,11 +8698,30 @@ function summarizeCluster(graph, cluster) {
|
|
|
8572
8698
|
}
|
|
8573
8699
|
|
|
8574
8700
|
// src/mcp-server/server.ts
|
|
8701
|
+
function compact(obj) {
|
|
8702
|
+
return JSON.stringify(obj, (_key, value) => value === null || value === void 0 ? void 0 : value);
|
|
8703
|
+
}
|
|
8575
8704
|
function createMcpServer(graph, repoName, workspaceRoot) {
|
|
8576
8705
|
const server = new Server(
|
|
8577
8706
|
{ name: "code-intel", version: "0.1.0" },
|
|
8578
8707
|
{ capabilities: { tools: {}, resources: {} } }
|
|
8579
8708
|
);
|
|
8709
|
+
let bm25Index = null;
|
|
8710
|
+
function ensureBm25Index() {
|
|
8711
|
+
if (bm25Index) return bm25Index;
|
|
8712
|
+
if (!workspaceRoot) return null;
|
|
8713
|
+
try {
|
|
8714
|
+
const idx = new Bm25Index(getBm25DbPath(workspaceRoot));
|
|
8715
|
+
idx.load();
|
|
8716
|
+
bm25Index = idx;
|
|
8717
|
+
return bm25Index;
|
|
8718
|
+
} catch {
|
|
8719
|
+
return null;
|
|
8720
|
+
}
|
|
8721
|
+
}
|
|
8722
|
+
if (workspaceRoot) {
|
|
8723
|
+
setImmediate(() => ensureBm25Index());
|
|
8724
|
+
}
|
|
8580
8725
|
const _tokenProp = {
|
|
8581
8726
|
_token: { type: "string", description: "Required if CODE_INTEL_TOKEN is configured" }
|
|
8582
8727
|
};
|
|
@@ -8596,13 +8741,15 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8596
8741
|
// ── Search & inspect ─────────────────────────────────────────────────
|
|
8597
8742
|
{
|
|
8598
8743
|
name: "search",
|
|
8599
|
-
description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc.",
|
|
8744
|
+
description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc. Optionally scope to a specific repo or group.",
|
|
8600
8745
|
inputSchema: {
|
|
8601
8746
|
type: "object",
|
|
8602
8747
|
properties: {
|
|
8603
8748
|
query: { type: "string", description: "Search query (symbol name, keyword, or partial match)" },
|
|
8604
8749
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8605
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
8750
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
8751
|
+
repo: { type: "string", description: "Scope search to a specific indexed repo name (optional; defaults to current repo)" },
|
|
8752
|
+
group: { type: "string", description: "Scope search across all repos in a group via cross-repo RRF merge (optional; overrides repo)" },
|
|
8606
8753
|
..._tokenProp
|
|
8607
8754
|
},
|
|
8608
8755
|
required: ["query"]
|
|
@@ -8632,7 +8779,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8632
8779
|
enum: ["callers", "callees", "both"],
|
|
8633
8780
|
description: "Which direction to trace \u2014 callers (who depends on it), callees (what it depends on), or both (default: both)"
|
|
8634
8781
|
},
|
|
8635
|
-
max_hops: { type: "number", description: "Maximum traversal depth (default:
|
|
8782
|
+
max_hops: { type: "number", description: "Maximum traversal depth (default: 2, max: 10)" },
|
|
8636
8783
|
..._tokenProp
|
|
8637
8784
|
},
|
|
8638
8785
|
required: ["target"]
|
|
@@ -8646,7 +8793,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8646
8793
|
properties: {
|
|
8647
8794
|
file_path: { type: "string", description: 'File path (partial match is supported, e.g. "auth/login.ts")' },
|
|
8648
8795
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8649
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
8796
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
8650
8797
|
..._tokenProp
|
|
8651
8798
|
},
|
|
8652
8799
|
required: ["file_path"]
|
|
@@ -8677,7 +8824,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8677
8824
|
description: "Filter by node kind: function | class | interface | method | type_alias | constant | enum (optional)"
|
|
8678
8825
|
},
|
|
8679
8826
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8680
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
8827
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
8681
8828
|
..._tokenProp
|
|
8682
8829
|
}
|
|
8683
8830
|
}
|
|
@@ -8695,7 +8842,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8695
8842
|
type: "object",
|
|
8696
8843
|
properties: {
|
|
8697
8844
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8698
|
-
limit: { type: "number", description: "Max clusters per page (default:
|
|
8845
|
+
limit: { type: "number", description: "Max clusters per page (default: 10, max: 500)" },
|
|
8699
8846
|
..._tokenProp
|
|
8700
8847
|
}
|
|
8701
8848
|
}
|
|
@@ -8707,7 +8854,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8707
8854
|
type: "object",
|
|
8708
8855
|
properties: {
|
|
8709
8856
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
8710
|
-
limit: { type: "number", description: "Max flows per page (default:
|
|
8857
|
+
limit: { type: "number", description: "Max flows per page (default: 10, max: 500)" },
|
|
8711
8858
|
..._tokenProp
|
|
8712
8859
|
}
|
|
8713
8860
|
}
|
|
@@ -8861,7 +9008,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8861
9008
|
},
|
|
8862
9009
|
maxHops: {
|
|
8863
9010
|
type: "number",
|
|
8864
|
-
description: "Maximum BFS depth for blast radius (default:
|
|
9011
|
+
description: "Maximum BFS depth for blast radius (default: 2, max: 10)"
|
|
8865
9012
|
},
|
|
8866
9013
|
..._tokenProp
|
|
8867
9014
|
}
|
|
@@ -8989,13 +9136,13 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8989
9136
|
const providedToken = a._token;
|
|
8990
9137
|
if (providedToken !== expectedToken) {
|
|
8991
9138
|
return {
|
|
8992
|
-
content: [{ type: "text", text:
|
|
9139
|
+
content: [{ type: "text", text: compact({ error: "Unauthorized: invalid or missing CODE_INTEL_TOKEN" }) }],
|
|
8993
9140
|
isError: true
|
|
8994
9141
|
};
|
|
8995
9142
|
}
|
|
8996
9143
|
}
|
|
8997
9144
|
const startMs = Date.now();
|
|
8998
|
-
const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot);
|
|
9145
|
+
const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot, ensureBm25Index);
|
|
8999
9146
|
const MCP_TIMEOUT_MS = parseInt(process.env["CODE_INTEL_MCP_TIMEOUT_MS"] ?? "30000", 10);
|
|
9000
9147
|
let timeoutHandle = null;
|
|
9001
9148
|
let timedOut = false;
|
|
@@ -9027,7 +9174,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9027
9174
|
mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
|
|
9028
9175
|
if (timedOut) {
|
|
9029
9176
|
return {
|
|
9030
|
-
content: [{ type: "text", text:
|
|
9177
|
+
content: [{ type: "text", text: compact({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
|
|
9031
9178
|
isError: false
|
|
9032
9179
|
};
|
|
9033
9180
|
}
|
|
@@ -9042,7 +9189,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
9042
9189
|
registerResources(server, graph, repoName);
|
|
9043
9190
|
return server;
|
|
9044
9191
|
}
|
|
9045
|
-
async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
9192
|
+
async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolver) {
|
|
9046
9193
|
switch (name) {
|
|
9047
9194
|
// ── repos ──────────────────────────────────────────────────────────────
|
|
9048
9195
|
case "repos": {
|
|
@@ -9050,10 +9197,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9050
9197
|
return {
|
|
9051
9198
|
content: [{
|
|
9052
9199
|
type: "text",
|
|
9053
|
-
text:
|
|
9054
|
-
registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
|
|
9055
|
-
null,
|
|
9056
|
-
2
|
|
9200
|
+
text: compact(
|
|
9201
|
+
registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
|
|
9057
9202
|
)
|
|
9058
9203
|
}]
|
|
9059
9204
|
};
|
|
@@ -9081,13 +9226,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9081
9226
|
return {
|
|
9082
9227
|
content: [{
|
|
9083
9228
|
type: "text",
|
|
9084
|
-
text:
|
|
9229
|
+
text: compact({
|
|
9085
9230
|
repo: repoName,
|
|
9086
9231
|
stats: graph.size,
|
|
9087
9232
|
nodeCounts: kindCounts,
|
|
9088
9233
|
edgeCounts,
|
|
9089
9234
|
health
|
|
9090
|
-
}
|
|
9235
|
+
})
|
|
9091
9236
|
}]
|
|
9092
9237
|
};
|
|
9093
9238
|
}
|
|
@@ -9095,15 +9240,59 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9095
9240
|
case "search": {
|
|
9096
9241
|
const query = a.query;
|
|
9097
9242
|
const offset = a.offset ?? 0;
|
|
9098
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9243
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9244
|
+
if (a.group) {
|
|
9245
|
+
const grp = loadGroup(a.group);
|
|
9246
|
+
if (!grp) {
|
|
9247
|
+
return { content: [{ type: "text", text: `Group "${a.group}" not found. Use list_groups to see available groups.` }] };
|
|
9248
|
+
}
|
|
9249
|
+
const { perRepo, merged } = await queryGroup(grp, query, effectiveLimit + offset);
|
|
9250
|
+
const paged = merged.slice(offset, offset + effectiveLimit);
|
|
9251
|
+
return {
|
|
9252
|
+
content: [{
|
|
9253
|
+
type: "text",
|
|
9254
|
+
text: compact({
|
|
9255
|
+
results: paged,
|
|
9256
|
+
perRepo,
|
|
9257
|
+
searchMode: "bm25-cross-repo",
|
|
9258
|
+
group: a.group,
|
|
9259
|
+
total: merged.length,
|
|
9260
|
+
offset,
|
|
9261
|
+
limit: effectiveLimit,
|
|
9262
|
+
hasMore: offset + effectiveLimit < merged.length
|
|
9263
|
+
})
|
|
9264
|
+
}]
|
|
9265
|
+
};
|
|
9266
|
+
}
|
|
9267
|
+
const repoGraph = a.repo ? await (async () => {
|
|
9268
|
+
const registry = loadRegistry();
|
|
9269
|
+
const entry = registry.find((r) => r.name === a.repo || r.path === a.repo);
|
|
9270
|
+
if (!entry) return graph;
|
|
9271
|
+
const { DbManager: DbMgr } = await Promise.resolve().then(() => (init_db_manager(), db_manager_exports));
|
|
9272
|
+
const { loadGraphFromDB: loadG } = await Promise.resolve().then(() => (init_graph_from_db(), graph_from_db_exports));
|
|
9273
|
+
const { createKnowledgeGraph: createG } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
|
|
9274
|
+
const dbPath = path32.join(entry.path, ".code-intel", "graph.db");
|
|
9275
|
+
if (!fs26.existsSync(dbPath)) return graph;
|
|
9276
|
+
const db = new DbMgr(dbPath, true);
|
|
9277
|
+
await db.init();
|
|
9278
|
+
const g = createG();
|
|
9279
|
+
await loadG(g, db);
|
|
9280
|
+
db.close();
|
|
9281
|
+
return g;
|
|
9282
|
+
})() : graph;
|
|
9099
9283
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
9100
9284
|
const fetchLimit = Math.min(offset + effectiveLimit, 500);
|
|
9101
|
-
const
|
|
9285
|
+
const bm25 = !a.repo || a.repo === repoName ? bm25Resolver ? bm25Resolver() : null : null;
|
|
9286
|
+
const bm25Results = bm25 ? bm25.search(query, fetchLimit * 3) : void 0;
|
|
9287
|
+
const { results: allResults, searchMode } = await hybridSearch(repoGraph, query, fetchLimit, {
|
|
9288
|
+
vectorDbPath: vdbPath,
|
|
9289
|
+
bm25Results: bm25Results ?? void 0
|
|
9290
|
+
});
|
|
9102
9291
|
const total = allResults.length;
|
|
9103
9292
|
const results = allResults.slice(offset, offset + effectiveLimit);
|
|
9104
9293
|
const hasMore = offset + effectiveLimit < total;
|
|
9105
9294
|
const suggestNextTools = [];
|
|
9106
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
9295
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
9107
9296
|
if (suggestEnabled && results.length > 0) {
|
|
9108
9297
|
const topName = results[0].name;
|
|
9109
9298
|
suggestNextTools.push(
|
|
@@ -9114,15 +9303,16 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9114
9303
|
return {
|
|
9115
9304
|
content: [{
|
|
9116
9305
|
type: "text",
|
|
9117
|
-
text:
|
|
9306
|
+
text: compact({
|
|
9118
9307
|
results,
|
|
9119
9308
|
searchMode,
|
|
9309
|
+
repo: a.repo ?? repoName,
|
|
9120
9310
|
total,
|
|
9121
9311
|
offset,
|
|
9122
9312
|
limit: effectiveLimit,
|
|
9123
9313
|
hasMore,
|
|
9124
9314
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9125
|
-
}
|
|
9315
|
+
})
|
|
9126
9316
|
}]
|
|
9127
9317
|
};
|
|
9128
9318
|
}
|
|
@@ -9144,7 +9334,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9144
9334
|
file: graph.getNode(e.target)?.filePath
|
|
9145
9335
|
}));
|
|
9146
9336
|
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"]
|
|
9337
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
9148
9338
|
const suggestNextTools = [];
|
|
9149
9339
|
if (suggestEnabled) {
|
|
9150
9340
|
const topCallerName = callers[0]?.name;
|
|
@@ -9156,7 +9346,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9156
9346
|
return {
|
|
9157
9347
|
content: [{
|
|
9158
9348
|
type: "text",
|
|
9159
|
-
text:
|
|
9349
|
+
text: compact({
|
|
9160
9350
|
node: {
|
|
9161
9351
|
id: node.id,
|
|
9162
9352
|
kind: node.kind,
|
|
@@ -9179,7 +9369,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9179
9369
|
cluster,
|
|
9180
9370
|
content: node.content?.slice(0, 500),
|
|
9181
9371
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9182
|
-
}
|
|
9372
|
+
})
|
|
9183
9373
|
}]
|
|
9184
9374
|
};
|
|
9185
9375
|
}
|
|
@@ -9187,7 +9377,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9187
9377
|
case "blast_radius": {
|
|
9188
9378
|
const target = a.target;
|
|
9189
9379
|
const direction = a.direction ?? "both";
|
|
9190
|
-
const maxHops = a.max_hops ??
|
|
9380
|
+
const maxHops = a.max_hops ?? 2;
|
|
9191
9381
|
const node = findNodeByName(graph, target);
|
|
9192
9382
|
if (!node) return { content: [{ type: "text", text: `Symbol "${target}" not found.` }] };
|
|
9193
9383
|
const affected = /* @__PURE__ */ new Set();
|
|
@@ -9214,7 +9404,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9214
9404
|
return n ? { id, name: n.name, kind: n.kind, filePath: n.filePath } : { id };
|
|
9215
9405
|
});
|
|
9216
9406
|
const risk = affected.size > 10 ? "HIGH" : affected.size > 5 ? "MEDIUM" : "LOW";
|
|
9217
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
9407
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
9218
9408
|
const suggestNextTools = [];
|
|
9219
9409
|
if (suggestEnabled) {
|
|
9220
9410
|
const highestRiskSymbol = node.name;
|
|
@@ -9227,13 +9417,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9227
9417
|
return {
|
|
9228
9418
|
content: [{
|
|
9229
9419
|
type: "text",
|
|
9230
|
-
text:
|
|
9420
|
+
text: compact({
|
|
9231
9421
|
target: node.name,
|
|
9232
9422
|
affectedCount: affected.size,
|
|
9233
9423
|
riskLevel: risk,
|
|
9234
9424
|
affected: affectedDetails,
|
|
9235
9425
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9236
|
-
}
|
|
9426
|
+
})
|
|
9237
9427
|
}]
|
|
9238
9428
|
};
|
|
9239
9429
|
}
|
|
@@ -9241,7 +9431,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9241
9431
|
case "file_symbols": {
|
|
9242
9432
|
const filePath = a.file_path;
|
|
9243
9433
|
const offset = a.offset ?? 0;
|
|
9244
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9434
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9245
9435
|
const allMatches = [];
|
|
9246
9436
|
for (const node of graph.allNodes()) {
|
|
9247
9437
|
if (node.filePath && node.filePath.includes(filePath)) {
|
|
@@ -9258,7 +9448,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9258
9448
|
return {
|
|
9259
9449
|
content: [{
|
|
9260
9450
|
type: "text",
|
|
9261
|
-
text:
|
|
9451
|
+
text: compact({ symbols: matches, total, offset, limit: effectiveLimit, hasMore })
|
|
9262
9452
|
}]
|
|
9263
9453
|
};
|
|
9264
9454
|
}
|
|
@@ -9299,7 +9489,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9299
9489
|
return {
|
|
9300
9490
|
content: [{
|
|
9301
9491
|
type: "text",
|
|
9302
|
-
text:
|
|
9492
|
+
text: compact({ from: fromName, to: toName, hops: foundPath.length - 1, path: pathDetails })
|
|
9303
9493
|
}]
|
|
9304
9494
|
};
|
|
9305
9495
|
}
|
|
@@ -9307,7 +9497,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9307
9497
|
case "list_exports": {
|
|
9308
9498
|
const kindFilter = a.kind;
|
|
9309
9499
|
const offset = a.offset ?? 0;
|
|
9310
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9500
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9311
9501
|
const allExports = [];
|
|
9312
9502
|
for (const node of graph.allNodes()) {
|
|
9313
9503
|
if (!node.exported) continue;
|
|
@@ -9320,7 +9510,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9320
9510
|
return {
|
|
9321
9511
|
content: [{
|
|
9322
9512
|
type: "text",
|
|
9323
|
-
text:
|
|
9513
|
+
text: compact({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore })
|
|
9324
9514
|
}]
|
|
9325
9515
|
};
|
|
9326
9516
|
}
|
|
@@ -9332,12 +9522,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9332
9522
|
routes.push({ name: node.name, filePath: node.filePath, startLine: node.startLine });
|
|
9333
9523
|
}
|
|
9334
9524
|
}
|
|
9335
|
-
return { content: [{ type: "text", text:
|
|
9525
|
+
return { content: [{ type: "text", text: compact(routes) }] };
|
|
9336
9526
|
}
|
|
9337
9527
|
// ── clusters ───────────────────────────────────────────────────────────
|
|
9338
9528
|
case "clusters": {
|
|
9339
9529
|
const offset = a.offset ?? 0;
|
|
9340
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9530
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9341
9531
|
const allClusters = [];
|
|
9342
9532
|
for (const node of graph.allNodes()) {
|
|
9343
9533
|
if (node.kind === "cluster") {
|
|
@@ -9364,14 +9554,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9364
9554
|
return {
|
|
9365
9555
|
content: [{
|
|
9366
9556
|
type: "text",
|
|
9367
|
-
text:
|
|
9557
|
+
text: compact({ clusters, total, offset, limit: effectiveLimit, hasMore })
|
|
9368
9558
|
}]
|
|
9369
9559
|
};
|
|
9370
9560
|
}
|
|
9371
9561
|
// ── flows ──────────────────────────────────────────────────────────────
|
|
9372
9562
|
case "flows": {
|
|
9373
9563
|
const offset = a.offset ?? 0;
|
|
9374
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
9564
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
9375
9565
|
const allFlows = [];
|
|
9376
9566
|
for (const node of graph.allNodes()) {
|
|
9377
9567
|
if (node.kind === "flow") {
|
|
@@ -9391,7 +9581,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9391
9581
|
return {
|
|
9392
9582
|
content: [{
|
|
9393
9583
|
type: "text",
|
|
9394
|
-
text:
|
|
9584
|
+
text: compact({ flows, total, offset, limit: effectiveLimit, hasMore })
|
|
9395
9585
|
}]
|
|
9396
9586
|
};
|
|
9397
9587
|
}
|
|
@@ -9459,14 +9649,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9459
9649
|
return {
|
|
9460
9650
|
content: [{
|
|
9461
9651
|
type: "text",
|
|
9462
|
-
text:
|
|
9652
|
+
text: compact({
|
|
9463
9653
|
baseRef,
|
|
9464
9654
|
changedFiles: changedFiles.map((f) => f.filePath),
|
|
9465
9655
|
directlyChangedSymbols: changedSymbols,
|
|
9466
9656
|
transitivelyAffectedSymbols: affectedSymbols,
|
|
9467
9657
|
totalAffected: allAffected.size,
|
|
9468
9658
|
riskLevel: risk
|
|
9469
|
-
}
|
|
9659
|
+
})
|
|
9470
9660
|
}]
|
|
9471
9661
|
};
|
|
9472
9662
|
}
|
|
@@ -9474,14 +9664,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9474
9664
|
case "query": {
|
|
9475
9665
|
const gqlInput = a.gql;
|
|
9476
9666
|
if (!gqlInput) {
|
|
9477
|
-
return { content: [{ type: "text", text:
|
|
9667
|
+
return { content: [{ type: "text", text: compact({ error: "Missing required parameter: gql" }) }], isError: true };
|
|
9478
9668
|
}
|
|
9479
9669
|
const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
|
|
9480
9670
|
const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
|
|
9481
9671
|
const ast = parseGQL2(gqlInput);
|
|
9482
9672
|
if (isGQLParseError2(ast)) {
|
|
9483
9673
|
return {
|
|
9484
|
-
content: [{ type: "text", text:
|
|
9674
|
+
content: [{ type: "text", text: compact({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
|
|
9485
9675
|
isError: true
|
|
9486
9676
|
};
|
|
9487
9677
|
}
|
|
@@ -9492,7 +9682,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9492
9682
|
return {
|
|
9493
9683
|
content: [{
|
|
9494
9684
|
type: "text",
|
|
9495
|
-
text:
|
|
9685
|
+
text: compact({
|
|
9496
9686
|
nodes: result.nodes,
|
|
9497
9687
|
edges: result.edges,
|
|
9498
9688
|
groups: result.groups,
|
|
@@ -9500,7 +9690,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9500
9690
|
executionTimeMs: result.executionTimeMs,
|
|
9501
9691
|
truncated: result.truncated,
|
|
9502
9692
|
totalCount: result.totalCount
|
|
9503
|
-
}
|
|
9693
|
+
})
|
|
9504
9694
|
}]
|
|
9505
9695
|
};
|
|
9506
9696
|
}
|
|
@@ -9514,7 +9704,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9514
9704
|
for (const node of graph.allNodes()) {
|
|
9515
9705
|
if (node.name === nameMatch[1]) results.push(node);
|
|
9516
9706
|
}
|
|
9517
|
-
return { content: [{ type: "text", text:
|
|
9707
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
|
|
9518
9708
|
}
|
|
9519
9709
|
const kindMatch = q?.match(/:\s*(\w+)/);
|
|
9520
9710
|
if (kindMatch) {
|
|
@@ -9523,9 +9713,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9523
9713
|
if (node.kind === kindMatch[1]) results.push(node);
|
|
9524
9714
|
if (results.length >= 50) break;
|
|
9525
9715
|
}
|
|
9526
|
-
return { content: [{ type: "text", text:
|
|
9716
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
|
|
9527
9717
|
}
|
|
9528
|
-
return { content: [{ type: "text", text:
|
|
9718
|
+
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
9719
|
}
|
|
9530
9720
|
// ── group_list ─────────────────────────────────────────────────────────
|
|
9531
9721
|
case "group_list": {
|
|
@@ -9533,16 +9723,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9533
9723
|
if (groupName) {
|
|
9534
9724
|
const group = loadGroup(groupName);
|
|
9535
9725
|
if (!group) return { content: [{ type: "text", text: `Group "${groupName}" not found.` }] };
|
|
9536
|
-
return { content: [{ type: "text", text:
|
|
9726
|
+
return { content: [{ type: "text", text: compact(group) }] };
|
|
9537
9727
|
}
|
|
9538
9728
|
const groups = listGroups();
|
|
9539
9729
|
return {
|
|
9540
9730
|
content: [{
|
|
9541
9731
|
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
|
|
9732
|
+
text: compact(
|
|
9733
|
+
groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members }))
|
|
9546
9734
|
)
|
|
9547
9735
|
}]
|
|
9548
9736
|
};
|
|
@@ -9560,14 +9748,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9560
9748
|
return {
|
|
9561
9749
|
content: [{
|
|
9562
9750
|
type: "text",
|
|
9563
|
-
text:
|
|
9751
|
+
text: compact({
|
|
9564
9752
|
groupName: result.groupName,
|
|
9565
9753
|
syncedAt: result.syncedAt,
|
|
9566
9754
|
memberCount: result.memberCount,
|
|
9567
9755
|
contractCount: result.contracts.length,
|
|
9568
9756
|
linkCount: result.links.length,
|
|
9569
9757
|
topLinks: result.links.slice(0, 20)
|
|
9570
|
-
}
|
|
9758
|
+
})
|
|
9571
9759
|
}]
|
|
9572
9760
|
};
|
|
9573
9761
|
}
|
|
@@ -9587,7 +9775,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9587
9775
|
return {
|
|
9588
9776
|
content: [{
|
|
9589
9777
|
type: "text",
|
|
9590
|
-
text:
|
|
9778
|
+
text: compact({ syncedAt: result.syncedAt, contracts, links })
|
|
9591
9779
|
}]
|
|
9592
9780
|
};
|
|
9593
9781
|
}
|
|
@@ -9602,7 +9790,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9602
9790
|
return {
|
|
9603
9791
|
content: [{
|
|
9604
9792
|
type: "text",
|
|
9605
|
-
text:
|
|
9793
|
+
text: compact({ query, merged, perRepo })
|
|
9606
9794
|
}]
|
|
9607
9795
|
};
|
|
9608
9796
|
}
|
|
@@ -9634,12 +9822,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9634
9822
|
return {
|
|
9635
9823
|
content: [{
|
|
9636
9824
|
type: "text",
|
|
9637
|
-
text:
|
|
9825
|
+
text: compact({
|
|
9638
9826
|
group: groupName,
|
|
9639
9827
|
lastSync: group.lastSync ?? null,
|
|
9640
9828
|
syncAgeMinutes: syncAge,
|
|
9641
9829
|
members: memberStatus
|
|
9642
|
-
}
|
|
9830
|
+
})
|
|
9643
9831
|
}]
|
|
9644
9832
|
};
|
|
9645
9833
|
}
|
|
@@ -9648,11 +9836,11 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9648
9836
|
const fromName = a.from;
|
|
9649
9837
|
const toName = a.to;
|
|
9650
9838
|
const result = explainRelationship(graph, fromName, toName);
|
|
9651
|
-
return { content: [{ type: "text", text:
|
|
9839
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9652
9840
|
}
|
|
9653
9841
|
// ── pr_impact ──────────────────────────────────────────────────────────
|
|
9654
9842
|
case "pr_impact": {
|
|
9655
|
-
const maxHops = a.maxHops ??
|
|
9843
|
+
const maxHops = a.maxHops ?? 2;
|
|
9656
9844
|
let changedFiles = a.changedFiles ?? [];
|
|
9657
9845
|
if (a.diff && typeof a.diff === "string") {
|
|
9658
9846
|
const diffFiles = parseDiffFiles(a.diff);
|
|
@@ -9662,37 +9850,37 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9662
9850
|
return {
|
|
9663
9851
|
content: [{
|
|
9664
9852
|
type: "text",
|
|
9665
|
-
text:
|
|
9853
|
+
text: compact({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
|
|
9666
9854
|
}]
|
|
9667
9855
|
};
|
|
9668
9856
|
}
|
|
9669
9857
|
const result = computePRImpact(graph, changedFiles, maxHops);
|
|
9670
|
-
return { content: [{ type: "text", text:
|
|
9858
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9671
9859
|
}
|
|
9672
9860
|
// ── similar_symbols ────────────────────────────────────────────────────
|
|
9673
9861
|
case "similar_symbols": {
|
|
9674
9862
|
const symbolName = a.symbol;
|
|
9675
9863
|
const limit = a.limit ?? 10;
|
|
9676
9864
|
const result = findSimilarSymbols(graph, symbolName, limit);
|
|
9677
|
-
return { content: [{ type: "text", text:
|
|
9865
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9678
9866
|
}
|
|
9679
9867
|
// ── health_report ──────────────────────────────────────────────────────
|
|
9680
9868
|
case "health_report": {
|
|
9681
9869
|
const scope = a.scope ?? ".";
|
|
9682
9870
|
const result = computeHealthReport(graph, scope);
|
|
9683
|
-
return { content: [{ type: "text", text:
|
|
9871
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9684
9872
|
}
|
|
9685
9873
|
// ── suggest_tests ──────────────────────────────────────────────────────
|
|
9686
9874
|
case "suggest_tests": {
|
|
9687
9875
|
const sym = a.symbol;
|
|
9688
9876
|
const result = suggestTests(graph, sym);
|
|
9689
|
-
return { content: [{ type: "text", text:
|
|
9877
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9690
9878
|
}
|
|
9691
9879
|
// ── cluster_summary ────────────────────────────────────────────────────
|
|
9692
9880
|
case "cluster_summary": {
|
|
9693
9881
|
const cluster = a.cluster;
|
|
9694
9882
|
const result = summarizeCluster(graph, cluster);
|
|
9695
|
-
return { content: [{ type: "text", text:
|
|
9883
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
9696
9884
|
}
|
|
9697
9885
|
case "deprecated_usage": {
|
|
9698
9886
|
const scope = a.scope;
|
|
@@ -9700,7 +9888,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9700
9888
|
const detector = new DeprecatedDetector2();
|
|
9701
9889
|
detector.tagDeprecated(graph);
|
|
9702
9890
|
const findings = detector.detect(graph, scope);
|
|
9703
|
-
return { content: [{ type: "text", text:
|
|
9891
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
9704
9892
|
}
|
|
9705
9893
|
// ── complexity_hotspots ────────────────────────────────────────────────
|
|
9706
9894
|
case "complexity_hotspots": {
|
|
@@ -9708,7 +9896,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9708
9896
|
const scope = a.scope;
|
|
9709
9897
|
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
9710
9898
|
const hotspots = computeComplexity2(graph, scope).slice(0, limit);
|
|
9711
|
-
return { content: [{ type: "text", text:
|
|
9899
|
+
return { content: [{ type: "text", text: compact({ hotspots, total: hotspots.length }) }] };
|
|
9712
9900
|
}
|
|
9713
9901
|
// ── coverage_gaps ──────────────────────────────────────────────────────
|
|
9714
9902
|
case "coverage_gaps": {
|
|
@@ -9720,12 +9908,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9720
9908
|
return {
|
|
9721
9909
|
content: [{
|
|
9722
9910
|
type: "text",
|
|
9723
|
-
text:
|
|
9911
|
+
text: compact({
|
|
9724
9912
|
untestedByRisk,
|
|
9725
9913
|
coveragePct: summary.coveragePct,
|
|
9726
9914
|
totalExported: summary.totalExported,
|
|
9727
9915
|
testedExported: summary.testedExported
|
|
9728
|
-
}
|
|
9916
|
+
})
|
|
9729
9917
|
}]
|
|
9730
9918
|
};
|
|
9731
9919
|
}
|
|
@@ -9736,7 +9924,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9736
9924
|
const scope = a.scope;
|
|
9737
9925
|
const includeTestFiles = a.includeTestFiles ?? false;
|
|
9738
9926
|
const findings = scanner.scan(graph, { scope, includeTestFiles });
|
|
9739
|
-
return { content: [{ type: "text", text:
|
|
9927
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
9740
9928
|
}
|
|
9741
9929
|
// ── vulnerability_scan ─────────────────────────────────────────────────
|
|
9742
9930
|
case "vulnerability_scan": {
|
|
@@ -9749,7 +9937,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
9749
9937
|
const minRank = sevRank[minSev] ?? 1;
|
|
9750
9938
|
let findings = detector.detect(graph, { scope, types });
|
|
9751
9939
|
findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
|
|
9752
|
-
return { content: [{ type: "text", text:
|
|
9940
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
9753
9941
|
}
|
|
9754
9942
|
default:
|
|
9755
9943
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
@@ -9770,21 +9958,21 @@ function registerResources(server, graph, repoName) {
|
|
|
9770
9958
|
for (const node of graph.allNodes()) {
|
|
9771
9959
|
kindCounts[node.kind] = (kindCounts[node.kind] ?? 0) + 1;
|
|
9772
9960
|
}
|
|
9773
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
9961
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
|
|
9774
9962
|
}
|
|
9775
9963
|
if (uri.endsWith("/clusters")) {
|
|
9776
9964
|
const clusters = [];
|
|
9777
9965
|
for (const node of graph.allNodes()) {
|
|
9778
9966
|
if (node.kind === "cluster") clusters.push({ id: node.id, name: node.name, memberCount: node.metadata?.memberCount });
|
|
9779
9967
|
}
|
|
9780
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
9968
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact(clusters) }] };
|
|
9781
9969
|
}
|
|
9782
9970
|
if (uri.endsWith("/flows")) {
|
|
9783
9971
|
const flows = [];
|
|
9784
9972
|
for (const node of graph.allNodes()) {
|
|
9785
9973
|
if (node.kind === "flow") flows.push({ id: node.id, name: node.name, steps: node.metadata?.steps, entryPoint: node.metadata?.entryPoint });
|
|
9786
9974
|
}
|
|
9787
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
9975
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact(flows) }] };
|
|
9788
9976
|
}
|
|
9789
9977
|
throw new Error(`Unknown resource: ${uri}`);
|
|
9790
9978
|
});
|
|
@@ -9833,7 +10021,8 @@ function parseDiff(diffText) {
|
|
|
9833
10021
|
}
|
|
9834
10022
|
return result;
|
|
9835
10023
|
}
|
|
9836
|
-
|
|
10024
|
+
init_knowledge_graph();
|
|
10025
|
+
init_graph_from_db();
|
|
9837
10026
|
init_logger();
|
|
9838
10027
|
init_codes();
|
|
9839
10028
|
init_middleware();
|
|
@@ -9844,7 +10033,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
9844
10033
|
var JobsDB = class {
|
|
9845
10034
|
db;
|
|
9846
10035
|
constructor(dbPath) {
|
|
9847
|
-
|
|
10036
|
+
fs26.mkdirSync(path32.dirname(dbPath), { recursive: true });
|
|
9848
10037
|
this.db = new Database2(dbPath);
|
|
9849
10038
|
this.db.pragma("journal_mode = WAL");
|
|
9850
10039
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -10109,7 +10298,7 @@ var BackupService = class {
|
|
|
10109
10298
|
constructor(backupDir) {
|
|
10110
10299
|
this.backupDir = backupDir ?? getBackupDir();
|
|
10111
10300
|
this.key = getBackupKey();
|
|
10112
|
-
|
|
10301
|
+
fs26.mkdirSync(this.backupDir, { recursive: true });
|
|
10113
10302
|
}
|
|
10114
10303
|
/**
|
|
10115
10304
|
* Create a backup for a repository.
|
|
@@ -10123,16 +10312,16 @@ var BackupService = class {
|
|
|
10123
10312
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
10124
10313
|
for (const f of candidates) {
|
|
10125
10314
|
const fp = path32.join(codeIntelDir, f);
|
|
10126
|
-
if (
|
|
10315
|
+
if (fs26.existsSync(fp)) {
|
|
10127
10316
|
filesToBackup.push({ name: f, localPath: fp });
|
|
10128
10317
|
}
|
|
10129
10318
|
}
|
|
10130
10319
|
const registryPath = path32.join(os13.homedir(), ".code-intel", "registry.json");
|
|
10131
|
-
if (
|
|
10320
|
+
if (fs26.existsSync(registryPath)) {
|
|
10132
10321
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
10133
10322
|
}
|
|
10134
10323
|
const usersDbPath = path32.join(os13.homedir(), ".code-intel", "users.db");
|
|
10135
|
-
if (
|
|
10324
|
+
if (fs26.existsSync(usersDbPath)) {
|
|
10136
10325
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
10137
10326
|
}
|
|
10138
10327
|
if (filesToBackup.length === 0) {
|
|
@@ -10143,7 +10332,7 @@ var BackupService = class {
|
|
|
10143
10332
|
createdAt,
|
|
10144
10333
|
version: BACKUP_VERSION,
|
|
10145
10334
|
files: filesToBackup.map((f) => {
|
|
10146
|
-
const data =
|
|
10335
|
+
const data = fs26.readFileSync(f.localPath);
|
|
10147
10336
|
return {
|
|
10148
10337
|
name: f.name,
|
|
10149
10338
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -10157,7 +10346,7 @@ var BackupService = class {
|
|
|
10157
10346
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
10158
10347
|
parts.push(manifestLenBuf, manifestBuf);
|
|
10159
10348
|
for (const f of filesToBackup) {
|
|
10160
|
-
const data =
|
|
10349
|
+
const data = fs26.readFileSync(f.localPath);
|
|
10161
10350
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
10162
10351
|
const nameLenBuf = Buffer.alloc(2);
|
|
10163
10352
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -10169,7 +10358,7 @@ var BackupService = class {
|
|
|
10169
10358
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
10170
10359
|
const backupFileName = `backup-${id}.cib`;
|
|
10171
10360
|
const backupPath = path32.join(this.backupDir, backupFileName);
|
|
10172
|
-
|
|
10361
|
+
fs26.writeFileSync(backupPath, encrypted);
|
|
10173
10362
|
const entry = {
|
|
10174
10363
|
id,
|
|
10175
10364
|
createdAt,
|
|
@@ -10198,7 +10387,7 @@ var BackupService = class {
|
|
|
10198
10387
|
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
10388
|
const fileName = path32.basename(entry.path);
|
|
10200
10389
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
10201
|
-
const body =
|
|
10390
|
+
const body = fs26.readFileSync(entry.path);
|
|
10202
10391
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
10203
10392
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
10204
10393
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -10215,8 +10404,8 @@ var BackupService = class {
|
|
|
10215
10404
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
10216
10405
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
10217
10406
|
}
|
|
10218
|
-
|
|
10219
|
-
|
|
10407
|
+
fs26.mkdirSync(path32.dirname(destPath), { recursive: true });
|
|
10408
|
+
fs26.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
10220
10409
|
}
|
|
10221
10410
|
/**
|
|
10222
10411
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -10262,10 +10451,10 @@ var BackupService = class {
|
|
|
10262
10451
|
if (!entry) {
|
|
10263
10452
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
10264
10453
|
}
|
|
10265
|
-
if (!
|
|
10454
|
+
if (!fs26.existsSync(entry.path)) {
|
|
10266
10455
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
10267
10456
|
}
|
|
10268
|
-
const encrypted =
|
|
10457
|
+
const encrypted = fs26.readFileSync(entry.path);
|
|
10269
10458
|
let plaintext;
|
|
10270
10459
|
try {
|
|
10271
10460
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -10280,7 +10469,7 @@ var BackupService = class {
|
|
|
10280
10469
|
const manifest = JSON.parse(manifestStr);
|
|
10281
10470
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
10282
10471
|
const codeIntelDir = path32.join(restoreBase, ".code-intel");
|
|
10283
|
-
|
|
10472
|
+
fs26.mkdirSync(codeIntelDir, { recursive: true });
|
|
10284
10473
|
for (const fileEntry of manifest.files) {
|
|
10285
10474
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
10286
10475
|
offset += 2;
|
|
@@ -10301,14 +10490,14 @@ var BackupService = class {
|
|
|
10301
10490
|
} else {
|
|
10302
10491
|
destPath = path32.join(codeIntelDir, name);
|
|
10303
10492
|
}
|
|
10304
|
-
|
|
10493
|
+
fs26.writeFileSync(destPath, data);
|
|
10305
10494
|
}
|
|
10306
10495
|
}
|
|
10307
10496
|
/**
|
|
10308
10497
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
10309
10498
|
*/
|
|
10310
10499
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
10311
|
-
const entries = this._loadIndex().filter((e) =>
|
|
10500
|
+
const entries = this._loadIndex().filter((e) => fs26.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
10312
10501
|
const keep = /* @__PURE__ */ new Set();
|
|
10313
10502
|
const now = /* @__PURE__ */ new Date();
|
|
10314
10503
|
const dailyCutoff = new Date(now);
|
|
@@ -10338,7 +10527,7 @@ var BackupService = class {
|
|
|
10338
10527
|
for (const e of entries) {
|
|
10339
10528
|
if (!keep.has(e.id)) {
|
|
10340
10529
|
try {
|
|
10341
|
-
|
|
10530
|
+
fs26.unlinkSync(e.path);
|
|
10342
10531
|
deleted++;
|
|
10343
10532
|
} catch {
|
|
10344
10533
|
}
|
|
@@ -10354,13 +10543,13 @@ var BackupService = class {
|
|
|
10354
10543
|
}
|
|
10355
10544
|
_loadIndex() {
|
|
10356
10545
|
try {
|
|
10357
|
-
return JSON.parse(
|
|
10546
|
+
return JSON.parse(fs26.readFileSync(this._indexPath(), "utf-8"));
|
|
10358
10547
|
} catch {
|
|
10359
10548
|
return [];
|
|
10360
10549
|
}
|
|
10361
10550
|
}
|
|
10362
10551
|
_saveIndex(entries) {
|
|
10363
|
-
|
|
10552
|
+
fs26.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
10364
10553
|
}
|
|
10365
10554
|
_appendIndex(entry) {
|
|
10366
10555
|
const entries = this._loadIndex();
|
|
@@ -11156,7 +11345,7 @@ var openApiSpec = {
|
|
|
11156
11345
|
var __dirname$1 = path32.dirname(fileURLToPath(import.meta.url));
|
|
11157
11346
|
var WEB_DIST = (() => {
|
|
11158
11347
|
const bundled = path32.resolve(__dirname$1, "..", "web");
|
|
11159
|
-
if (
|
|
11348
|
+
if (fs26.existsSync(bundled)) return bundled;
|
|
11160
11349
|
return path32.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
11161
11350
|
})();
|
|
11162
11351
|
function getAllowedOrigins() {
|
|
@@ -11241,8 +11430,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11241
11430
|
const metaFilePath = path32.join(workspaceRoot, ".code-intel", "meta.json");
|
|
11242
11431
|
let metaOk = false;
|
|
11243
11432
|
try {
|
|
11244
|
-
if (
|
|
11245
|
-
const raw =
|
|
11433
|
+
if (fs26.existsSync(metaFilePath)) {
|
|
11434
|
+
const raw = fs26.readFileSync(metaFilePath, "utf-8");
|
|
11246
11435
|
const meta = JSON.parse(raw);
|
|
11247
11436
|
if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
|
|
11248
11437
|
}
|
|
@@ -11433,12 +11622,12 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11433
11622
|
return;
|
|
11434
11623
|
}
|
|
11435
11624
|
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));
|
|
11625
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11626
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11438
11627
|
res.status(201).json({ user: { id: user.id, username: user.username, role: user.role } });
|
|
11439
11628
|
});
|
|
11440
11629
|
app.post("/auth/login", async (req, res) => {
|
|
11441
|
-
const { username, password } = req.body;
|
|
11630
|
+
const { username, password, rememberMe } = req.body;
|
|
11442
11631
|
if (!username || !password) {
|
|
11443
11632
|
res.status(400).json({
|
|
11444
11633
|
error: {
|
|
@@ -11482,10 +11671,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11482
11671
|
});
|
|
11483
11672
|
return;
|
|
11484
11673
|
}
|
|
11485
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11674
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role }, rememberMe === true);
|
|
11486
11675
|
db.logAccess(user.id, "/auth/login", "login", "allow", req.ip ?? "unknown");
|
|
11487
11676
|
authAttemptsTotal.inc({ method: "local", outcome: "success" });
|
|
11488
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11677
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11489
11678
|
res.json({ user: { id: user.id, username: user.username, role: user.role } });
|
|
11490
11679
|
});
|
|
11491
11680
|
app.post("/auth/logout", (req, res) => {
|
|
@@ -11607,9 +11796,9 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11607
11796
|
authAttemptsTotal.inc({ method: "oidc", outcome: "success" });
|
|
11608
11797
|
logger_default.info(`[oidc] Auto-provisioned new user: ${finalUsername} (${cfg.defaultRole})`);
|
|
11609
11798
|
}
|
|
11610
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11799
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11611
11800
|
db.logAccess(user.id, "/auth/callback", "oidc-login", "allow", req.ip ?? "unknown");
|
|
11612
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11801
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11613
11802
|
res.redirect(302, "/");
|
|
11614
11803
|
} catch (err) {
|
|
11615
11804
|
logger_default.warn("[oidc] Callback failed:", err instanceof Error ? err.message : err);
|
|
@@ -11727,7 +11916,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11727
11916
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
11728
11917
|
if (!entry) return null;
|
|
11729
11918
|
const dbPath = path32.join(entry.path, ".code-intel", "graph.db");
|
|
11730
|
-
if (!
|
|
11919
|
+
if (!fs26.existsSync(dbPath)) return null;
|
|
11731
11920
|
const repoGraph = createKnowledgeGraph();
|
|
11732
11921
|
const db = new DbManager(dbPath, true);
|
|
11733
11922
|
try {
|
|
@@ -11749,7 +11938,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11749
11938
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
11750
11939
|
if (!regEntry) continue;
|
|
11751
11940
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
11752
|
-
if (!
|
|
11941
|
+
if (!fs26.existsSync(dbPath)) continue;
|
|
11753
11942
|
const db = new DbManager(dbPath, true);
|
|
11754
11943
|
try {
|
|
11755
11944
|
await db.init();
|
|
@@ -11831,7 +12020,21 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11831
12020
|
});
|
|
11832
12021
|
});
|
|
11833
12022
|
app.post("/api/v1/search", requireToolScope("search"), async (req, res) => {
|
|
11834
|
-
const { query, limit, repo } = req.body;
|
|
12023
|
+
const { query, limit, repo, group } = req.body;
|
|
12024
|
+
if (group) {
|
|
12025
|
+
const grp = loadGroup(group);
|
|
12026
|
+
if (!grp) {
|
|
12027
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: `Group '${group}' not found`, hint: "Use /api/v1/groups to list available groups" } });
|
|
12028
|
+
return;
|
|
12029
|
+
}
|
|
12030
|
+
try {
|
|
12031
|
+
const { perRepo, merged } = await queryGroup(grp, query ?? "", limit ?? 20);
|
|
12032
|
+
res.json({ results: merged, perRepo, searchMode: "bm25", group });
|
|
12033
|
+
} catch (err) {
|
|
12034
|
+
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
|
|
12035
|
+
}
|
|
12036
|
+
return;
|
|
12037
|
+
}
|
|
11835
12038
|
const g = await getGraphForRepo(repo);
|
|
11836
12039
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
11837
12040
|
const bm25 = !repo || repo === repoName ? ensureBm25Index() : null;
|
|
@@ -11840,7 +12043,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11840
12043
|
vectorDbPath: vdbPath,
|
|
11841
12044
|
bm25Results: bm25Results ?? void 0
|
|
11842
12045
|
});
|
|
11843
|
-
res.json({ results, searchMode });
|
|
12046
|
+
res.json({ results, searchMode, repo: repo ?? repoName });
|
|
11844
12047
|
});
|
|
11845
12048
|
app.post("/api/v1/vector-search", async (req, res) => {
|
|
11846
12049
|
const { query, limit = 10 } = req.body;
|
|
@@ -11882,7 +12085,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11882
12085
|
return;
|
|
11883
12086
|
}
|
|
11884
12087
|
try {
|
|
11885
|
-
const content =
|
|
12088
|
+
const content = fs26.readFileSync(file_path, "utf-8");
|
|
11886
12089
|
res.json({ content });
|
|
11887
12090
|
} catch {
|
|
11888
12091
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -12114,6 +12317,97 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12114
12317
|
}
|
|
12115
12318
|
res.json(result);
|
|
12116
12319
|
});
|
|
12320
|
+
app.post("/api/v1/groups", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12321
|
+
const { name } = req.body;
|
|
12322
|
+
if (!name || !name.trim()) {
|
|
12323
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "Group name is required" } });
|
|
12324
|
+
return;
|
|
12325
|
+
}
|
|
12326
|
+
const trimmed = name.trim();
|
|
12327
|
+
if (groupExists(trimmed)) {
|
|
12328
|
+
res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${trimmed}" already exists` } });
|
|
12329
|
+
return;
|
|
12330
|
+
}
|
|
12331
|
+
const group = { name: trimmed, createdAt: (/* @__PURE__ */ new Date()).toISOString(), members: [] };
|
|
12332
|
+
saveGroup(group);
|
|
12333
|
+
res.status(201).json(group);
|
|
12334
|
+
});
|
|
12335
|
+
app.delete("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12336
|
+
const groupName = req.params["name"];
|
|
12337
|
+
if (!groupExists(groupName)) {
|
|
12338
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12339
|
+
return;
|
|
12340
|
+
}
|
|
12341
|
+
deleteGroup(groupName);
|
|
12342
|
+
res.status(204).end();
|
|
12343
|
+
});
|
|
12344
|
+
app.patch("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12345
|
+
const groupName = req.params["name"];
|
|
12346
|
+
const group = loadGroup(groupName);
|
|
12347
|
+
if (!group) {
|
|
12348
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12349
|
+
return;
|
|
12350
|
+
}
|
|
12351
|
+
const { name } = req.body;
|
|
12352
|
+
if (!name || !name.trim()) {
|
|
12353
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "New name is required" } });
|
|
12354
|
+
return;
|
|
12355
|
+
}
|
|
12356
|
+
const newName = name.trim();
|
|
12357
|
+
if (newName !== group.name && groupExists(newName)) {
|
|
12358
|
+
res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${newName}" already exists` } });
|
|
12359
|
+
return;
|
|
12360
|
+
}
|
|
12361
|
+
if (newName !== group.name) {
|
|
12362
|
+
deleteGroup(group.name);
|
|
12363
|
+
group.name = newName;
|
|
12364
|
+
}
|
|
12365
|
+
saveGroup(group);
|
|
12366
|
+
res.json(group);
|
|
12367
|
+
});
|
|
12368
|
+
app.post("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12369
|
+
const groupName = req.params["name"];
|
|
12370
|
+
const group = loadGroup(groupName);
|
|
12371
|
+
if (!group) {
|
|
12372
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12373
|
+
return;
|
|
12374
|
+
}
|
|
12375
|
+
const { groupPath, registryName } = req.body;
|
|
12376
|
+
if (!groupPath || !registryName) {
|
|
12377
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath and registryName are required" } });
|
|
12378
|
+
return;
|
|
12379
|
+
}
|
|
12380
|
+
const registry = loadRegistry();
|
|
12381
|
+
if (!registry.find((r) => r.name === registryName)) {
|
|
12382
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: `Repo "${registryName}" not found in registry. Run code-intel analyze first.` } });
|
|
12383
|
+
return;
|
|
12384
|
+
}
|
|
12385
|
+
try {
|
|
12386
|
+
const updated = addMember(groupName, { groupPath, registryName });
|
|
12387
|
+
res.json(updated);
|
|
12388
|
+
} catch (err) {
|
|
12389
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
|
|
12390
|
+
}
|
|
12391
|
+
});
|
|
12392
|
+
app.delete("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12393
|
+
const groupName = req.params["name"];
|
|
12394
|
+
const group = loadGroup(groupName);
|
|
12395
|
+
if (!group) {
|
|
12396
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12397
|
+
return;
|
|
12398
|
+
}
|
|
12399
|
+
const { groupPath } = req.body;
|
|
12400
|
+
if (!groupPath) {
|
|
12401
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath is required" } });
|
|
12402
|
+
return;
|
|
12403
|
+
}
|
|
12404
|
+
try {
|
|
12405
|
+
const updated = removeMember(groupName, groupPath);
|
|
12406
|
+
res.json(updated);
|
|
12407
|
+
} catch (err) {
|
|
12408
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
|
|
12409
|
+
}
|
|
12410
|
+
});
|
|
12117
12411
|
app.post("/api/v1/groups/:name/sync", async (req, res) => {
|
|
12118
12412
|
const group = loadGroup(req.params.name);
|
|
12119
12413
|
if (!group) {
|
|
@@ -12124,8 +12418,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12124
12418
|
const result = await syncGroup(group);
|
|
12125
12419
|
saveSyncResult(result);
|
|
12126
12420
|
group.lastSync = result.syncedAt;
|
|
12127
|
-
|
|
12128
|
-
saveGroup2(group);
|
|
12421
|
+
saveGroup(group);
|
|
12129
12422
|
res.json(result);
|
|
12130
12423
|
} catch (err) {
|
|
12131
12424
|
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
|
|
@@ -12161,7 +12454,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12161
12454
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
12162
12455
|
if (!regEntry) continue;
|
|
12163
12456
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
12164
|
-
if (!
|
|
12457
|
+
if (!fs26.existsSync(dbPath)) continue;
|
|
12165
12458
|
const db = new DbManager(dbPath, true);
|
|
12166
12459
|
try {
|
|
12167
12460
|
await db.init();
|
|
@@ -12188,7 +12481,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12188
12481
|
let edgeCount = 0;
|
|
12189
12482
|
if (regEntry) {
|
|
12190
12483
|
const dbPath = path32.join(regEntry.path, ".code-intel", "graph.db");
|
|
12191
|
-
if (
|
|
12484
|
+
if (fs26.existsSync(dbPath)) {
|
|
12192
12485
|
try {
|
|
12193
12486
|
const db = new DbManager(dbPath, true);
|
|
12194
12487
|
await db.init();
|
|
@@ -12251,7 +12544,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12251
12544
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
12252
12545
|
if (!regEntry) continue;
|
|
12253
12546
|
const candidate = path32.resolve(path32.join(regEntry.path, normalizedFile));
|
|
12254
|
-
if (
|
|
12547
|
+
if (fs26.existsSync(candidate)) {
|
|
12255
12548
|
baseDir = regEntry.path;
|
|
12256
12549
|
break;
|
|
12257
12550
|
}
|
|
@@ -12303,7 +12596,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12303
12596
|
}
|
|
12304
12597
|
let fileContent;
|
|
12305
12598
|
try {
|
|
12306
|
-
fileContent =
|
|
12599
|
+
fileContent = fs26.readFileSync(resolvedFile, "utf-8");
|
|
12307
12600
|
} catch {
|
|
12308
12601
|
res.status(404).json({
|
|
12309
12602
|
error: {
|
|
@@ -12469,7 +12762,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12469
12762
|
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
12763
|
}
|
|
12471
12764
|
});
|
|
12472
|
-
if (
|
|
12765
|
+
if (fs26.existsSync(WEB_DIST)) {
|
|
12473
12766
|
app.use(express.static(WEB_DIST));
|
|
12474
12767
|
app.get("/{*path}", (_req, res) => {
|
|
12475
12768
|
res.sendFile(path32.join(WEB_DIST, "index.html"));
|
|
@@ -12625,7 +12918,7 @@ async function startHttpServer(graph, repoName, port = 4747, workspaceRoot, watc
|
|
|
12625
12918
|
}
|
|
12626
12919
|
|
|
12627
12920
|
// src/multi-repo/index.ts
|
|
12628
|
-
|
|
12921
|
+
init_graph_from_db();
|
|
12629
12922
|
|
|
12630
12923
|
// src/multi-repo/cross-repo-search.ts
|
|
12631
12924
|
function mergeSearchResults(...perRepoResults) {
|