@vohongtho.infotech/code-intel 1.0.0 → 1.0.1

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