@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/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 fs25, { existsSync } from 'fs';
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 (!fs25.existsSync(_Logger.LOG_DIR)) {
348
- fs25.mkdirSync(_Logger.LOG_DIR, { recursive: true });
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 fs25.promises.readFile(filePath, "utf-8");
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/multi-repo/group-registry.ts
2579
- var group_registry_exports = {};
2580
- __export(group_registry_exports, {
2581
- addMember: () => addMember,
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
- function groupFile(name) {
2592
- return path32.join(GROUPS_DIR, `${name}.json`);
2593
- }
2594
- function loadGroup(name) {
2595
- try {
2596
- return JSON.parse(fs25.readFileSync(groupFile(name), "utf-8"));
2597
- } catch {
2598
- return null;
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
- function saveGroup(group) {
2602
- fs25.mkdirSync(GROUPS_DIR, { recursive: true });
2603
- fs25.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
2604
- }
2605
- function listGroups() {
2606
- const groups = [];
2607
- try {
2608
- for (const file of fs25.readdirSync(GROUPS_DIR)) {
2609
- if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
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
- const g = JSON.parse(
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
- } catch {
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 removeMember(groupName, groupPath) {
2648
- const group = loadGroup(groupName);
2649
- if (!group) throw new Error(`Group "${groupName}" not found.`);
2650
- const before = group.members.length;
2651
- group.members = group.members.filter((m) => m.groupPath !== groupPath);
2652
- if (group.members.length === before) {
2653
- throw new Error(`No member at path "${groupPath}" in group "${groupName}".`);
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
- return JSON.parse(
2668
- fs25.readFileSync(path32.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
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 GROUPS_DIR;
2675
- var init_group_registry = __esm({
2676
- "src/multi-repo/group-registry.ts"() {
2677
- GROUPS_DIR = path32.join(os13.homedir(), ".code-intel", "groups");
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 = fs25.readFileSync(path32.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
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
- fs25.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
4565
+ fs26.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
4292
4566
  if (process.platform !== "win32") {
4293
4567
  try {
4294
- fs25.chmodSync(dir, SECURE_DIR_MODE);
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
- fs25.chmodSync(file, SECURE_FILE_MODE);
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 (!fs25.existsSync(dir)) return;
4309
- for (const name of fs25.readdirSync(dir)) {
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
- fs25.chmodSync(path32.join(dir, name), SECURE_FILE_MODE);
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 (!fs25.existsSync(secretsPath)) return {};
4651
- const blob = fs25.readFileSync(secretsPath);
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 expiresAt = Date.now() + getSessionTtlMs();
4675
- sessionStore.set(sessionId, { userId: user.id, username: user.username, role: user.role, expiresAt });
4676
- return sessionId;
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
- return [`CREATE REL TABLE GROUP IF NOT EXISTS code_edges (
5207
- ${fromToPairs.join(",\n ")},
5208
- kind STRING,
5209
- weight DOUBLE,
5210
- label STRING
5211
- )`];
5212
- }
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 = fs25.readFileSync(path32.join(workspaceRoot, ".codeintelignore"), "utf-8");
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 = fs25.readdirSync(dir, { withFileTypes: true });
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 = fs25.statSync(fullPath);
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
- fs25.mkdirSync(path32.dirname(logPath), { recursive: true });
6456
- fs25.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
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 = fs25.readFileSync(this.getLogPath(), "utf-8");
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 && fs25.existsSync(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
- fs25.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
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 (fs25.existsSync(f)) fs25.unlinkSync(f);
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 (!fs25.existsSync(this.dbPath)) return;
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. LIMIT pushdown: scores only the posting lists for query terms,
7050
- * then partial-sorts to return only the top `limit` results.
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
- const entries = [...scores.entries()];
7071
- entries.sort((a, b) => b[1] - a[1]);
7072
- const topK = entries.slice(0, limit);
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 (!fs25.existsSync(this.dbPath)) return;
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
- var DbManager = class {
7169
- db = null;
7170
- conn = null;
7171
- dbPath;
7172
- readOnly;
7173
- constructor(dbPath, readOnly = false) {
7174
- this.dbPath = dbPath;
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
- fs25.mkdirSync(outputDir, { recursive: true });
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
- fs25.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
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
- fs25.mkdirSync(outputDir, { recursive: true });
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
- fs25.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
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 = fs25.mkdtempSync(path32.join(os13.tmpdir(), "code-intel-csv-"));
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 (!fs25.existsSync(csvPath)) continue;
7324
- const stat = fs25.statSync(csvPath);
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 (!fs25.existsSync(group.filePath)) continue;
7338
- const stat = fs25.statSync(group.filePath);
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
- fs25.rmSync(tmpDir, { recursive: true, force: true });
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 = fs25.readFileSync(REPOS_FILE, "utf-8");
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
- fs25.mkdirSync(GLOBAL_DIR, { recursive: true });
7416
- fs25.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
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
- fs25.mkdirSync(metaDir, { recursive: true });
7435
- fs25.writeFileSync(path32.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
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 = fs25.readFileSync(path32.join(repoDir, ".code-intel", "meta.json"), "utf-8");
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
- // src/mcp-server/server.ts
7453
- init_group_registry();
7454
-
7455
- // src/multi-repo/graph-from-db.ts
7456
- var TABLE_TO_KIND2 = Object.fromEntries(
7457
- Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
7458
- );
7459
- function parseRow(row, kind) {
7460
- return {
7461
- id: String(row["id"] ?? ""),
7462
- kind,
7463
- name: String(row["name"] ?? ""),
7464
- filePath: String(row["file_path"] ?? ""),
7465
- startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
7466
- endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
7467
- exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
7468
- content: row["content"] ? String(row["content"]) : void 0,
7469
- metadata: row["metadata"] ? (() => {
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
- return JSON.parse(String(row["metadata"]));
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
- const edgeRows = await db.query(
7504
- `MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
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
- // src/multi-repo/group-sync.ts
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 = fs25.readdirSync(dir, { withFileTypes: true });
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 = fs25.readFileSync(filePath, "utf-8");
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 = fs25.readFileSync(filePath, "utf-8");
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 = fs25.readFileSync(filePath, "utf-8");
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 (!fs25.existsSync(dbPath)) {
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 (!fs25.existsSync(dbPath)) continue;
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: 50, max: 500)" },
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: 5)" },
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: 50, max: 500)" },
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: 50, max: 500)" },
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: 50, max: 500)" },
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: 50, max: 500)" },
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: 5)"
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: JSON.stringify({ error: "Unauthorized: invalid or missing CODE_INTEL_TOKEN" }) }],
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: JSON.stringify({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
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: JSON.stringify(
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: JSON.stringify({
9229
+ text: compact({
9085
9230
  repo: repoName,
9086
9231
  stats: graph.size,
9087
9232
  nodeCounts: kindCounts,
9088
9233
  edgeCounts,
9089
9234
  health
9090
- }, null, 2)
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 ?? 50, 500);
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 { results: allResults, searchMode } = await hybridSearch(graph, query, fetchLimit, { vectorDbPath: vdbPath });
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"] !== "false";
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: JSON.stringify({
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
- }, null, 2)
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"] !== "false";
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: JSON.stringify({
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
- }, null, 2)
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 ?? 5;
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"] !== "false";
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: JSON.stringify({
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
- }, null, 2)
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 ?? 50, 500);
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: JSON.stringify({ symbols: matches, total, offset, limit: effectiveLimit, hasMore }, null, 2)
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: JSON.stringify({ from: fromName, to: toName, hops: foundPath.length - 1, path: pathDetails }, null, 2)
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 ?? 50, 500);
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: JSON.stringify({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore }, null, 2)
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: JSON.stringify(routes, null, 2) }] };
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 ?? 50, 500);
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: JSON.stringify({ clusters, total, offset, limit: effectiveLimit, hasMore }, null, 2)
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 ?? 50, 500);
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: JSON.stringify({ flows, total, offset, limit: effectiveLimit, hasMore }, null, 2)
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: JSON.stringify({
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
- }, null, 2)
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: JSON.stringify({ error: "Missing required parameter: gql" }) }], isError: true };
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: JSON.stringify({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
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: JSON.stringify({
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
- }, null, 2)
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: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
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: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
9716
+ return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
9527
9717
  }
9528
- return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, error: "Query not recognized. Use name='X' or :kind syntax. Or use the query tool with GQL instead." }) }] };
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: JSON.stringify(group, null, 2) }] };
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: JSON.stringify(
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: JSON.stringify({
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
- }, null, 2)
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: JSON.stringify({ syncedAt: result.syncedAt, contracts, links }, null, 2)
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: JSON.stringify({ query, merged, perRepo }, null, 2)
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: JSON.stringify({
9825
+ text: compact({
9638
9826
  group: groupName,
9639
9827
  lastSync: group.lastSync ?? null,
9640
9828
  syncAgeMinutes: syncAge,
9641
9829
  members: memberStatus
9642
- }, null, 2)
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: JSON.stringify(result, null, 2) }] };
9839
+ return { content: [{ type: "text", text: compact(result) }] };
9652
9840
  }
9653
9841
  // ── pr_impact ──────────────────────────────────────────────────────────
9654
9842
  case "pr_impact": {
9655
- const maxHops = a.maxHops ?? 5;
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: JSON.stringify({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
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: JSON.stringify(result, null, 2) }] };
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: JSON.stringify(result, null, 2) }] };
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: JSON.stringify(result, null, 2) }] };
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: JSON.stringify(result, null, 2) }] };
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: JSON.stringify(result, null, 2) }] };
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: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
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: JSON.stringify({ hotspots, total: hotspots.length }, null, 2) }] };
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: JSON.stringify({
9911
+ text: compact({
9724
9912
  untestedByRisk,
9725
9913
  coveragePct: summary.coveragePct,
9726
9914
  totalExported: summary.totalExported,
9727
9915
  testedExported: summary.testedExported
9728
- }, null, 2)
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: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
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: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
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: JSON.stringify({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
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: JSON.stringify(clusters) }] };
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: JSON.stringify(flows) }] };
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
- init_group_registry();
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
- fs25.mkdirSync(path32.dirname(dbPath), { recursive: true });
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
- fs25.mkdirSync(this.backupDir, { recursive: true });
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 (fs25.existsSync(fp)) {
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 (fs25.existsSync(registryPath)) {
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 (fs25.existsSync(usersDbPath)) {
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 = fs25.readFileSync(f.localPath);
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 = fs25.readFileSync(f.localPath);
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
- fs25.writeFileSync(backupPath, encrypted);
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 = fs25.readFileSync(entry.path);
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
- fs25.mkdirSync(path32.dirname(destPath), { recursive: true });
10219
- fs25.writeFileSync(destPath, Buffer.from(result.body, "binary"));
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 (!fs25.existsSync(entry.path)) {
10454
+ if (!fs26.existsSync(entry.path)) {
10266
10455
  throw new Error(`Backup file not found at: ${entry.path}`);
10267
10456
  }
10268
- const encrypted = fs25.readFileSync(entry.path);
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
- fs25.mkdirSync(codeIntelDir, { recursive: true });
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
- fs25.writeFileSync(destPath, data);
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) => fs25.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
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
- fs25.unlinkSync(e.path);
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(fs25.readFileSync(this._indexPath(), "utf-8"));
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
- fs25.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
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 (fs25.existsSync(bundled)) return bundled;
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 (fs25.existsSync(metaFilePath)) {
11245
- const raw = fs25.readFileSync(metaFilePath, "utf-8");
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 (!fs25.existsSync(dbPath)) return null;
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 (!fs25.existsSync(dbPath)) continue;
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 = fs25.readFileSync(file_path, "utf-8");
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
- const { saveGroup: saveGroup2 } = await Promise.resolve().then(() => (init_group_registry(), group_registry_exports));
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 (!fs25.existsSync(dbPath)) continue;
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 (fs25.existsSync(dbPath)) {
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 (fs25.existsSync(candidate)) {
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 = fs25.readFileSync(resolvedFile, "utf-8");
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 (fs25.existsSync(WEB_DIST)) {
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
- init_group_registry();
12921
+ init_graph_from_db();
12629
12922
 
12630
12923
  // src/multi-repo/cross-repo-search.ts
12631
12924
  function mergeSearchResults(...perRepoResults) {