@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/cli/main.js CHANGED
@@ -7,7 +7,7 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
7
7
  import { SpanStatusCode, trace, context } from '@opentelemetry/api';
8
8
  import winston from 'winston';
9
9
  import DailyRotateFile from 'winston-daily-rotate-file';
10
- import fs38, { readFileSync, existsSync } from 'fs';
10
+ import fs39, { readFileSync, existsSync } from 'fs';
11
11
  import path39, { dirname, join } from 'path';
12
12
  import os13 from 'os';
13
13
  import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client';
@@ -336,8 +336,8 @@ var init_logger = __esm({
336
336
  transports.push(new winston.transports.Console());
337
337
  if (!isProduction) {
338
338
  try {
339
- if (!fs38.existsSync(_Logger.LOG_DIR)) {
340
- fs38.mkdirSync(_Logger.LOG_DIR, { recursive: true });
339
+ if (!fs39.existsSync(_Logger.LOG_DIR)) {
340
+ fs39.mkdirSync(_Logger.LOG_DIR, { recursive: true });
341
341
  }
342
342
  transports.push(
343
343
  new DailyRotateFile({
@@ -394,6 +394,10 @@ var init_logger = __esm({
394
394
  });
395
395
 
396
396
  // src/graph/knowledge-graph.ts
397
+ var knowledge_graph_exports = {};
398
+ __export(knowledge_graph_exports, {
399
+ createKnowledgeGraph: () => createKnowledgeGraph
400
+ });
397
401
  function createKnowledgeGraph() {
398
402
  const nodes = /* @__PURE__ */ new Map();
399
403
  const edges = /* @__PURE__ */ new Map();
@@ -889,7 +893,7 @@ var init_id_generator = __esm({
889
893
  });
890
894
  function loadIgnorePatterns(workspaceRoot) {
891
895
  try {
892
- const raw = fs38.readFileSync(path39.join(workspaceRoot, ".codeintelignore"), "utf-8");
896
+ const raw = fs39.readFileSync(path39.join(workspaceRoot, ".codeintelignore"), "utf-8");
893
897
  const extras = /* @__PURE__ */ new Set();
894
898
  for (const line of raw.split("\n")) {
895
899
  const trimmed = line.trim();
@@ -949,7 +953,7 @@ var init_scan_phase = __esm({
949
953
  function walk2(dir) {
950
954
  let entries;
951
955
  try {
952
- entries = fs38.readdirSync(dir, { withFileTypes: true });
956
+ entries = fs39.readdirSync(dir, { withFileTypes: true });
953
957
  } catch {
954
958
  return;
955
959
  }
@@ -966,7 +970,7 @@ var init_scan_phase = __esm({
966
970
  if (!extensions.has(ext)) continue;
967
971
  const fullPath = path39.join(dir, name);
968
972
  try {
969
- const stat = fs38.statSync(fullPath);
973
+ const stat = fs39.statSync(fullPath);
970
974
  if (stat.size > MAX_FILE_SIZE_BYTES) continue;
971
975
  } catch {
972
976
  continue;
@@ -2519,7 +2523,7 @@ var init_parse_phase = __esm({
2519
2523
  const batch = filePaths.slice(i, i + CONCURRENCY);
2520
2524
  await Promise.all(batch.map(async (filePath) => {
2521
2525
  try {
2522
- const source = await fs38.promises.readFile(filePath, "utf-8");
2526
+ const source = await fs39.promises.readFile(filePath, "utf-8");
2523
2527
  context2.fileCache.set(filePath, source);
2524
2528
  } catch {
2525
2529
  }
@@ -3127,8 +3131,8 @@ var init_llm_governance = __esm({
3127
3131
  ...entry
3128
3132
  };
3129
3133
  const logPath = this.getLogPath();
3130
- fs38.mkdirSync(path39.dirname(logPath), { recursive: true });
3131
- fs38.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
3134
+ fs39.mkdirSync(path39.dirname(logPath), { recursive: true });
3135
+ fs39.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
3132
3136
  } catch {
3133
3137
  }
3134
3138
  }
@@ -3138,7 +3142,7 @@ var init_llm_governance = __esm({
3138
3142
  */
3139
3143
  readLog(limit = 100) {
3140
3144
  try {
3141
- const raw = fs38.readFileSync(this.getLogPath(), "utf-8");
3145
+ const raw = fs39.readFileSync(this.getLogPath(), "utf-8");
3142
3146
  const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
3143
3147
  return lines.map((l) => JSON.parse(l));
3144
3148
  } catch {
@@ -3612,14 +3616,14 @@ var init_parse_phase_parallel = __esm({
3612
3616
  const batch = filePaths.slice(i, i + CONCURRENCY);
3613
3617
  await Promise.all(batch.map(async (filePath) => {
3614
3618
  try {
3615
- const source = await fs38.promises.readFile(filePath, "utf-8");
3619
+ const source = await fs39.promises.readFile(filePath, "utf-8");
3616
3620
  context2.fileCache.set(filePath, source);
3617
3621
  } catch {
3618
3622
  }
3619
3623
  }));
3620
3624
  }
3621
3625
  const workerScript = workerScriptPath();
3622
- const workerScriptExists = fs38.existsSync(workerScript);
3626
+ const workerScriptExists = fs39.existsSync(workerScript);
3623
3627
  if (!workerScriptExists || workerCount === 1) {
3624
3628
  logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
3625
3629
  const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
@@ -3734,7 +3738,7 @@ var init_resolve_phase_parallel = __esm({
3734
3738
  }
3735
3739
  const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
3736
3740
  const workerScript = workerScriptPath2();
3737
- const workerScriptExists = fs38.existsSync(workerScript);
3741
+ const workerScriptExists = fs39.existsSync(workerScript);
3738
3742
  const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os13.cpus().length - 1);
3739
3743
  if (!workerScriptExists || workerCount === 1) {
3740
3744
  logger_default.info(`[resolve-parallel] falling back to sequential`);
@@ -4071,6 +4075,41 @@ function nodeToDoc(node) {
4071
4075
  (node.content ?? "").slice(0, 1e3)
4072
4076
  ].join(" ");
4073
4077
  }
4078
+ function heapTopK(scores, k) {
4079
+ if (k <= 0) return [];
4080
+ const heap = [];
4081
+ function heapifyUp(i) {
4082
+ while (i > 0) {
4083
+ const parent = i - 1 >> 1;
4084
+ if (heap[parent][1] > heap[i][1]) {
4085
+ [heap[parent], heap[i]] = [heap[i], heap[parent]];
4086
+ i = parent;
4087
+ } else break;
4088
+ }
4089
+ }
4090
+ function heapifyDown(i) {
4091
+ const n = heap.length;
4092
+ while (true) {
4093
+ let smallest = i;
4094
+ const l = 2 * i + 1, r = 2 * i + 2;
4095
+ if (l < n && heap[l][1] < heap[smallest][1]) smallest = l;
4096
+ if (r < n && heap[r][1] < heap[smallest][1]) smallest = r;
4097
+ if (smallest === i) break;
4098
+ [heap[smallest], heap[i]] = [heap[i], heap[smallest]];
4099
+ i = smallest;
4100
+ }
4101
+ }
4102
+ for (const [nodeId, score] of scores) {
4103
+ if (heap.length < k) {
4104
+ heap.push([nodeId, score]);
4105
+ heapifyUp(heap.length - 1);
4106
+ } else if (score > heap[0][1]) {
4107
+ heap[0] = [nodeId, score];
4108
+ heapifyDown(0);
4109
+ }
4110
+ }
4111
+ return heap.sort((a, b) => b[1] - a[1]);
4112
+ }
4074
4113
  function getBm25DbPath(workspaceRoot) {
4075
4114
  return path39.join(workspaceRoot, ".code-intel", "bm25.db");
4076
4115
  }
@@ -4132,10 +4171,10 @@ var init_bm25_index = __esm({
4132
4171
  postings.push({ nodeId, tf: count });
4133
4172
  }
4134
4173
  }
4135
- fs38.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
4174
+ fs39.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
4136
4175
  for (const f of [this.dbPath, `${this.dbPath}-shm`, `${this.dbPath}-wal`]) {
4137
4176
  try {
4138
- if (fs38.existsSync(f)) fs38.unlinkSync(f);
4177
+ if (fs39.existsSync(f)) fs39.unlinkSync(f);
4139
4178
  } catch {
4140
4179
  }
4141
4180
  }
@@ -4173,7 +4212,7 @@ var init_bm25_index = __esm({
4173
4212
  * Called once on `serve` startup.
4174
4213
  */
4175
4214
  load() {
4176
- if (!fs38.existsSync(this.dbPath)) return;
4215
+ if (!fs39.existsSync(this.dbPath)) return;
4177
4216
  const db = new Database2(this.dbPath, { readonly: true });
4178
4217
  try {
4179
4218
  const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
@@ -4207,8 +4246,13 @@ var init_bm25_index = __esm({
4207
4246
  }
4208
4247
  // ── Search ──────────────────────────────────────────────────────────────────
4209
4248
  /**
4210
- * BM25 search. LIMIT pushdown: scores only the posting lists for query terms,
4211
- * then partial-sorts to return only the top `limit` results.
4249
+ * BM25 search.
4250
+ *
4251
+ * Performance strategy:
4252
+ * 1. Skip ultra-high-df terms (df/N > 0.6) — near-zero IDF, dominate posting
4253
+ * lists for common words like "function", "return", "export" in large repos.
4254
+ * 2. Min-heap top-K selection — O(n log k) instead of full O(n log n) sort.
4255
+ * For k=10 and n=30,000 candidates this is ~10× faster than Array.sort.
4212
4256
  */
4213
4257
  search(query, limit) {
4214
4258
  if (!this._loaded || this.invertedIndex.size === 0) return [];
@@ -4221,6 +4265,7 @@ var init_bm25_index = __esm({
4221
4265
  const postings = this.invertedIndex.get(term);
4222
4266
  if (!postings) continue;
4223
4267
  const df = postings.length;
4268
+ if (N > 100 && df / N > 0.6) continue;
4224
4269
  const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
4225
4270
  for (const { nodeId, tf } of postings) {
4226
4271
  const dl = this.docLengths.get(nodeId) ?? avgdl;
@@ -4228,10 +4273,9 @@ var init_bm25_index = __esm({
4228
4273
  scores.set(nodeId, (scores.get(nodeId) ?? 0) + score);
4229
4274
  }
4230
4275
  }
4231
- const entries = [...scores.entries()];
4232
- entries.sort((a, b) => b[1] - a[1]);
4233
- const topK = entries.slice(0, limit);
4234
- return topK.map(([nodeId, score]) => {
4276
+ if (scores.size === 0) return [];
4277
+ const topEntries = heapTopK(scores, limit);
4278
+ return topEntries.map(([nodeId, score]) => {
4235
4279
  const meta = this.nodeMeta.get(nodeId);
4236
4280
  return {
4237
4281
  nodeId,
@@ -4250,7 +4294,7 @@ var init_bm25_index = __esm({
4250
4294
  * Works even if `load()` was not called (reads affected terms directly from DB).
4251
4295
  */
4252
4296
  updateNodes(nodes) {
4253
- if (!fs38.existsSync(this.dbPath)) return;
4297
+ if (!fs39.existsSync(this.dbPath)) return;
4254
4298
  if (nodes.length === 0) return;
4255
4299
  const changedIds = new Set(nodes.map((n) => n.id));
4256
4300
  const newTermFreqs = /* @__PURE__ */ new Map();
@@ -4325,6 +4369,12 @@ var init_bm25_index = __esm({
4325
4369
  };
4326
4370
  }
4327
4371
  });
4372
+
4373
+ // src/storage/db-manager.ts
4374
+ var db_manager_exports = {};
4375
+ __export(db_manager_exports, {
4376
+ DbManager: () => DbManager
4377
+ });
4328
4378
  var DbManager;
4329
4379
  var init_db_manager = __esm({
4330
4380
  "src/storage/db-manager.ts"() {
@@ -4339,7 +4389,7 @@ var init_db_manager = __esm({
4339
4389
  }
4340
4390
  async init() {
4341
4391
  if (!this.readOnly) {
4342
- fs38.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
4392
+ fs39.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
4343
4393
  }
4344
4394
  this.db = new Database(this.dbPath, 0, true, this.readOnly);
4345
4395
  await this.db.init();
@@ -4379,7 +4429,7 @@ var init_db_manager = __esm({
4379
4429
  }
4380
4430
  });
4381
4431
  function writeNodeCSVs(graph, outputDir) {
4382
- fs38.mkdirSync(outputDir, { recursive: true });
4432
+ fs39.mkdirSync(outputDir, { recursive: true });
4383
4433
  const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
4384
4434
  const tableBuffers = /* @__PURE__ */ new Map();
4385
4435
  const tableFilePaths = /* @__PURE__ */ new Map();
@@ -4407,12 +4457,12 @@ function writeNodeCSVs(graph, outputDir) {
4407
4457
  );
4408
4458
  }
4409
4459
  for (const [table, lines] of tableBuffers) {
4410
- fs38.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
4460
+ fs39.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
4411
4461
  }
4412
4462
  return tableFilePaths;
4413
4463
  }
4414
4464
  function writeEdgeCSV(graph, outputDir) {
4415
- fs38.mkdirSync(outputDir, { recursive: true });
4465
+ fs39.mkdirSync(outputDir, { recursive: true });
4416
4466
  const header = "from_id,to_id,kind,weight,label\n";
4417
4467
  const groups = /* @__PURE__ */ new Map();
4418
4468
  for (const edge of graph.allEdges()) {
@@ -4438,7 +4488,7 @@ function writeEdgeCSV(graph, outputDir) {
4438
4488
  }
4439
4489
  const result = [];
4440
4490
  for (const group of groups.values()) {
4441
- fs38.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
4491
+ fs39.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
4442
4492
  result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
4443
4493
  }
4444
4494
  return result;
@@ -4481,7 +4531,7 @@ async function loadGraphToDB(graph, dbManager) {
4481
4531
  } catch {
4482
4532
  }
4483
4533
  }
4484
- const tmpDir = fs38.mkdtempSync(path39.join(os13.tmpdir(), "code-intel-csv-"));
4534
+ const tmpDir = fs39.mkdtempSync(path39.join(os13.tmpdir(), "code-intel-csv-"));
4485
4535
  try {
4486
4536
  const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
4487
4537
  const edgeGroups = writeEdgeCSV(graph, tmpDir);
@@ -4500,8 +4550,8 @@ async function loadGraphToDB(graph, dbManager) {
4500
4550
  }
4501
4551
  let nodeCount = 0;
4502
4552
  for (const [table, csvPath] of nodeTableFiles) {
4503
- if (!fs38.existsSync(csvPath)) continue;
4504
- const stat = fs38.statSync(csvPath);
4553
+ if (!fs39.existsSync(csvPath)) continue;
4554
+ const stat = fs39.statSync(csvPath);
4505
4555
  if (stat.size < 50) continue;
4506
4556
  try {
4507
4557
  await dbManager.execute(
@@ -4514,8 +4564,8 @@ async function loadGraphToDB(graph, dbManager) {
4514
4564
  }
4515
4565
  let edgeCount = 0;
4516
4566
  for (const group of edgeGroups) {
4517
- if (!fs38.existsSync(group.filePath)) continue;
4518
- const stat = fs38.statSync(group.filePath);
4567
+ if (!fs39.existsSync(group.filePath)) continue;
4568
+ const stat = fs39.statSync(group.filePath);
4519
4569
  if (stat.size < 50) continue;
4520
4570
  try {
4521
4571
  await dbManager.execute(
@@ -4529,7 +4579,7 @@ async function loadGraphToDB(graph, dbManager) {
4529
4579
  return { nodeCount, edgeCount };
4530
4580
  } finally {
4531
4581
  try {
4532
- fs38.rmSync(tmpDir, { recursive: true, force: true });
4582
+ fs39.rmSync(tmpDir, { recursive: true, force: true });
4533
4583
  } catch {
4534
4584
  }
4535
4585
  }
@@ -4640,15 +4690,15 @@ __export(repo_registry_exports, {
4640
4690
  });
4641
4691
  function loadRegistry() {
4642
4692
  try {
4643
- const data = fs38.readFileSync(REPOS_FILE, "utf-8");
4693
+ const data = fs39.readFileSync(REPOS_FILE, "utf-8");
4644
4694
  return JSON.parse(data);
4645
4695
  } catch {
4646
4696
  return [];
4647
4697
  }
4648
4698
  }
4649
4699
  function saveRegistry(entries) {
4650
- fs38.mkdirSync(GLOBAL_DIR, { recursive: true });
4651
- fs38.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
4700
+ fs39.mkdirSync(GLOBAL_DIR, { recursive: true });
4701
+ fs39.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
4652
4702
  }
4653
4703
  function upsertRepo(entry) {
4654
4704
  const entries = loadRegistry();
@@ -4682,12 +4732,12 @@ __export(metadata_exports, {
4682
4732
  });
4683
4733
  function saveMetadata(repoDir, metadata) {
4684
4734
  const metaDir = path39.join(repoDir, ".code-intel");
4685
- fs38.mkdirSync(metaDir, { recursive: true });
4686
- fs38.writeFileSync(path39.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
4735
+ fs39.mkdirSync(metaDir, { recursive: true });
4736
+ fs39.writeFileSync(path39.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
4687
4737
  }
4688
4738
  function loadMetadata(repoDir) {
4689
4739
  try {
4690
- const data = fs38.readFileSync(path39.join(repoDir, ".code-intel", "meta.json"), "utf-8");
4740
+ const data = fs39.readFileSync(path39.join(repoDir, ".code-intel", "meta.json"), "utf-8");
4691
4741
  return JSON.parse(data);
4692
4742
  } catch {
4693
4743
  return null;
@@ -4757,23 +4807,23 @@ function groupFile(name) {
4757
4807
  }
4758
4808
  function loadGroup(name) {
4759
4809
  try {
4760
- return JSON.parse(fs38.readFileSync(groupFile(name), "utf-8"));
4810
+ return JSON.parse(fs39.readFileSync(groupFile(name), "utf-8"));
4761
4811
  } catch {
4762
4812
  return null;
4763
4813
  }
4764
4814
  }
4765
4815
  function saveGroup(group) {
4766
- fs38.mkdirSync(GROUPS_DIR, { recursive: true });
4767
- fs38.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
4816
+ fs39.mkdirSync(GROUPS_DIR, { recursive: true });
4817
+ fs39.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
4768
4818
  }
4769
4819
  function listGroups() {
4770
4820
  const groups = [];
4771
4821
  try {
4772
- for (const file of fs38.readdirSync(GROUPS_DIR)) {
4822
+ for (const file of fs39.readdirSync(GROUPS_DIR)) {
4773
4823
  if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
4774
4824
  try {
4775
4825
  const g = JSON.parse(
4776
- fs38.readFileSync(path39.join(GROUPS_DIR, file), "utf-8")
4826
+ fs39.readFileSync(path39.join(GROUPS_DIR, file), "utf-8")
4777
4827
  );
4778
4828
  groups.push(g);
4779
4829
  } catch {
@@ -4785,16 +4835,16 @@ function listGroups() {
4785
4835
  }
4786
4836
  function deleteGroup(name) {
4787
4837
  try {
4788
- fs38.unlinkSync(groupFile(name));
4838
+ fs39.unlinkSync(groupFile(name));
4789
4839
  } catch {
4790
4840
  }
4791
4841
  try {
4792
- fs38.unlinkSync(path39.join(GROUPS_DIR, `${name}.sync.json`));
4842
+ fs39.unlinkSync(path39.join(GROUPS_DIR, `${name}.sync.json`));
4793
4843
  } catch {
4794
4844
  }
4795
4845
  }
4796
4846
  function groupExists(name) {
4797
- return fs38.existsSync(groupFile(name));
4847
+ return fs39.existsSync(groupFile(name));
4798
4848
  }
4799
4849
  function addMember(groupName, member) {
4800
4850
  const group = loadGroup(groupName);
@@ -4820,8 +4870,8 @@ function removeMember(groupName, groupPath) {
4820
4870
  return group;
4821
4871
  }
4822
4872
  function saveSyncResult(result) {
4823
- fs38.mkdirSync(GROUPS_DIR, { recursive: true });
4824
- fs38.writeFileSync(
4873
+ fs39.mkdirSync(GROUPS_DIR, { recursive: true });
4874
+ fs39.writeFileSync(
4825
4875
  path39.join(GROUPS_DIR, `${result.groupName}.sync.json`),
4826
4876
  JSON.stringify(result, null, 2) + "\n"
4827
4877
  );
@@ -4829,7 +4879,7 @@ function saveSyncResult(result) {
4829
4879
  function loadSyncResult(groupName) {
4830
4880
  try {
4831
4881
  return JSON.parse(
4832
- fs38.readFileSync(path39.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
4882
+ fs39.readFileSync(path39.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
4833
4883
  );
4834
4884
  } catch {
4835
4885
  return null;
@@ -4843,6 +4893,10 @@ var init_group_registry = __esm({
4843
4893
  });
4844
4894
 
4845
4895
  // src/multi-repo/graph-from-db.ts
4896
+ var graph_from_db_exports = {};
4897
+ __export(graph_from_db_exports, {
4898
+ loadGraphFromDB: () => loadGraphFromDB
4899
+ });
4846
4900
  function parseRow(row, kind) {
4847
4901
  return {
4848
4902
  id: String(row["id"] ?? ""),
@@ -4923,7 +4977,7 @@ function scanForFiles(root, matcher, maxDepth = 2) {
4923
4977
  if (depth > maxDepth) return;
4924
4978
  let entries;
4925
4979
  try {
4926
- entries = fs38.readdirSync(dir, { withFileTypes: true });
4980
+ entries = fs39.readdirSync(dir, { withFileTypes: true });
4927
4981
  } catch {
4928
4982
  return;
4929
4983
  }
@@ -4945,7 +4999,7 @@ var init_file_scanner = __esm({
4945
4999
  });
4946
5000
  function tryParseFile(filePath) {
4947
5001
  const ext = path39.extname(filePath).toLowerCase();
4948
- const content = fs38.readFileSync(filePath, "utf-8");
5002
+ const content = fs39.readFileSync(filePath, "utf-8");
4949
5003
  if (ext === ".json") {
4950
5004
  try {
4951
5005
  return JSON.parse(content);
@@ -5014,7 +5068,7 @@ async function parseGraphQLContracts(repoRoot) {
5014
5068
  const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
5015
5069
  const contracts = [];
5016
5070
  for (const filePath of files) {
5017
- const content = fs38.readFileSync(filePath, "utf-8");
5071
+ const content = fs39.readFileSync(filePath, "utf-8");
5018
5072
  const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
5019
5073
  let match;
5020
5074
  while ((match = typeRegex.exec(content)) !== null) {
@@ -5050,7 +5104,7 @@ async function parseProtoContracts(repoRoot) {
5050
5104
  const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
5051
5105
  const contracts = [];
5052
5106
  for (const filePath of files) {
5053
- const content = fs38.readFileSync(filePath, "utf-8");
5107
+ const content = fs39.readFileSync(filePath, "utf-8");
5054
5108
  const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
5055
5109
  let serviceMatch;
5056
5110
  while ((serviceMatch = serviceRegex.exec(content)) !== null) {
@@ -5269,7 +5323,7 @@ async function syncGroup(group) {
5269
5323
  continue;
5270
5324
  }
5271
5325
  const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
5272
- if (!fs38.existsSync(dbPath)) {
5326
+ if (!fs39.existsSync(dbPath)) {
5273
5327
  logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
5274
5328
  continue;
5275
5329
  }
@@ -5450,10 +5504,10 @@ var init_codes = __esm({
5450
5504
  }
5451
5505
  });
5452
5506
  function secureMkdir(dir) {
5453
- fs38.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
5507
+ fs39.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
5454
5508
  if (process.platform !== "win32") {
5455
5509
  try {
5456
- fs38.chmodSync(dir, SECURE_DIR_MODE);
5510
+ fs39.chmodSync(dir, SECURE_DIR_MODE);
5457
5511
  } catch {
5458
5512
  }
5459
5513
  }
@@ -5461,22 +5515,22 @@ function secureMkdir(dir) {
5461
5515
  function secureChmodFile(file) {
5462
5516
  if (process.platform === "win32") return;
5463
5517
  try {
5464
- fs38.chmodSync(file, SECURE_FILE_MODE);
5518
+ fs39.chmodSync(file, SECURE_FILE_MODE);
5465
5519
  } catch {
5466
5520
  }
5467
5521
  }
5468
5522
  function secureWriteFile(file, data) {
5469
5523
  secureMkdir(path39.dirname(file));
5470
- fs38.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
5524
+ fs39.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
5471
5525
  secureChmodFile(file);
5472
5526
  }
5473
5527
  function tightenDbFiles(dir) {
5474
5528
  if (process.platform === "win32") return;
5475
- if (!fs38.existsSync(dir)) return;
5476
- for (const name of fs38.readdirSync(dir)) {
5529
+ if (!fs39.existsSync(dir)) return;
5530
+ for (const name of fs39.readdirSync(dir)) {
5477
5531
  if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
5478
5532
  try {
5479
- fs38.chmodSync(path39.join(dir, name), SECURE_FILE_MODE);
5533
+ fs39.chmodSync(path39.join(dir, name), SECURE_FILE_MODE);
5480
5534
  } catch {
5481
5535
  }
5482
5536
  }
@@ -5825,8 +5879,8 @@ function decryptSecrets(encrypted) {
5825
5879
  return JSON.parse(plaintext.toString("utf8"));
5826
5880
  }
5827
5881
  function loadSecrets(secretsPath = getSecretsPath()) {
5828
- if (!fs38.existsSync(secretsPath)) return {};
5829
- const blob = fs38.readFileSync(secretsPath);
5882
+ if (!fs39.existsSync(secretsPath)) return {};
5883
+ const blob = fs39.readFileSync(secretsPath);
5830
5884
  return decryptSecrets(blob);
5831
5885
  }
5832
5886
  function saveSecrets(blob, secretsPath = getSecretsPath()) {
@@ -5866,11 +5920,18 @@ function getSessionTtlMs() {
5866
5920
  const hours = parseInt(process.env["CODE_INTEL_SESSION_TTL_HOURS"] ?? "8", 10);
5867
5921
  return (isNaN(hours) ? 8 : hours) * 60 * 60 * 1e3;
5868
5922
  }
5869
- function createSession(user) {
5923
+ function createSession(user, rememberMe = false) {
5870
5924
  const sessionId = v4();
5871
- const expiresAt = Date.now() + getSessionTtlMs();
5872
- sessionStore.set(sessionId, { userId: user.id, username: user.username, role: user.role, expiresAt });
5873
- return sessionId;
5925
+ const ttlMs = rememberMe ? REMEMBER_ME_TTL_MS : getSessionTtlMs();
5926
+ const expiresAt = Date.now() + ttlMs;
5927
+ sessionStore.set(sessionId, {
5928
+ userId: user.id,
5929
+ username: user.username,
5930
+ role: user.role,
5931
+ expiresAt,
5932
+ ttlMs
5933
+ });
5934
+ return { sessionId, ttlMs };
5874
5935
  }
5875
5936
  function getSession(sessionId) {
5876
5937
  const entry = sessionStore.get(sessionId);
@@ -5879,10 +5940,9 @@ function getSession(sessionId) {
5879
5940
  sessionStore.delete(sessionId);
5880
5941
  return null;
5881
5942
  }
5882
- const ttlMs = getSessionTtlMs();
5883
5943
  const remaining = entry.expiresAt - Date.now();
5884
- if (remaining < ttlMs * 0.75) {
5885
- entry.expiresAt = Date.now() + ttlMs;
5944
+ if (remaining < entry.ttlMs * 0.75) {
5945
+ entry.expiresAt = Date.now() + entry.ttlMs;
5886
5946
  }
5887
5947
  return entry;
5888
5948
  }
@@ -5902,7 +5962,7 @@ function authMiddleware(req, res, next) {
5902
5962
  const session = getSession(sessionId);
5903
5963
  if (session) {
5904
5964
  req.user = { id: session.userId, username: session.username, role: session.role, authMethod: "session" };
5905
- res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
5965
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, session.ttlMs));
5906
5966
  next();
5907
5967
  return;
5908
5968
  }
@@ -6072,9 +6132,9 @@ function parseCookies(cookieHeader) {
6072
6132
  }
6073
6133
  return result;
6074
6134
  }
6075
- function buildSessionCookie(sessionId) {
6135
+ function buildSessionCookie(sessionId, ttlMs) {
6076
6136
  const isProduction = process.env["NODE_ENV"] === "production";
6077
- const maxAge = Math.floor(getSessionTtlMs() / 1e3);
6137
+ const maxAge = Math.floor((ttlMs ?? getSessionTtlMs()) / 1e3);
6078
6138
  const parts = [
6079
6139
  `${SESSION_COOKIE_NAME}=${encodeURIComponent(sessionId)}`,
6080
6140
  `HttpOnly`,
@@ -6091,7 +6151,7 @@ function clearSessionCookie() {
6091
6151
  async function verifyPassword(plain, hash) {
6092
6152
  return bcrypt.compare(plain, hash);
6093
6153
  }
6094
- var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
6154
+ var sessionStore, SESSION_COOKIE_NAME, REMEMBER_ME_TTL_MS, ROLE_RANK;
6095
6155
  var init_middleware = __esm({
6096
6156
  "src/auth/middleware.ts"() {
6097
6157
  init_users_db();
@@ -6099,6 +6159,7 @@ var init_middleware = __esm({
6099
6159
  init_secret_store();
6100
6160
  sessionStore = /* @__PURE__ */ new Map();
6101
6161
  SESSION_COOKIE_NAME = "code_intel_session";
6162
+ REMEMBER_ME_TTL_MS = 12 * 60 * 60 * 1e3;
6102
6163
  ROLE_RANK = {
6103
6164
  viewer: 1,
6104
6165
  "repo-owner": 2,
@@ -7871,7 +7932,7 @@ var init_secret_scanner = __esm({
7871
7932
  const ignorePatterns = [...options?.ignorePatterns ?? []];
7872
7933
  if (options?.workspaceRoot) {
7873
7934
  try {
7874
- const raw = fs38.readFileSync(path39.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
7935
+ const raw = fs39.readFileSync(path39.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
7875
7936
  for (const line of raw.split("\n")) {
7876
7937
  const trimmed = line.trim();
7877
7938
  if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
@@ -8129,22 +8190,22 @@ __export(init_wizard_exports, {
8129
8190
  wipeConfig: () => wipeConfig
8130
8191
  });
8131
8192
  function configExists() {
8132
- return fs38.existsSync(CONFIG_PATH);
8193
+ return fs39.existsSync(CONFIG_PATH);
8133
8194
  }
8134
8195
  function loadConfig() {
8135
8196
  if (!configExists()) return null;
8136
8197
  try {
8137
- return JSON.parse(fs38.readFileSync(CONFIG_PATH, "utf-8"));
8198
+ return JSON.parse(fs39.readFileSync(CONFIG_PATH, "utf-8"));
8138
8199
  } catch {
8139
8200
  return null;
8140
8201
  }
8141
8202
  }
8142
8203
  function saveConfig(cfg) {
8143
- fs38.mkdirSync(GLOBAL_DIR2, { recursive: true });
8144
- fs38.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
8204
+ fs39.mkdirSync(GLOBAL_DIR2, { recursive: true });
8205
+ fs39.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
8145
8206
  }
8146
8207
  function wipeConfig() {
8147
- if (fs38.existsSync(CONFIG_PATH)) fs38.unlinkSync(CONFIG_PATH);
8208
+ if (fs39.existsSync(CONFIG_PATH)) fs39.unlinkSync(CONFIG_PATH);
8148
8209
  }
8149
8210
  function commandExists(bin) {
8150
8211
  try {
@@ -8224,8 +8285,8 @@ async function runInitWizard(opts = {}) {
8224
8285
  const mcpFile = path39.resolve(editor.mcpConfigKey);
8225
8286
  try {
8226
8287
  let existing = {};
8227
- if (fs38.existsSync(mcpFile)) {
8228
- existing = JSON.parse(fs38.readFileSync(mcpFile, "utf-8"));
8288
+ if (fs39.existsSync(mcpFile)) {
8289
+ existing = JSON.parse(fs39.readFileSync(mcpFile, "utf-8"));
8229
8290
  }
8230
8291
  const merged = {
8231
8292
  ...existing,
@@ -8234,8 +8295,8 @@ async function runInitWizard(opts = {}) {
8234
8295
  ...mcpConfig.servers
8235
8296
  }
8236
8297
  };
8237
- fs38.mkdirSync(path39.dirname(mcpFile), { recursive: true });
8238
- fs38.writeFileSync(mcpFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8298
+ fs39.mkdirSync(path39.dirname(mcpFile), { recursive: true });
8299
+ fs39.writeFileSync(mcpFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8239
8300
  console.log(` \u2705 MCP registered for ${name} \u2192 ${editor.mcpConfigKey}`);
8240
8301
  } catch {
8241
8302
  console.log(` \u26A0 Could not write MCP config for ${name} \u2014 do it manually.`);
@@ -9005,8 +9066,8 @@ var init_file_watcher = __esm({
9005
9066
  readCodeIntelIgnore() {
9006
9067
  const ignoreFile = path39.join(this.workspaceRoot, ".codeintelignore");
9007
9068
  try {
9008
- if (!fs38.existsSync(ignoreFile)) return [];
9009
- return fs38.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
9069
+ if (!fs39.existsSync(ignoreFile)) return [];
9070
+ return fs39.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
9010
9071
  } catch {
9011
9072
  return [];
9012
9073
  }
@@ -9067,7 +9128,7 @@ var init_incremental_indexer = __esm({
9067
9128
  graph.removeNodeCascade(id);
9068
9129
  nodesRemoved++;
9069
9130
  }
9070
- if (fs38.existsSync(dbPath)) {
9131
+ if (fs39.existsSync(dbPath)) {
9071
9132
  try {
9072
9133
  const db = new DbManager(dbPath);
9073
9134
  await db.init();
@@ -9082,7 +9143,7 @@ var init_incremental_indexer = __esm({
9082
9143
  }
9083
9144
  const existingFiles = changedFiles.filter((f) => {
9084
9145
  try {
9085
- return fs38.statSync(f).isFile();
9146
+ return fs39.statSync(f).isFile();
9086
9147
  } catch {
9087
9148
  return false;
9088
9149
  }
@@ -9104,7 +9165,7 @@ var init_incremental_indexer = __esm({
9104
9165
  await runPipeline([noopScan, parsePhase, resolvePhase], context2);
9105
9166
  }
9106
9167
  const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
9107
- if (fs38.existsSync(dbPath) && existingFiles.length > 0) {
9168
+ if (fs39.existsSync(dbPath) && existingFiles.length > 0) {
9108
9169
  try {
9109
9170
  const db = new DbManager(dbPath);
9110
9171
  await db.init();
@@ -9116,7 +9177,7 @@ var init_incremental_indexer = __esm({
9116
9177
  db.close();
9117
9178
  try {
9118
9179
  const bm25DbPath = getBm25DbPath(workspaceRoot);
9119
- if (fs38.existsSync(bm25DbPath)) {
9180
+ if (fs39.existsSync(bm25DbPath)) {
9120
9181
  const bm25 = new Bm25Index(bm25DbPath);
9121
9182
  bm25.updateNodes(nodesToUpsert);
9122
9183
  logger_default.info(`[incremental] BM25 index updated: ${nodesToUpsert.length} nodes`);
@@ -9155,31 +9216,31 @@ function getQueriesDir(workspaceRoot) {
9155
9216
  }
9156
9217
  function ensureQueriesDir(workspaceRoot) {
9157
9218
  const dir = getQueriesDir(workspaceRoot);
9158
- if (!fs38.existsSync(dir)) {
9159
- fs38.mkdirSync(dir, { recursive: true });
9219
+ if (!fs39.existsSync(dir)) {
9220
+ fs39.mkdirSync(dir, { recursive: true });
9160
9221
  }
9161
9222
  return dir;
9162
9223
  }
9163
9224
  function saveQuery(workspaceRoot, name, gql) {
9164
9225
  const dir = ensureQueriesDir(workspaceRoot);
9165
9226
  const filePath = path39.join(dir, `${name}.gql`);
9166
- fs38.writeFileSync(filePath, gql, "utf-8");
9227
+ fs39.writeFileSync(filePath, gql, "utf-8");
9167
9228
  }
9168
9229
  function loadQuery(workspaceRoot, name) {
9169
9230
  const dir = getQueriesDir(workspaceRoot);
9170
9231
  const filePath = path39.join(dir, `${name}.gql`);
9171
- if (!fs38.existsSync(filePath)) return null;
9172
- return fs38.readFileSync(filePath, "utf-8");
9232
+ if (!fs39.existsSync(filePath)) return null;
9233
+ return fs39.readFileSync(filePath, "utf-8");
9173
9234
  }
9174
9235
  function listQueries(workspaceRoot) {
9175
9236
  const dir = getQueriesDir(workspaceRoot);
9176
- if (!fs38.existsSync(dir)) return [];
9177
- const files = fs38.readdirSync(dir).filter((f) => f.endsWith(".gql"));
9237
+ if (!fs39.existsSync(dir)) return [];
9238
+ const files = fs39.readdirSync(dir).filter((f) => f.endsWith(".gql"));
9178
9239
  return files.map((f) => {
9179
9240
  const filePath = path39.join(dir, f);
9180
9241
  const name = f.replace(/\.gql$/, "");
9181
- const content = fs38.readFileSync(filePath, "utf-8");
9182
- const stat = fs38.statSync(filePath);
9242
+ const content = fs39.readFileSync(filePath, "utf-8");
9243
+ const stat = fs39.statSync(filePath);
9183
9244
  return {
9184
9245
  name,
9185
9246
  content,
@@ -9191,14 +9252,14 @@ function listQueries(workspaceRoot) {
9191
9252
  function deleteQuery(workspaceRoot, name) {
9192
9253
  const dir = getQueriesDir(workspaceRoot);
9193
9254
  const filePath = path39.join(dir, `${name}.gql`);
9194
- if (!fs38.existsSync(filePath)) return false;
9195
- fs38.unlinkSync(filePath);
9255
+ if (!fs39.existsSync(filePath)) return false;
9256
+ fs39.unlinkSync(filePath);
9196
9257
  return true;
9197
9258
  }
9198
9259
  function queryExists(workspaceRoot, name) {
9199
9260
  const dir = getQueriesDir(workspaceRoot);
9200
9261
  const filePath = path39.join(dir, `${name}.gql`);
9201
- return fs38.existsSync(filePath);
9262
+ return fs39.existsSync(filePath);
9202
9263
  }
9203
9264
  var init_saved_queries = __esm({
9204
9265
  "src/query/saved-queries.ts"() {
@@ -9266,6 +9327,324 @@ var init_sarif_builder = __esm({
9266
9327
  }
9267
9328
  });
9268
9329
 
9330
+ // src/context/token-counter.ts
9331
+ var token_counter_exports = {};
9332
+ __export(token_counter_exports, {
9333
+ estimateTokens: () => estimateTokens,
9334
+ measureBlocks: () => measureBlocks
9335
+ });
9336
+ function estimateTokens(text) {
9337
+ if (!text) return 0;
9338
+ const words = text.split(/\s+/).filter(Boolean).length;
9339
+ const chars = text.length;
9340
+ return Math.ceil((words * 1.3 + chars * 0.25) / 2);
9341
+ }
9342
+ function measureBlocks(doc) {
9343
+ const summary = estimateTokens(doc.summary);
9344
+ const logic = estimateTokens(doc.logic);
9345
+ const relation = estimateTokens(doc.relation);
9346
+ const focusCode = estimateTokens(doc.focusCode);
9347
+ return { summary, logic, relation, focusCode, total: summary + logic + relation + focusCode };
9348
+ }
9349
+ var init_token_counter = __esm({
9350
+ "src/context/token-counter.ts"() {
9351
+ }
9352
+ });
9353
+
9354
+ // src/context/builder.ts
9355
+ var builder_exports = {};
9356
+ __export(builder_exports, {
9357
+ build: () => build,
9358
+ detectQueryIntent: () => detectQueryIntent
9359
+ });
9360
+ function detectQueryIntent(question) {
9361
+ const q = question.toLowerCase();
9362
+ if (/\b(show|code|implement|source|how is written|function body|method body)\b/.test(q)) return "code";
9363
+ if (/\b(who calls|callers?|depends on|blast radius|impact|upstream)\b/.test(q)) return "callers";
9364
+ if (/\b(architecture|overview|structure|design|how is built|system)\b/.test(q)) return "architecture";
9365
+ return "auto";
9366
+ }
9367
+ function last2Segments(filePath) {
9368
+ const parts = filePath.replace(/\\/g, "/").split("/").filter(Boolean);
9369
+ return parts.slice(-2).join("/");
9370
+ }
9371
+ function firstSentence(text) {
9372
+ if (!text) return "";
9373
+ const sentence = text.split(/[.!?]/)[0]?.trim() ?? "";
9374
+ const words = sentence.split(/\s+/);
9375
+ return words.slice(0, 15).join(" ");
9376
+ }
9377
+ function getCluster(graph, nodeId) {
9378
+ for (const edge of graph.findEdgesFrom(nodeId)) {
9379
+ if (edge.kind === "belongs_to") return graph.getNode(edge.target)?.name;
9380
+ }
9381
+ return void 0;
9382
+ }
9383
+ function dirOf(filePath) {
9384
+ const parts = filePath.replace(/\\/g, "/").split("/").filter(Boolean);
9385
+ return parts.slice(0, -1).join("/") || ".";
9386
+ }
9387
+ function meaningfulLines(content) {
9388
+ return content.split("\n").filter((l) => {
9389
+ const t = l.trim();
9390
+ return t.length > 0 && !t.startsWith("//") && !t.startsWith("*") && !t.startsWith("#");
9391
+ });
9392
+ }
9393
+ function adaptiveSnippet(content) {
9394
+ if (!content) return { lines: "", truncated: false };
9395
+ const stripped = content.replace(/^\n+|\n+$/g, "");
9396
+ const rawLines = stripped.split("\n");
9397
+ const ml = meaningfulLines(stripped).length;
9398
+ if (ml <= 10) return { lines: stripped, truncated: false };
9399
+ if (ml <= 25) {
9400
+ const out2 = rawLines.slice(0, 25);
9401
+ const truncated = rawLines.length > 25;
9402
+ return { lines: out2.join("\n") + (truncated ? "\n// ..." : ""), truncated };
9403
+ }
9404
+ const out = rawLines.slice(0, 40);
9405
+ const remaining = rawLines.length - 40;
9406
+ return {
9407
+ lines: out.join("\n") + (remaining > 0 ? `
9408
+ // ... (${remaining} more lines)` : ""),
9409
+ truncated: remaining > 0
9410
+ };
9411
+ }
9412
+ function buildSummaryBlock(nodes, graph, dedup) {
9413
+ if (nodes.length === 0) return "";
9414
+ const byDir = /* @__PURE__ */ new Map();
9415
+ for (const node of nodes) {
9416
+ const dir = dirOf(node.filePath);
9417
+ if (!byDir.has(dir)) byDir.set(dir, []);
9418
+ byDir.get(dir).push(node);
9419
+ }
9420
+ const lines = ["[SUMMARY]"];
9421
+ for (const [dir, group] of byDir) {
9422
+ const useHeader = group.length >= 3;
9423
+ if (useHeader) lines.push(`${dir}/:`);
9424
+ for (const node of group) {
9425
+ const summary = firstSentence(node.metadata?.["summary"]);
9426
+ const callerCount = [...graph.findEdgesTo(node.id)].filter((e) => e.kind === "calls").length;
9427
+ getCluster(graph, node.id);
9428
+ const badges = [];
9429
+ if (callerCount >= 10) badges.push("\u26A0");
9430
+ if (callerCount === 0) badges.push("\u{1F47B}");
9431
+ const badgeStr = badges.join("");
9432
+ const path210 = last2Segments(node.filePath);
9433
+ const line = node.startLine ? `:${node.startLine}` : "";
9434
+ const fullFmt = `${node.name} [${node.kind}] ${path210}${line}${badgeStr ? " " + badgeStr : ""}${summary ? " \u2014 " + summary : ""}`;
9435
+ const formatted = dedup.formatSymbol(node.name, node.filePath, fullFmt);
9436
+ lines.push(useHeader ? ` ${formatted}` : formatted);
9437
+ }
9438
+ }
9439
+ return lines.join("\n");
9440
+ }
9441
+ function buildLogicBlock(nodes, graph, dedup) {
9442
+ if (nodes.length === 0) return "";
9443
+ const lines = ["[LOGIC]"];
9444
+ const nodeCallees = /* @__PURE__ */ new Map();
9445
+ const calleeUsage = /* @__PURE__ */ new Map();
9446
+ for (const node of nodes) {
9447
+ const callees = [];
9448
+ for (const edge of graph.findEdgesFrom(node.id)) {
9449
+ if (edge.kind === "calls") {
9450
+ const callee = graph.getNode(edge.target);
9451
+ if (callee && callee.name !== node.name) {
9452
+ callees.push(callee.name);
9453
+ calleeUsage.set(callee.name, (calleeUsage.get(callee.name) ?? 0) + 1);
9454
+ }
9455
+ }
9456
+ }
9457
+ nodeCallees.set(node.id, [...new Set(callees)]);
9458
+ }
9459
+ const sharedCallees = new Set(
9460
+ [...calleeUsage.entries()].filter(([, cnt]) => cnt >= 3).map(([name]) => name)
9461
+ );
9462
+ if (sharedCallees.size > 0) {
9463
+ lines.push(`(all above \u2192 ${[...sharedCallees].join(", ")})`);
9464
+ }
9465
+ for (const node of nodes) {
9466
+ const callees = (nodeCallees.get(node.id) ?? []).filter((c) => !sharedCallees.has(c));
9467
+ for (const callee of callees) {
9468
+ dedup.markCallPair(node.name, callee);
9469
+ }
9470
+ if (callees.length === 0) continue;
9471
+ if (callees.length <= 5) {
9472
+ for (const callee of callees) dedup.markInLogic(callee);
9473
+ lines.push(`${node.name} \u2192 ${callees.join(", ")}`);
9474
+ } else {
9475
+ lines.push(`${node.name} \u2192`);
9476
+ for (const callee of callees) {
9477
+ dedup.markInLogic(callee);
9478
+ if (dedup.hasSymbol(callee)) {
9479
+ lines.push(` ${callee}`);
9480
+ } else {
9481
+ const calleeNode = [...graph.allNodes()].find((n) => n.name === callee);
9482
+ const path40 = calleeNode ? ` (${last2Segments(calleeNode.filePath)})` : "";
9483
+ lines.push(` ${callee}${path40}`);
9484
+ }
9485
+ }
9486
+ }
9487
+ }
9488
+ return lines.length > 1 ? lines.join("\n") : "";
9489
+ }
9490
+ function buildRelationBlock(nodes, graph, dedup) {
9491
+ if (nodes.length === 0) return "";
9492
+ const lines = ["[RELATION]"];
9493
+ for (const node of nodes) {
9494
+ const callers = [...graph.findEdgesTo(node.id)].filter((e) => e.kind === "calls").map((e) => graph.getNode(e.source)?.name).filter((n) => Boolean(n));
9495
+ const extendsNodes = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "extends").map((e) => graph.getNode(e.target)?.name).filter((n) => Boolean(n));
9496
+ const implementsNodes = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "implements").map((e) => graph.getNode(e.target)?.name).filter((n) => Boolean(n));
9497
+ const highBlast = callers.length >= 5;
9498
+ const prefix = highBlast ? "\u26A1 " : "";
9499
+ if (callers.length > 0) {
9500
+ const nonDupCallers = callers.filter(
9501
+ (c) => highBlast || !dedup.hasCallPair(c, node.name)
9502
+ );
9503
+ if (nonDupCallers.length > 0) {
9504
+ const top3 = nonDupCallers.slice(0, 3);
9505
+ const rest = nonDupCallers.length - 3;
9506
+ const callerStr = top3.join(", ") + (rest > 0 ? ` (+${rest} more \u2014 use blast_radius for full list)` : "");
9507
+ lines.push(`${prefix}${node.name} \u2190 ${callerStr}`);
9508
+ }
9509
+ }
9510
+ const heritage = [];
9511
+ if (extendsNodes.length > 0) heritage.push(`extends ${extendsNodes.join(", ")}`);
9512
+ if (implementsNodes.length > 0) heritage.push(`implements ${implementsNodes.join(" \xB7 ")}`);
9513
+ if (heritage.length > 0) lines.push(`${node.name}: ${heritage.join(" \xB7 ")}`);
9514
+ }
9515
+ return lines.length > 1 ? lines.join("\n") : "";
9516
+ }
9517
+ function buildFocusCodeBlock(seeds, nodes, dedup, signatureOnlyThreshold, tokenBudget) {
9518
+ if (nodes.length === 0) return { text: "", truncated: false };
9519
+ const lines = ["[FOCUS CODE]"];
9520
+ let usedTokens = estimateTokens("[FOCUS CODE]");
9521
+ let truncated = false;
9522
+ for (let i = 0; i < nodes.length; i++) {
9523
+ const node = nodes[i];
9524
+ const seed = seeds.find((s) => s.nodeId === node.id);
9525
+ const score = seed?.refinedScore ?? 1;
9526
+ const content = node.content;
9527
+ const ml = content ? meaningfulLines(content).length : 0;
9528
+ if (ml <= 5 && dedup.isInLogic(node.name)) continue;
9529
+ const header = `// ${node.name} \u2014 ${last2Segments(node.filePath)}${node.startLine ? ":" + node.startLine : ""}`;
9530
+ if (score < signatureOnlyThreshold) {
9531
+ const sig = content?.split("\n").find((l) => l.trim().length > 0) ?? "";
9532
+ const sigLine = sig ? sig.trimEnd() + (sig.includes("{") ? " ... }" : "") : "";
9533
+ const entry2 = `${header}
9534
+ // (low relevance)
9535
+ ${sigLine}`;
9536
+ const toks2 = estimateTokens(entry2);
9537
+ if (usedTokens + toks2 > tokenBudget) {
9538
+ truncated = true;
9539
+ break;
9540
+ }
9541
+ lines.push(entry2);
9542
+ usedTokens += toks2;
9543
+ continue;
9544
+ }
9545
+ const { lines: snippet, truncated: snipTruncated } = adaptiveSnippet(content);
9546
+ const entry = `${header}
9547
+ \`\`\`
9548
+ ${snippet}
9549
+ \`\`\``;
9550
+ const toks = estimateTokens(entry);
9551
+ if (usedTokens + toks > tokenBudget) {
9552
+ truncated = true;
9553
+ break;
9554
+ }
9555
+ lines.push(entry);
9556
+ usedTokens += toks;
9557
+ if (snipTruncated) truncated = true;
9558
+ }
9559
+ return { text: lines.length > 1 ? lines.join("\n\n") : "", truncated };
9560
+ }
9561
+ function build(seeds, graph, options = {}) {
9562
+ const maxTokens = options.maxTokens ?? 6e3;
9563
+ const signatureOnlyThreshold = options.signatureOnlyThreshold ?? 0.3;
9564
+ const intent = options.queryIntent ?? "auto";
9565
+ const budgets = BUDGET_PRESETS[intent];
9566
+ const nodes = seeds.map((s) => graph.getNode(s.nodeId)).filter((n) => n !== void 0);
9567
+ const dedup = new DedupeRegistry();
9568
+ let available = maxTokens;
9569
+ const summaryText = buildSummaryBlock(nodes, graph, dedup);
9570
+ const summaryToks = estimateTokens(summaryText);
9571
+ const summaryUsed = Math.min(summaryToks, Math.min(budgets.summary, available));
9572
+ available -= summaryUsed;
9573
+ const logicText = buildLogicBlock(nodes, graph, dedup);
9574
+ const logicToks = estimateTokens(logicText);
9575
+ const logicBudget = Math.min(budgets.logic, Math.floor(available * 0.35));
9576
+ const logicUsed = Math.min(logicToks, logicBudget);
9577
+ available -= logicUsed;
9578
+ const relationText = buildRelationBlock(nodes, graph, dedup);
9579
+ const relationToks = estimateTokens(relationText);
9580
+ const relationBudget = Math.min(budgets.relation, Math.floor(available * 0.35));
9581
+ const relationUsed = Math.min(relationToks, relationBudget);
9582
+ available -= relationUsed;
9583
+ const focusBudget = available;
9584
+ const { text: focusText, truncated } = buildFocusCodeBlock(
9585
+ seeds,
9586
+ nodes,
9587
+ dedup,
9588
+ signatureOnlyThreshold,
9589
+ focusBudget
9590
+ );
9591
+ return {
9592
+ summary: summaryText,
9593
+ logic: logicText,
9594
+ relation: relationText,
9595
+ focusCode: focusText,
9596
+ truncated,
9597
+ intent
9598
+ };
9599
+ }
9600
+ var BUDGET_PRESETS, DedupeRegistry;
9601
+ var init_builder = __esm({
9602
+ "src/context/builder.ts"() {
9603
+ init_token_counter();
9604
+ BUDGET_PRESETS = {
9605
+ code: { summary: 300, logic: 400, relation: 300, focusCode: 5e3 },
9606
+ callers: { summary: 500, logic: 300, relation: 2500, focusCode: 700 },
9607
+ architecture: { summary: 1200, logic: 800, relation: 800, focusCode: 1200 },
9608
+ auto: { summary: 800, logic: 600, relation: 500, focusCode: 1500 }
9609
+ };
9610
+ DedupeRegistry = class {
9611
+ seenSymbols = /* @__PURE__ */ new Set();
9612
+ seenFilePaths = /* @__PURE__ */ new Set();
9613
+ seenCallPairs = /* @__PURE__ */ new Set();
9614
+ logicSymbols = /* @__PURE__ */ new Set();
9615
+ // B.4.2: symbols referenced in LOGIC
9616
+ /** Returns full format on first mention, name-only on repeats. */
9617
+ formatSymbol(name, filePath, extra) {
9618
+ const key = name;
9619
+ if (this.seenSymbols.has(key)) return name;
9620
+ this.seenSymbols.add(key);
9621
+ this.seenFilePaths.add(filePath);
9622
+ return extra;
9623
+ }
9624
+ hasSymbol(name) {
9625
+ return this.seenSymbols.has(name);
9626
+ }
9627
+ markCallPair(caller, callee) {
9628
+ this.seenCallPairs.add(`${caller}\u2192${callee}`);
9629
+ }
9630
+ hasCallPair(caller, callee) {
9631
+ return this.seenCallPairs.has(`${caller}\u2192${callee}`);
9632
+ }
9633
+ hasFilePath(fp) {
9634
+ return this.seenFilePaths.has(fp);
9635
+ }
9636
+ /** Mark a symbol as referenced in the LOGIC block (B.4.2). */
9637
+ markInLogic(name) {
9638
+ this.logicSymbols.add(name);
9639
+ }
9640
+ /** Returns true only if symbol was referenced in LOGIC (B.4.2). */
9641
+ isInLogic(name) {
9642
+ return this.logicSymbols.has(name);
9643
+ }
9644
+ };
9645
+ }
9646
+ });
9647
+
9269
9648
  // src/cli/main.ts
9270
9649
  init_logger();
9271
9650
  init_knowledge_graph();
@@ -9763,7 +10142,7 @@ init_embedder();
9763
10142
  async function hybridSearch(graph, query, limit, options = {}) {
9764
10143
  const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
9765
10144
  const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
9766
- const hasVectorDb = Boolean(vectorDbPath && fs38.existsSync(vectorDbPath));
10145
+ const hasVectorDb = Boolean(vectorDbPath && fs39.existsSync(vectorDbPath));
9767
10146
  if (!hasVectorDb) {
9768
10147
  const bm25Results2 = await bm25Promise;
9769
10148
  return {
@@ -9831,7 +10210,7 @@ async function queryGroup(group, query, limit = 20) {
9831
10210
  const regEntry = registry.find((r) => r.name === member.registryName);
9832
10211
  if (!regEntry) continue;
9833
10212
  const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
9834
- if (!fs38.existsSync(dbPath)) continue;
10213
+ if (!fs39.existsSync(dbPath)) continue;
9835
10214
  const graph = createKnowledgeGraph();
9836
10215
  const db = new DbManager(dbPath, true);
9837
10216
  try {
@@ -9873,7 +10252,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
9873
10252
  var JobsDB = class {
9874
10253
  db;
9875
10254
  constructor(dbPath) {
9876
- fs38.mkdirSync(path39.dirname(dbPath), { recursive: true });
10255
+ fs39.mkdirSync(path39.dirname(dbPath), { recursive: true });
9877
10256
  this.db = new Database2(dbPath);
9878
10257
  this.db.pragma("journal_mode = WAL");
9879
10258
  this.db.pragma("foreign_keys = ON");
@@ -10141,7 +10520,7 @@ var BackupService = class {
10141
10520
  constructor(backupDir) {
10142
10521
  this.backupDir = backupDir ?? getBackupDir();
10143
10522
  this.key = getBackupKey();
10144
- fs38.mkdirSync(this.backupDir, { recursive: true });
10523
+ fs39.mkdirSync(this.backupDir, { recursive: true });
10145
10524
  }
10146
10525
  /**
10147
10526
  * Create a backup for a repository.
@@ -10155,16 +10534,16 @@ var BackupService = class {
10155
10534
  const candidates = ["graph.db", "vector.db", "meta.json"];
10156
10535
  for (const f of candidates) {
10157
10536
  const fp = path39.join(codeIntelDir, f);
10158
- if (fs38.existsSync(fp)) {
10537
+ if (fs39.existsSync(fp)) {
10159
10538
  filesToBackup.push({ name: f, localPath: fp });
10160
10539
  }
10161
10540
  }
10162
10541
  const registryPath = path39.join(os13.homedir(), ".code-intel", "registry.json");
10163
- if (fs38.existsSync(registryPath)) {
10542
+ if (fs39.existsSync(registryPath)) {
10164
10543
  filesToBackup.push({ name: "registry.json", localPath: registryPath });
10165
10544
  }
10166
10545
  const usersDbPath = path39.join(os13.homedir(), ".code-intel", "users.db");
10167
- if (fs38.existsSync(usersDbPath)) {
10546
+ if (fs39.existsSync(usersDbPath)) {
10168
10547
  filesToBackup.push({ name: "users.db", localPath: usersDbPath });
10169
10548
  }
10170
10549
  if (filesToBackup.length === 0) {
@@ -10175,7 +10554,7 @@ var BackupService = class {
10175
10554
  createdAt,
10176
10555
  version: BACKUP_VERSION,
10177
10556
  files: filesToBackup.map((f) => {
10178
- const data = fs38.readFileSync(f.localPath);
10557
+ const data = fs39.readFileSync(f.localPath);
10179
10558
  return {
10180
10559
  name: f.name,
10181
10560
  sha256: crypto5.createHash("sha256").update(data).digest("hex"),
@@ -10189,7 +10568,7 @@ var BackupService = class {
10189
10568
  manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
10190
10569
  parts.push(manifestLenBuf, manifestBuf);
10191
10570
  for (const f of filesToBackup) {
10192
- const data = fs38.readFileSync(f.localPath);
10571
+ const data = fs39.readFileSync(f.localPath);
10193
10572
  const nameBuf = Buffer.from(f.name, "utf-8");
10194
10573
  const nameLenBuf = Buffer.alloc(2);
10195
10574
  nameLenBuf.writeUInt16BE(nameBuf.length, 0);
@@ -10201,7 +10580,7 @@ var BackupService = class {
10201
10580
  const encrypted = encryptBuffer(plaintext, this.key);
10202
10581
  const backupFileName = `backup-${id}.cib`;
10203
10582
  const backupPath = path39.join(this.backupDir, backupFileName);
10204
- fs38.writeFileSync(backupPath, encrypted);
10583
+ fs39.writeFileSync(backupPath, encrypted);
10205
10584
  const entry = {
10206
10585
  id,
10207
10586
  createdAt,
@@ -10230,7 +10609,7 @@ var BackupService = class {
10230
10609
  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.");
10231
10610
  const fileName = path39.basename(entry.path);
10232
10611
  const s3Key = `${cfg.prefix}${fileName}`;
10233
- const body = fs38.readFileSync(entry.path);
10612
+ const body = fs39.readFileSync(entry.path);
10234
10613
  const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
10235
10614
  if (result.statusCode < 200 || result.statusCode >= 300) {
10236
10615
  throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
@@ -10247,8 +10626,8 @@ var BackupService = class {
10247
10626
  if (result.statusCode < 200 || result.statusCode >= 300) {
10248
10627
  throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
10249
10628
  }
10250
- fs38.mkdirSync(path39.dirname(destPath), { recursive: true });
10251
- fs38.writeFileSync(destPath, Buffer.from(result.body, "binary"));
10629
+ fs39.mkdirSync(path39.dirname(destPath), { recursive: true });
10630
+ fs39.writeFileSync(destPath, Buffer.from(result.body, "binary"));
10252
10631
  }
10253
10632
  /**
10254
10633
  * List backup objects in S3 with the configured prefix.
@@ -10294,10 +10673,10 @@ var BackupService = class {
10294
10673
  if (!entry) {
10295
10674
  throw new Error(`Backup "${backupId}" not found.`);
10296
10675
  }
10297
- if (!fs38.existsSync(entry.path)) {
10676
+ if (!fs39.existsSync(entry.path)) {
10298
10677
  throw new Error(`Backup file not found at: ${entry.path}`);
10299
10678
  }
10300
- const encrypted = fs38.readFileSync(entry.path);
10679
+ const encrypted = fs39.readFileSync(entry.path);
10301
10680
  let plaintext;
10302
10681
  try {
10303
10682
  plaintext = decryptBuffer(encrypted, this.key);
@@ -10312,7 +10691,7 @@ var BackupService = class {
10312
10691
  const manifest = JSON.parse(manifestStr);
10313
10692
  const restoreBase = targetRepoPath ?? entry.repoPath;
10314
10693
  const codeIntelDir = path39.join(restoreBase, ".code-intel");
10315
- fs38.mkdirSync(codeIntelDir, { recursive: true });
10694
+ fs39.mkdirSync(codeIntelDir, { recursive: true });
10316
10695
  for (const fileEntry of manifest.files) {
10317
10696
  const nameLen = plaintext.readUInt16BE(offset);
10318
10697
  offset += 2;
@@ -10333,14 +10712,14 @@ var BackupService = class {
10333
10712
  } else {
10334
10713
  destPath = path39.join(codeIntelDir, name);
10335
10714
  }
10336
- fs38.writeFileSync(destPath, data);
10715
+ fs39.writeFileSync(destPath, data);
10337
10716
  }
10338
10717
  }
10339
10718
  /**
10340
10719
  * Apply retention policy: keep N daily, M weekly, L monthly backups.
10341
10720
  */
10342
10721
  applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
10343
- const entries = this._loadIndex().filter((e) => fs38.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
10722
+ const entries = this._loadIndex().filter((e) => fs39.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
10344
10723
  const keep = /* @__PURE__ */ new Set();
10345
10724
  const now = /* @__PURE__ */ new Date();
10346
10725
  const dailyCutoff = new Date(now);
@@ -10370,7 +10749,7 @@ var BackupService = class {
10370
10749
  for (const e of entries) {
10371
10750
  if (!keep.has(e.id)) {
10372
10751
  try {
10373
- fs38.unlinkSync(e.path);
10752
+ fs39.unlinkSync(e.path);
10374
10753
  deleted++;
10375
10754
  } catch {
10376
10755
  }
@@ -10386,13 +10765,13 @@ var BackupService = class {
10386
10765
  }
10387
10766
  _loadIndex() {
10388
10767
  try {
10389
- return JSON.parse(fs38.readFileSync(this._indexPath(), "utf-8"));
10768
+ return JSON.parse(fs39.readFileSync(this._indexPath(), "utf-8"));
10390
10769
  } catch {
10391
10770
  return [];
10392
10771
  }
10393
10772
  }
10394
10773
  _saveIndex(entries) {
10395
- fs38.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
10774
+ fs39.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
10396
10775
  }
10397
10776
  _appendIndex(entry) {
10398
10777
  const entries = this._loadIndex();
@@ -11036,7 +11415,7 @@ var openApiSpec = {
11036
11415
  var __dirname$1 = path39.dirname(fileURLToPath(import.meta.url));
11037
11416
  var WEB_DIST = (() => {
11038
11417
  const bundled = path39.resolve(__dirname$1, "..", "web");
11039
- if (fs38.existsSync(bundled)) return bundled;
11418
+ if (fs39.existsSync(bundled)) return bundled;
11040
11419
  return path39.resolve(__dirname$1, "..", "..", "..", "web", "dist");
11041
11420
  })();
11042
11421
  function getAllowedOrigins() {
@@ -11121,8 +11500,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11121
11500
  const metaFilePath = path39.join(workspaceRoot, ".code-intel", "meta.json");
11122
11501
  let metaOk = false;
11123
11502
  try {
11124
- if (fs38.existsSync(metaFilePath)) {
11125
- const raw = fs38.readFileSync(metaFilePath, "utf-8");
11503
+ if (fs39.existsSync(metaFilePath)) {
11504
+ const raw = fs39.readFileSync(metaFilePath, "utf-8");
11126
11505
  const meta = JSON.parse(raw);
11127
11506
  if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
11128
11507
  }
@@ -11313,12 +11692,12 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11313
11692
  return;
11314
11693
  }
11315
11694
  const user = db.createUser(username, password, "admin");
11316
- const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
11317
- res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
11695
+ const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
11696
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
11318
11697
  res.status(201).json({ user: { id: user.id, username: user.username, role: user.role } });
11319
11698
  });
11320
11699
  app.post("/auth/login", async (req, res) => {
11321
- const { username, password } = req.body;
11700
+ const { username, password, rememberMe } = req.body;
11322
11701
  if (!username || !password) {
11323
11702
  res.status(400).json({
11324
11703
  error: {
@@ -11362,10 +11741,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11362
11741
  });
11363
11742
  return;
11364
11743
  }
11365
- const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
11744
+ const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role }, rememberMe === true);
11366
11745
  db.logAccess(user.id, "/auth/login", "login", "allow", req.ip ?? "unknown");
11367
11746
  authAttemptsTotal.inc({ method: "local", outcome: "success" });
11368
- res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
11747
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
11369
11748
  res.json({ user: { id: user.id, username: user.username, role: user.role } });
11370
11749
  });
11371
11750
  app.post("/auth/logout", (req, res) => {
@@ -11487,9 +11866,9 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11487
11866
  authAttemptsTotal.inc({ method: "oidc", outcome: "success" });
11488
11867
  logger_default.info(`[oidc] Auto-provisioned new user: ${finalUsername} (${cfg.defaultRole})`);
11489
11868
  }
11490
- const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
11869
+ const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
11491
11870
  db.logAccess(user.id, "/auth/callback", "oidc-login", "allow", req.ip ?? "unknown");
11492
- res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
11871
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
11493
11872
  res.redirect(302, "/");
11494
11873
  } catch (err) {
11495
11874
  logger_default.warn("[oidc] Callback failed:", err instanceof Error ? err.message : err);
@@ -11607,7 +11986,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11607
11986
  const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
11608
11987
  if (!entry) return null;
11609
11988
  const dbPath = path39.join(entry.path, ".code-intel", "graph.db");
11610
- if (!fs38.existsSync(dbPath)) return null;
11989
+ if (!fs39.existsSync(dbPath)) return null;
11611
11990
  const repoGraph = createKnowledgeGraph();
11612
11991
  const db = new DbManager(dbPath, true);
11613
11992
  try {
@@ -11629,7 +12008,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11629
12008
  const regEntry = registry.find((r) => r.name === member.registryName);
11630
12009
  if (!regEntry) continue;
11631
12010
  const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
11632
- if (!fs38.existsSync(dbPath)) continue;
12011
+ if (!fs39.existsSync(dbPath)) continue;
11633
12012
  const db = new DbManager(dbPath, true);
11634
12013
  try {
11635
12014
  await db.init();
@@ -11711,7 +12090,21 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11711
12090
  });
11712
12091
  });
11713
12092
  app.post("/api/v1/search", requireToolScope("search"), async (req, res) => {
11714
- const { query, limit, repo } = req.body;
12093
+ const { query, limit, repo, group } = req.body;
12094
+ if (group) {
12095
+ const grp = loadGroup(group);
12096
+ if (!grp) {
12097
+ res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: `Group '${group}' not found`, hint: "Use /api/v1/groups to list available groups" } });
12098
+ return;
12099
+ }
12100
+ try {
12101
+ const { perRepo, merged } = await queryGroup(grp, query ?? "", limit ?? 20);
12102
+ res.json({ results: merged, perRepo, searchMode: "bm25", group });
12103
+ } catch (err) {
12104
+ res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
12105
+ }
12106
+ return;
12107
+ }
11715
12108
  const g = await getGraphForRepo(repo);
11716
12109
  const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
11717
12110
  const bm25 = !repo || repo === repoName ? ensureBm25Index() : null;
@@ -11720,7 +12113,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11720
12113
  vectorDbPath: vdbPath,
11721
12114
  bm25Results: bm25Results ?? void 0
11722
12115
  });
11723
- res.json({ results, searchMode });
12116
+ res.json({ results, searchMode, repo: repo ?? repoName });
11724
12117
  });
11725
12118
  app.post("/api/v1/vector-search", async (req, res) => {
11726
12119
  const { query, limit = 10 } = req.body;
@@ -11762,7 +12155,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
11762
12155
  return;
11763
12156
  }
11764
12157
  try {
11765
- const content = fs38.readFileSync(file_path, "utf-8");
12158
+ const content = fs39.readFileSync(file_path, "utf-8");
11766
12159
  res.json({ content });
11767
12160
  } catch {
11768
12161
  res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
@@ -12041,7 +12434,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12041
12434
  const regEntry = registry.find((r) => r.name === member.registryName);
12042
12435
  if (!regEntry) continue;
12043
12436
  const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
12044
- if (!fs38.existsSync(dbPath)) continue;
12437
+ if (!fs39.existsSync(dbPath)) continue;
12045
12438
  const db = new DbManager(dbPath, true);
12046
12439
  try {
12047
12440
  await db.init();
@@ -12068,7 +12461,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12068
12461
  let edgeCount = 0;
12069
12462
  if (regEntry) {
12070
12463
  const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
12071
- if (fs38.existsSync(dbPath)) {
12464
+ if (fs39.existsSync(dbPath)) {
12072
12465
  try {
12073
12466
  const db = new DbManager(dbPath, true);
12074
12467
  await db.init();
@@ -12131,7 +12524,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12131
12524
  const regEntry = registry.find((r) => r.name === member.registryName);
12132
12525
  if (!regEntry) continue;
12133
12526
  const candidate = path39.resolve(path39.join(regEntry.path, normalizedFile));
12134
- if (fs38.existsSync(candidate)) {
12527
+ if (fs39.existsSync(candidate)) {
12135
12528
  baseDir = regEntry.path;
12136
12529
  break;
12137
12530
  }
@@ -12183,7 +12576,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12183
12576
  }
12184
12577
  let fileContent;
12185
12578
  try {
12186
- fileContent = fs38.readFileSync(resolvedFile, "utf-8");
12579
+ fileContent = fs39.readFileSync(resolvedFile, "utf-8");
12187
12580
  } catch {
12188
12581
  res.status(404).json({
12189
12582
  error: {
@@ -12349,7 +12742,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
12349
12742
  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() } });
12350
12743
  }
12351
12744
  });
12352
- if (fs38.existsSync(WEB_DIST)) {
12745
+ if (fs39.existsSync(WEB_DIST)) {
12353
12746
  app.use(express.static(WEB_DIST));
12354
12747
  app.get("/{*path}", (_req, res) => {
12355
12748
  res.sendFile(path39.join(WEB_DIST, "index.html"));
@@ -12503,6 +12896,7 @@ async function startHttpServer(graph, repoName, port = 4747, workspaceRoot, watc
12503
12896
  });
12504
12897
  });
12505
12898
  }
12899
+ init_bm25_index();
12506
12900
  init_storage();
12507
12901
  init_repo_registry();
12508
12902
  init_metadata();
@@ -13029,11 +13423,30 @@ function summarizeCluster(graph, cluster) {
13029
13423
  }
13030
13424
 
13031
13425
  // src/mcp-server/server.ts
13426
+ function compact(obj) {
13427
+ return JSON.stringify(obj, (_key, value) => value === null || value === void 0 ? void 0 : value);
13428
+ }
13032
13429
  function createMcpServer(graph, repoName, workspaceRoot) {
13033
13430
  const server = new Server(
13034
13431
  { name: "code-intel", version: "0.1.0" },
13035
13432
  { capabilities: { tools: {}, resources: {} } }
13036
13433
  );
13434
+ let bm25Index = null;
13435
+ function ensureBm25Index() {
13436
+ if (bm25Index) return bm25Index;
13437
+ if (!workspaceRoot) return null;
13438
+ try {
13439
+ const idx = new Bm25Index(getBm25DbPath(workspaceRoot));
13440
+ idx.load();
13441
+ bm25Index = idx;
13442
+ return bm25Index;
13443
+ } catch {
13444
+ return null;
13445
+ }
13446
+ }
13447
+ if (workspaceRoot) {
13448
+ setImmediate(() => ensureBm25Index());
13449
+ }
13037
13450
  const _tokenProp = {
13038
13451
  _token: { type: "string", description: "Required if CODE_INTEL_TOKEN is configured" }
13039
13452
  };
@@ -13053,13 +13466,15 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13053
13466
  // ── Search & inspect ─────────────────────────────────────────────────
13054
13467
  {
13055
13468
  name: "search",
13056
- description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc.",
13469
+ description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc. Optionally scope to a specific repo or group.",
13057
13470
  inputSchema: {
13058
13471
  type: "object",
13059
13472
  properties: {
13060
13473
  query: { type: "string", description: "Search query (symbol name, keyword, or partial match)" },
13061
13474
  offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
13062
- limit: { type: "number", description: "Max results per page (default: 50, max: 500)" },
13475
+ limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
13476
+ repo: { type: "string", description: "Scope search to a specific indexed repo name (optional; defaults to current repo)" },
13477
+ group: { type: "string", description: "Scope search across all repos in a group via cross-repo RRF merge (optional; overrides repo)" },
13063
13478
  ..._tokenProp
13064
13479
  },
13065
13480
  required: ["query"]
@@ -13089,7 +13504,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13089
13504
  enum: ["callers", "callees", "both"],
13090
13505
  description: "Which direction to trace \u2014 callers (who depends on it), callees (what it depends on), or both (default: both)"
13091
13506
  },
13092
- max_hops: { type: "number", description: "Maximum traversal depth (default: 5)" },
13507
+ max_hops: { type: "number", description: "Maximum traversal depth (default: 2, max: 10)" },
13093
13508
  ..._tokenProp
13094
13509
  },
13095
13510
  required: ["target"]
@@ -13103,7 +13518,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13103
13518
  properties: {
13104
13519
  file_path: { type: "string", description: 'File path (partial match is supported, e.g. "auth/login.ts")' },
13105
13520
  offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
13106
- limit: { type: "number", description: "Max results per page (default: 50, max: 500)" },
13521
+ limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
13107
13522
  ..._tokenProp
13108
13523
  },
13109
13524
  required: ["file_path"]
@@ -13134,7 +13549,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13134
13549
  description: "Filter by node kind: function | class | interface | method | type_alias | constant | enum (optional)"
13135
13550
  },
13136
13551
  offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
13137
- limit: { type: "number", description: "Max results per page (default: 50, max: 500)" },
13552
+ limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
13138
13553
  ..._tokenProp
13139
13554
  }
13140
13555
  }
@@ -13152,7 +13567,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13152
13567
  type: "object",
13153
13568
  properties: {
13154
13569
  offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
13155
- limit: { type: "number", description: "Max clusters per page (default: 50, max: 500)" },
13570
+ limit: { type: "number", description: "Max clusters per page (default: 10, max: 500)" },
13156
13571
  ..._tokenProp
13157
13572
  }
13158
13573
  }
@@ -13164,7 +13579,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13164
13579
  type: "object",
13165
13580
  properties: {
13166
13581
  offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
13167
- limit: { type: "number", description: "Max flows per page (default: 50, max: 500)" },
13582
+ limit: { type: "number", description: "Max flows per page (default: 10, max: 500)" },
13168
13583
  ..._tokenProp
13169
13584
  }
13170
13585
  }
@@ -13318,7 +13733,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13318
13733
  },
13319
13734
  maxHops: {
13320
13735
  type: "number",
13321
- description: "Maximum BFS depth for blast radius (default: 5)"
13736
+ description: "Maximum BFS depth for blast radius (default: 2, max: 10)"
13322
13737
  },
13323
13738
  ..._tokenProp
13324
13739
  }
@@ -13446,13 +13861,13 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13446
13861
  const providedToken = a._token;
13447
13862
  if (providedToken !== expectedToken) {
13448
13863
  return {
13449
- content: [{ type: "text", text: JSON.stringify({ error: "Unauthorized: invalid or missing CODE_INTEL_TOKEN" }) }],
13864
+ content: [{ type: "text", text: compact({ error: "Unauthorized: invalid or missing CODE_INTEL_TOKEN" }) }],
13450
13865
  isError: true
13451
13866
  };
13452
13867
  }
13453
13868
  }
13454
13869
  const startMs = Date.now();
13455
- const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot);
13870
+ const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot, ensureBm25Index);
13456
13871
  const MCP_TIMEOUT_MS = parseInt(process.env["CODE_INTEL_MCP_TIMEOUT_MS"] ?? "30000", 10);
13457
13872
  let timeoutHandle = null;
13458
13873
  let timedOut = false;
@@ -13484,7 +13899,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13484
13899
  mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
13485
13900
  if (timedOut) {
13486
13901
  return {
13487
- content: [{ type: "text", text: JSON.stringify({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
13902
+ content: [{ type: "text", text: compact({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
13488
13903
  isError: false
13489
13904
  };
13490
13905
  }
@@ -13499,7 +13914,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
13499
13914
  registerResources(server, graph, repoName);
13500
13915
  return server;
13501
13916
  }
13502
- async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13917
+ async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolver) {
13503
13918
  switch (name) {
13504
13919
  // ── repos ──────────────────────────────────────────────────────────────
13505
13920
  case "repos": {
@@ -13507,10 +13922,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13507
13922
  return {
13508
13923
  content: [{
13509
13924
  type: "text",
13510
- text: JSON.stringify(
13511
- registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats })),
13512
- null,
13513
- 2
13925
+ text: compact(
13926
+ registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
13514
13927
  )
13515
13928
  }]
13516
13929
  };
@@ -13538,13 +13951,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13538
13951
  return {
13539
13952
  content: [{
13540
13953
  type: "text",
13541
- text: JSON.stringify({
13954
+ text: compact({
13542
13955
  repo: repoName,
13543
13956
  stats: graph.size,
13544
13957
  nodeCounts: kindCounts,
13545
13958
  edgeCounts,
13546
13959
  health
13547
- }, null, 2)
13960
+ })
13548
13961
  }]
13549
13962
  };
13550
13963
  }
@@ -13552,15 +13965,59 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13552
13965
  case "search": {
13553
13966
  const query = a.query;
13554
13967
  const offset = a.offset ?? 0;
13555
- const effectiveLimit = Math.min(a.limit ?? 50, 500);
13968
+ const effectiveLimit = Math.min(a.limit ?? 10, 500);
13969
+ if (a.group) {
13970
+ const grp = loadGroup(a.group);
13971
+ if (!grp) {
13972
+ return { content: [{ type: "text", text: `Group "${a.group}" not found. Use list_groups to see available groups.` }] };
13973
+ }
13974
+ const { perRepo, merged } = await queryGroup(grp, query, effectiveLimit + offset);
13975
+ const paged = merged.slice(offset, offset + effectiveLimit);
13976
+ return {
13977
+ content: [{
13978
+ type: "text",
13979
+ text: compact({
13980
+ results: paged,
13981
+ perRepo,
13982
+ searchMode: "bm25-cross-repo",
13983
+ group: a.group,
13984
+ total: merged.length,
13985
+ offset,
13986
+ limit: effectiveLimit,
13987
+ hasMore: offset + effectiveLimit < merged.length
13988
+ })
13989
+ }]
13990
+ };
13991
+ }
13992
+ const repoGraph = a.repo ? await (async () => {
13993
+ const registry = loadRegistry();
13994
+ const entry = registry.find((r) => r.name === a.repo || r.path === a.repo);
13995
+ if (!entry) return graph;
13996
+ const { DbManager: DbMgr } = await Promise.resolve().then(() => (init_db_manager(), db_manager_exports));
13997
+ const { loadGraphFromDB: loadG } = await Promise.resolve().then(() => (init_graph_from_db(), graph_from_db_exports));
13998
+ const { createKnowledgeGraph: createG } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
13999
+ const dbPath = path39.join(entry.path, ".code-intel", "graph.db");
14000
+ if (!fs39.existsSync(dbPath)) return graph;
14001
+ const db = new DbMgr(dbPath, true);
14002
+ await db.init();
14003
+ const g = createG();
14004
+ await loadG(g, db);
14005
+ db.close();
14006
+ return g;
14007
+ })() : graph;
13556
14008
  const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
13557
14009
  const fetchLimit = Math.min(offset + effectiveLimit, 500);
13558
- const { results: allResults, searchMode } = await hybridSearch(graph, query, fetchLimit, { vectorDbPath: vdbPath });
14010
+ const bm25 = !a.repo || a.repo === repoName ? bm25Resolver ? bm25Resolver() : null : null;
14011
+ const bm25Results = bm25 ? bm25.search(query, fetchLimit * 3) : void 0;
14012
+ const { results: allResults, searchMode } = await hybridSearch(repoGraph, query, fetchLimit, {
14013
+ vectorDbPath: vdbPath,
14014
+ bm25Results: bm25Results ?? void 0
14015
+ });
13559
14016
  const total = allResults.length;
13560
14017
  const results = allResults.slice(offset, offset + effectiveLimit);
13561
14018
  const hasMore = offset + effectiveLimit < total;
13562
14019
  const suggestNextTools = [];
13563
- const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] !== "false";
14020
+ const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
13564
14021
  if (suggestEnabled && results.length > 0) {
13565
14022
  const topName = results[0].name;
13566
14023
  suggestNextTools.push(
@@ -13571,15 +14028,16 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13571
14028
  return {
13572
14029
  content: [{
13573
14030
  type: "text",
13574
- text: JSON.stringify({
14031
+ text: compact({
13575
14032
  results,
13576
14033
  searchMode,
14034
+ repo: a.repo ?? repoName,
13577
14035
  total,
13578
14036
  offset,
13579
14037
  limit: effectiveLimit,
13580
14038
  hasMore,
13581
14039
  ...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
13582
- }, null, 2)
14040
+ })
13583
14041
  }]
13584
14042
  };
13585
14043
  }
@@ -13601,7 +14059,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13601
14059
  file: graph.getNode(e.target)?.filePath
13602
14060
  }));
13603
14061
  const cluster = incoming.filter((e) => e.kind === "belongs_to").map((e) => graph.getNode(e.target)?.name)[0];
13604
- const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] !== "false";
14062
+ const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
13605
14063
  const suggestNextTools = [];
13606
14064
  if (suggestEnabled) {
13607
14065
  const topCallerName = callers[0]?.name;
@@ -13613,7 +14071,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13613
14071
  return {
13614
14072
  content: [{
13615
14073
  type: "text",
13616
- text: JSON.stringify({
14074
+ text: compact({
13617
14075
  node: {
13618
14076
  id: node.id,
13619
14077
  kind: node.kind,
@@ -13636,7 +14094,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13636
14094
  cluster,
13637
14095
  content: node.content?.slice(0, 500),
13638
14096
  ...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
13639
- }, null, 2)
14097
+ })
13640
14098
  }]
13641
14099
  };
13642
14100
  }
@@ -13644,7 +14102,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13644
14102
  case "blast_radius": {
13645
14103
  const target = a.target;
13646
14104
  const direction = a.direction ?? "both";
13647
- const maxHops = a.max_hops ?? 5;
14105
+ const maxHops = a.max_hops ?? 2;
13648
14106
  const node = findNodeByName(graph, target);
13649
14107
  if (!node) return { content: [{ type: "text", text: `Symbol "${target}" not found.` }] };
13650
14108
  const affected = /* @__PURE__ */ new Set();
@@ -13671,7 +14129,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13671
14129
  return n ? { id, name: n.name, kind: n.kind, filePath: n.filePath } : { id };
13672
14130
  });
13673
14131
  const risk = affected.size > 10 ? "HIGH" : affected.size > 5 ? "MEDIUM" : "LOW";
13674
- const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] !== "false";
14132
+ const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
13675
14133
  const suggestNextTools = [];
13676
14134
  if (suggestEnabled) {
13677
14135
  const highestRiskSymbol = node.name;
@@ -13684,13 +14142,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13684
14142
  return {
13685
14143
  content: [{
13686
14144
  type: "text",
13687
- text: JSON.stringify({
14145
+ text: compact({
13688
14146
  target: node.name,
13689
14147
  affectedCount: affected.size,
13690
14148
  riskLevel: risk,
13691
14149
  affected: affectedDetails,
13692
14150
  ...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
13693
- }, null, 2)
14151
+ })
13694
14152
  }]
13695
14153
  };
13696
14154
  }
@@ -13698,7 +14156,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13698
14156
  case "file_symbols": {
13699
14157
  const filePath = a.file_path;
13700
14158
  const offset = a.offset ?? 0;
13701
- const effectiveLimit = Math.min(a.limit ?? 50, 500);
14159
+ const effectiveLimit = Math.min(a.limit ?? 10, 500);
13702
14160
  const allMatches = [];
13703
14161
  for (const node of graph.allNodes()) {
13704
14162
  if (node.filePath && node.filePath.includes(filePath)) {
@@ -13715,7 +14173,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13715
14173
  return {
13716
14174
  content: [{
13717
14175
  type: "text",
13718
- text: JSON.stringify({ symbols: matches, total, offset, limit: effectiveLimit, hasMore }, null, 2)
14176
+ text: compact({ symbols: matches, total, offset, limit: effectiveLimit, hasMore })
13719
14177
  }]
13720
14178
  };
13721
14179
  }
@@ -13756,7 +14214,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13756
14214
  return {
13757
14215
  content: [{
13758
14216
  type: "text",
13759
- text: JSON.stringify({ from: fromName, to: toName, hops: foundPath.length - 1, path: pathDetails }, null, 2)
14217
+ text: compact({ from: fromName, to: toName, hops: foundPath.length - 1, path: pathDetails })
13760
14218
  }]
13761
14219
  };
13762
14220
  }
@@ -13764,7 +14222,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13764
14222
  case "list_exports": {
13765
14223
  const kindFilter = a.kind;
13766
14224
  const offset = a.offset ?? 0;
13767
- const effectiveLimit = Math.min(a.limit ?? 50, 500);
14225
+ const effectiveLimit = Math.min(a.limit ?? 10, 500);
13768
14226
  const allExports = [];
13769
14227
  for (const node of graph.allNodes()) {
13770
14228
  if (!node.exported) continue;
@@ -13777,7 +14235,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13777
14235
  return {
13778
14236
  content: [{
13779
14237
  type: "text",
13780
- text: JSON.stringify({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore }, null, 2)
14238
+ text: compact({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore })
13781
14239
  }]
13782
14240
  };
13783
14241
  }
@@ -13789,12 +14247,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13789
14247
  routes.push({ name: node.name, filePath: node.filePath, startLine: node.startLine });
13790
14248
  }
13791
14249
  }
13792
- return { content: [{ type: "text", text: JSON.stringify(routes, null, 2) }] };
14250
+ return { content: [{ type: "text", text: compact(routes) }] };
13793
14251
  }
13794
14252
  // ── clusters ───────────────────────────────────────────────────────────
13795
14253
  case "clusters": {
13796
14254
  const offset = a.offset ?? 0;
13797
- const effectiveLimit = Math.min(a.limit ?? 50, 500);
14255
+ const effectiveLimit = Math.min(a.limit ?? 10, 500);
13798
14256
  const allClusters = [];
13799
14257
  for (const node of graph.allNodes()) {
13800
14258
  if (node.kind === "cluster") {
@@ -13821,14 +14279,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13821
14279
  return {
13822
14280
  content: [{
13823
14281
  type: "text",
13824
- text: JSON.stringify({ clusters, total, offset, limit: effectiveLimit, hasMore }, null, 2)
14282
+ text: compact({ clusters, total, offset, limit: effectiveLimit, hasMore })
13825
14283
  }]
13826
14284
  };
13827
14285
  }
13828
14286
  // ── flows ──────────────────────────────────────────────────────────────
13829
14287
  case "flows": {
13830
14288
  const offset = a.offset ?? 0;
13831
- const effectiveLimit = Math.min(a.limit ?? 50, 500);
14289
+ const effectiveLimit = Math.min(a.limit ?? 10, 500);
13832
14290
  const allFlows = [];
13833
14291
  for (const node of graph.allNodes()) {
13834
14292
  if (node.kind === "flow") {
@@ -13848,7 +14306,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13848
14306
  return {
13849
14307
  content: [{
13850
14308
  type: "text",
13851
- text: JSON.stringify({ flows, total, offset, limit: effectiveLimit, hasMore }, null, 2)
14309
+ text: compact({ flows, total, offset, limit: effectiveLimit, hasMore })
13852
14310
  }]
13853
14311
  };
13854
14312
  }
@@ -13916,14 +14374,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13916
14374
  return {
13917
14375
  content: [{
13918
14376
  type: "text",
13919
- text: JSON.stringify({
14377
+ text: compact({
13920
14378
  baseRef,
13921
14379
  changedFiles: changedFiles.map((f) => f.filePath),
13922
14380
  directlyChangedSymbols: changedSymbols,
13923
14381
  transitivelyAffectedSymbols: affectedSymbols,
13924
14382
  totalAffected: allAffected.size,
13925
14383
  riskLevel: risk
13926
- }, null, 2)
14384
+ })
13927
14385
  }]
13928
14386
  };
13929
14387
  }
@@ -13931,14 +14389,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13931
14389
  case "query": {
13932
14390
  const gqlInput = a.gql;
13933
14391
  if (!gqlInput) {
13934
- return { content: [{ type: "text", text: JSON.stringify({ error: "Missing required parameter: gql" }) }], isError: true };
14392
+ return { content: [{ type: "text", text: compact({ error: "Missing required parameter: gql" }) }], isError: true };
13935
14393
  }
13936
14394
  const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
13937
14395
  const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
13938
14396
  const ast = parseGQL2(gqlInput);
13939
14397
  if (isGQLParseError2(ast)) {
13940
14398
  return {
13941
- content: [{ type: "text", text: JSON.stringify({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
14399
+ content: [{ type: "text", text: compact({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
13942
14400
  isError: true
13943
14401
  };
13944
14402
  }
@@ -13949,7 +14407,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13949
14407
  return {
13950
14408
  content: [{
13951
14409
  type: "text",
13952
- text: JSON.stringify({
14410
+ text: compact({
13953
14411
  nodes: result.nodes,
13954
14412
  edges: result.edges,
13955
14413
  groups: result.groups,
@@ -13957,7 +14415,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13957
14415
  executionTimeMs: result.executionTimeMs,
13958
14416
  truncated: result.truncated,
13959
14417
  totalCount: result.totalCount
13960
- }, null, 2)
14418
+ })
13961
14419
  }]
13962
14420
  };
13963
14421
  }
@@ -13971,7 +14429,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13971
14429
  for (const node of graph.allNodes()) {
13972
14430
  if (node.name === nameMatch[1]) results.push(node);
13973
14431
  }
13974
- return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
14432
+ return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
13975
14433
  }
13976
14434
  const kindMatch = q?.match(/:\s*(\w+)/);
13977
14435
  if (kindMatch) {
@@ -13980,9 +14438,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13980
14438
  if (node.kind === kindMatch[1]) results.push(node);
13981
14439
  if (results.length >= 50) break;
13982
14440
  }
13983
- return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
14441
+ return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
13984
14442
  }
13985
- 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." }) }] };
14443
+ 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." }) }] };
13986
14444
  }
13987
14445
  // ── group_list ─────────────────────────────────────────────────────────
13988
14446
  case "group_list": {
@@ -13990,16 +14448,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
13990
14448
  if (groupName) {
13991
14449
  const group = loadGroup(groupName);
13992
14450
  if (!group) return { content: [{ type: "text", text: `Group "${groupName}" not found.` }] };
13993
- return { content: [{ type: "text", text: JSON.stringify(group, null, 2) }] };
14451
+ return { content: [{ type: "text", text: compact(group) }] };
13994
14452
  }
13995
14453
  const groups = listGroups();
13996
14454
  return {
13997
14455
  content: [{
13998
14456
  type: "text",
13999
- text: JSON.stringify(
14000
- groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members })),
14001
- null,
14002
- 2
14457
+ text: compact(
14458
+ groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members }))
14003
14459
  )
14004
14460
  }]
14005
14461
  };
@@ -14017,14 +14473,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14017
14473
  return {
14018
14474
  content: [{
14019
14475
  type: "text",
14020
- text: JSON.stringify({
14476
+ text: compact({
14021
14477
  groupName: result.groupName,
14022
14478
  syncedAt: result.syncedAt,
14023
14479
  memberCount: result.memberCount,
14024
14480
  contractCount: result.contracts.length,
14025
14481
  linkCount: result.links.length,
14026
14482
  topLinks: result.links.slice(0, 20)
14027
- }, null, 2)
14483
+ })
14028
14484
  }]
14029
14485
  };
14030
14486
  }
@@ -14044,7 +14500,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14044
14500
  return {
14045
14501
  content: [{
14046
14502
  type: "text",
14047
- text: JSON.stringify({ syncedAt: result.syncedAt, contracts, links }, null, 2)
14503
+ text: compact({ syncedAt: result.syncedAt, contracts, links })
14048
14504
  }]
14049
14505
  };
14050
14506
  }
@@ -14059,7 +14515,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14059
14515
  return {
14060
14516
  content: [{
14061
14517
  type: "text",
14062
- text: JSON.stringify({ query, merged, perRepo }, null, 2)
14518
+ text: compact({ query, merged, perRepo })
14063
14519
  }]
14064
14520
  };
14065
14521
  }
@@ -14091,12 +14547,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14091
14547
  return {
14092
14548
  content: [{
14093
14549
  type: "text",
14094
- text: JSON.stringify({
14550
+ text: compact({
14095
14551
  group: groupName,
14096
14552
  lastSync: group.lastSync ?? null,
14097
14553
  syncAgeMinutes: syncAge,
14098
14554
  members: memberStatus
14099
- }, null, 2)
14555
+ })
14100
14556
  }]
14101
14557
  };
14102
14558
  }
@@ -14105,11 +14561,11 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14105
14561
  const fromName = a.from;
14106
14562
  const toName = a.to;
14107
14563
  const result = explainRelationship(graph, fromName, toName);
14108
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14564
+ return { content: [{ type: "text", text: compact(result) }] };
14109
14565
  }
14110
14566
  // ── pr_impact ──────────────────────────────────────────────────────────
14111
14567
  case "pr_impact": {
14112
- const maxHops = a.maxHops ?? 5;
14568
+ const maxHops = a.maxHops ?? 2;
14113
14569
  let changedFiles = a.changedFiles ?? [];
14114
14570
  if (a.diff && typeof a.diff === "string") {
14115
14571
  const diffFiles = parseDiffFiles(a.diff);
@@ -14119,37 +14575,37 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14119
14575
  return {
14120
14576
  content: [{
14121
14577
  type: "text",
14122
- text: JSON.stringify({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
14578
+ text: compact({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
14123
14579
  }]
14124
14580
  };
14125
14581
  }
14126
14582
  const result = computePRImpact(graph, changedFiles, maxHops);
14127
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14583
+ return { content: [{ type: "text", text: compact(result) }] };
14128
14584
  }
14129
14585
  // ── similar_symbols ────────────────────────────────────────────────────
14130
14586
  case "similar_symbols": {
14131
14587
  const symbolName = a.symbol;
14132
14588
  const limit = a.limit ?? 10;
14133
14589
  const result = findSimilarSymbols(graph, symbolName, limit);
14134
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14590
+ return { content: [{ type: "text", text: compact(result) }] };
14135
14591
  }
14136
14592
  // ── health_report ──────────────────────────────────────────────────────
14137
14593
  case "health_report": {
14138
14594
  const scope = a.scope ?? ".";
14139
14595
  const result = computeHealthReport(graph, scope);
14140
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14596
+ return { content: [{ type: "text", text: compact(result) }] };
14141
14597
  }
14142
14598
  // ── suggest_tests ──────────────────────────────────────────────────────
14143
14599
  case "suggest_tests": {
14144
14600
  const sym = a.symbol;
14145
14601
  const result = suggestTests(graph, sym);
14146
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14602
+ return { content: [{ type: "text", text: compact(result) }] };
14147
14603
  }
14148
14604
  // ── cluster_summary ────────────────────────────────────────────────────
14149
14605
  case "cluster_summary": {
14150
14606
  const cluster = a.cluster;
14151
14607
  const result = summarizeCluster(graph, cluster);
14152
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14608
+ return { content: [{ type: "text", text: compact(result) }] };
14153
14609
  }
14154
14610
  case "deprecated_usage": {
14155
14611
  const scope = a.scope;
@@ -14157,7 +14613,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14157
14613
  const detector = new DeprecatedDetector2();
14158
14614
  detector.tagDeprecated(graph);
14159
14615
  const findings = detector.detect(graph, scope);
14160
- return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
14616
+ return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
14161
14617
  }
14162
14618
  // ── complexity_hotspots ────────────────────────────────────────────────
14163
14619
  case "complexity_hotspots": {
@@ -14165,7 +14621,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14165
14621
  const scope = a.scope;
14166
14622
  const limit = typeof a.limit === "number" ? a.limit : 20;
14167
14623
  const hotspots = computeComplexity2(graph, scope).slice(0, limit);
14168
- return { content: [{ type: "text", text: JSON.stringify({ hotspots, total: hotspots.length }, null, 2) }] };
14624
+ return { content: [{ type: "text", text: compact({ hotspots, total: hotspots.length }) }] };
14169
14625
  }
14170
14626
  // ── coverage_gaps ──────────────────────────────────────────────────────
14171
14627
  case "coverage_gaps": {
@@ -14177,12 +14633,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14177
14633
  return {
14178
14634
  content: [{
14179
14635
  type: "text",
14180
- text: JSON.stringify({
14636
+ text: compact({
14181
14637
  untestedByRisk,
14182
14638
  coveragePct: summary.coveragePct,
14183
14639
  totalExported: summary.totalExported,
14184
14640
  testedExported: summary.testedExported
14185
- }, null, 2)
14641
+ })
14186
14642
  }]
14187
14643
  };
14188
14644
  }
@@ -14193,7 +14649,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14193
14649
  const scope = a.scope;
14194
14650
  const includeTestFiles = a.includeTestFiles ?? false;
14195
14651
  const findings = scanner.scan(graph, { scope, includeTestFiles });
14196
- return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
14652
+ return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
14197
14653
  }
14198
14654
  // ── vulnerability_scan ─────────────────────────────────────────────────
14199
14655
  case "vulnerability_scan": {
@@ -14206,7 +14662,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
14206
14662
  const minRank = sevRank[minSev] ?? 1;
14207
14663
  let findings = detector.detect(graph, { scope, types });
14208
14664
  findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
14209
- return { content: [{ type: "text", text: JSON.stringify({ findings, total: findings.length }, null, 2) }] };
14665
+ return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
14210
14666
  }
14211
14667
  default:
14212
14668
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
@@ -14227,21 +14683,21 @@ function registerResources(server, graph, repoName) {
14227
14683
  for (const node of graph.allNodes()) {
14228
14684
  kindCounts[node.kind] = (kindCounts[node.kind] ?? 0) + 1;
14229
14685
  }
14230
- return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
14686
+ return { contents: [{ uri, mimeType: "application/json", text: compact({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
14231
14687
  }
14232
14688
  if (uri.endsWith("/clusters")) {
14233
14689
  const clusters = [];
14234
14690
  for (const node of graph.allNodes()) {
14235
14691
  if (node.kind === "cluster") clusters.push({ id: node.id, name: node.name, memberCount: node.metadata?.memberCount });
14236
14692
  }
14237
- return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(clusters) }] };
14693
+ return { contents: [{ uri, mimeType: "application/json", text: compact(clusters) }] };
14238
14694
  }
14239
14695
  if (uri.endsWith("/flows")) {
14240
14696
  const flows = [];
14241
14697
  for (const node of graph.allNodes()) {
14242
14698
  if (node.kind === "flow") flows.push({ id: node.id, name: node.name, steps: node.metadata?.steps, entryPoint: node.metadata?.entryPoint });
14243
14699
  }
14244
- return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(flows) }] };
14700
+ return { contents: [{ uri, mimeType: "application/json", text: compact(flows) }] };
14245
14701
  }
14246
14702
  throw new Error(`Unknown resource: ${uri}`);
14247
14703
  });
@@ -14297,8 +14753,8 @@ async function writeSkillFiles(graph, workspaceRoot, projectName) {
14297
14753
  const outputDir = path39.join(workspaceRoot, ".claude", "skills", "code-intel");
14298
14754
  const areas = buildAreaMap(graph, workspaceRoot);
14299
14755
  if (areas.length === 0) return { skills: [], outputDir };
14300
- fs38.rmSync(outputDir, { recursive: true, force: true });
14301
- fs38.mkdirSync(outputDir, { recursive: true });
14756
+ fs39.rmSync(outputDir, { recursive: true, force: true });
14757
+ fs39.mkdirSync(outputDir, { recursive: true });
14302
14758
  const skills = [];
14303
14759
  const usedNames = /* @__PURE__ */ new Set();
14304
14760
  for (const area of areas) {
@@ -14306,8 +14762,8 @@ async function writeSkillFiles(graph, workspaceRoot, projectName) {
14306
14762
  usedNames.add(kebab);
14307
14763
  const content = renderSkill(area, projectName, kebab);
14308
14764
  const dir = path39.join(outputDir, kebab);
14309
- fs38.mkdirSync(dir, { recursive: true });
14310
- fs38.writeFileSync(path39.join(dir, "SKILL.md"), content, "utf-8");
14765
+ fs39.mkdirSync(dir, { recursive: true });
14766
+ fs39.writeFileSync(path39.join(dir, "SKILL.md"), content, "utf-8");
14311
14767
  skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
14312
14768
  }
14313
14769
  return { skills, outputDir };
@@ -14490,13 +14946,13 @@ function writeContextFiles(workspaceRoot, projectName, stats, skills) {
14490
14946
  upsertFile(path39.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
14491
14947
  upsertFile(path39.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
14492
14948
  const githubDir = path39.join(workspaceRoot, ".github");
14493
- if (!fs38.existsSync(githubDir)) fs38.mkdirSync(githubDir, { recursive: true });
14949
+ if (!fs39.existsSync(githubDir)) fs39.mkdirSync(githubDir, { recursive: true });
14494
14950
  upsertFile(path39.join(githubDir, "copilot-instructions.md"), block, "copilot-instructions.md");
14495
14951
  const cursorDir = path39.join(workspaceRoot, ".cursor", "rules");
14496
- if (!fs38.existsSync(cursorDir)) fs38.mkdirSync(cursorDir, { recursive: true });
14952
+ if (!fs39.existsSync(cursorDir)) fs39.mkdirSync(cursorDir, { recursive: true });
14497
14953
  upsertFile(path39.join(cursorDir, "code-intel.mdc"), block, "code-intel.mdc");
14498
14954
  const kiroDir = path39.join(workspaceRoot, ".kiro", "steering");
14499
- if (!fs38.existsSync(kiroDir)) fs38.mkdirSync(kiroDir, { recursive: true });
14955
+ if (!fs39.existsSync(kiroDir)) fs39.mkdirSync(kiroDir, { recursive: true });
14500
14956
  upsertFile(path39.join(kiroDir, "code-intel.md"), block, "code-intel.md");
14501
14957
  }
14502
14958
  function buildBlock(projectName, stats, skills) {
@@ -14523,6 +14979,14 @@ ${skills.map((s) => `- Working in **${s.label}**? \u2192 Load \`.claude/skills/c
14523
14979
  > \u26A0 This section is auto-managed by \`code-intel analyze\`. Do **not** edit between the markers \u2014 your changes will be overwritten.
14524
14980
  > Add your own notes below the \`${BLOCK_END}\` marker.
14525
14981
 
14982
+ <!-- TOOL POLICY: ENFORCED -->
14983
+ FORBIDDEN: grep, rg, find, cat, sed, ls (for symbol/code discovery)
14984
+ REQUIRED BEFORE ANY CODE ACTION:
14985
+ 1. code-intel search "<concept>" \u2190 replaces grep/rg/find
14986
+ 2. code-intel inspect <symbol> \u2190 replaces cat + manual file reading
14987
+ 3. code-intel impact <symbol> \u2190 replaces manual caller tracing
14988
+ <!-- /TOOL POLICY -->
14989
+
14526
14990
  Indexed: **${stats.nodes.toLocaleString()} nodes** | **${stats.edges.toLocaleString()} edges** | **${stats.files} files** | analyzed in ${(stats.duration / 1e3).toFixed(1)}s
14527
14991
 
14528
14992
  > Index stale? Re-run: \`code-intel analyze\`
@@ -14558,7 +15022,9 @@ These rules apply to **every coding agent or AI assistant** working in this repo
14558
15022
  ## Never Do
14559
15023
 
14560
15024
  - NEVER ignore impact warnings \u2014 always report blast radius to the user.
14561
- - NEVER skip \`code-intel search\` before grepping or opening files.
15025
+ - **STOP** \u2014 do not call grep, rg, find, cat, sed, or read a file cold.
15026
+ Always run \`code-intel search "<concept>"\` first.
15027
+ Violating this wastes ~3,000 tokens per lookup and degrades session quality.
14562
15028
  - NEVER make changes to a symbol with \u2265 5 callers without running \`code-intel impact\` first.
14563
15029
  - NEVER use find-and-replace for symbol renames.
14564
15030
 
@@ -14634,7 +15100,7 @@ ${skillTable}
14634
15100
  ${BLOCK_END}`;
14635
15101
  }
14636
15102
  function upsertFile(filePath, block, fileName) {
14637
- if (!fs38.existsSync(filePath)) {
15103
+ if (!fs39.existsSync(filePath)) {
14638
15104
  const newContent = [
14639
15105
  `# ${fileName}`,
14640
15106
  "",
@@ -14645,17 +15111,17 @@ function upsertFile(filePath, block, fileName) {
14645
15111
  "<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
14646
15112
  ""
14647
15113
  ].join("\n");
14648
- fs38.writeFileSync(filePath, newContent, "utf-8");
15114
+ fs39.writeFileSync(filePath, newContent, "utf-8");
14649
15115
  return;
14650
15116
  }
14651
- const existing = fs38.readFileSync(filePath, "utf-8");
15117
+ const existing = fs39.readFileSync(filePath, "utf-8");
14652
15118
  const startIdx = findLineMarker(existing, BLOCK_START);
14653
15119
  const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
14654
15120
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
14655
15121
  const before = existing.slice(0, startIdx);
14656
15122
  const after = existing.slice(endIdx + BLOCK_END.length);
14657
15123
  const updated = (before + block + after).trimEnd() + "\n";
14658
- fs38.writeFileSync(filePath, updated, "utf-8");
15124
+ fs39.writeFileSync(filePath, updated, "utf-8");
14659
15125
  return;
14660
15126
  }
14661
15127
  const appended = [
@@ -14668,7 +15134,7 @@ function upsertFile(filePath, block, fileName) {
14668
15134
  block,
14669
15135
  ""
14670
15136
  ].join("\n");
14671
- fs38.writeFileSync(filePath, appended, "utf-8");
15137
+ fs39.writeFileSync(filePath, appended, "utf-8");
14672
15138
  }
14673
15139
  function findLineMarker(content, marker, startFrom = 0) {
14674
15140
  let idx = content.indexOf(marker, startFrom);
@@ -14717,7 +15183,7 @@ function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
14717
15183
  continue;
14718
15184
  }
14719
15185
  try {
14720
- const { mtimeMs } = fs38.statSync(absPath);
15186
+ const { mtimeMs } = fs39.statSync(absPath);
14721
15187
  if (mtimeMs > stored) changed.push(absPath);
14722
15188
  } catch {
14723
15189
  changed.push(absPath);
@@ -14729,7 +15195,7 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
14729
15195
  const snap = {};
14730
15196
  for (const absPath of filePaths) {
14731
15197
  try {
14732
- const { mtimeMs } = fs38.statSync(absPath);
15198
+ const { mtimeMs } = fs39.statSync(absPath);
14733
15199
  snap[path39.relative(workspaceRoot, absPath)] = mtimeMs;
14734
15200
  } catch {
14735
15201
  }
@@ -14770,10 +15236,10 @@ function expandGlob(root, pattern) {
14770
15236
  const parts = pattern.replace(/\/\*\*?$/, "").split("/").filter(Boolean);
14771
15237
  if (parts.length === 0) return [];
14772
15238
  const dir = path39.join(root, ...parts);
14773
- if (!fs38.existsSync(dir)) return [];
14774
- return fs38.readdirSync(dir).map((entry) => path39.join(dir, entry)).filter((p) => {
15239
+ if (!fs39.existsSync(dir)) return [];
15240
+ return fs39.readdirSync(dir).map((entry) => path39.join(dir, entry)).filter((p) => {
14775
15241
  try {
14776
- return fs38.statSync(p).isDirectory();
15242
+ return fs39.statSync(p).isDirectory();
14777
15243
  } catch {
14778
15244
  return false;
14779
15245
  }
@@ -14785,9 +15251,9 @@ function resolvePackages(root, patterns) {
14785
15251
  const dirs = expandGlob(root, pattern);
14786
15252
  for (const dir of dirs) {
14787
15253
  const pkgJsonPath = path39.join(dir, "package.json");
14788
- if (!fs38.existsSync(pkgJsonPath)) continue;
15254
+ if (!fs39.existsSync(pkgJsonPath)) continue;
14789
15255
  try {
14790
- const pkgJson = JSON.parse(fs38.readFileSync(pkgJsonPath, "utf-8"));
15256
+ const pkgJson = JSON.parse(fs39.readFileSync(pkgJsonPath, "utf-8"));
14791
15257
  const name = pkgJson.name ?? path39.basename(dir);
14792
15258
  packages.push({ name, path: dir });
14793
15259
  } catch {
@@ -14798,12 +15264,12 @@ function resolvePackages(root, patterns) {
14798
15264
  }
14799
15265
  async function detectWorkspace(root) {
14800
15266
  const turboJsonPath = path39.join(root, "turbo.json");
14801
- if (fs38.existsSync(turboJsonPath)) {
15267
+ if (fs39.existsSync(turboJsonPath)) {
14802
15268
  let patterns = [];
14803
15269
  const pkgJsonPath = path39.join(root, "package.json");
14804
- if (fs38.existsSync(pkgJsonPath)) {
15270
+ if (fs39.existsSync(pkgJsonPath)) {
14805
15271
  try {
14806
- const pkgJson = JSON.parse(fs38.readFileSync(pkgJsonPath, "utf-8"));
15272
+ const pkgJson = JSON.parse(fs39.readFileSync(pkgJsonPath, "utf-8"));
14807
15273
  if (pkgJson.workspaces) {
14808
15274
  patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
14809
15275
  }
@@ -14812,15 +15278,15 @@ async function detectWorkspace(root) {
14812
15278
  }
14813
15279
  if (patterns.length === 0) {
14814
15280
  const fallbackDir = path39.join(root, "packages");
14815
- if (fs38.existsSync(fallbackDir)) patterns = ["packages/*"];
15281
+ if (fs39.existsSync(fallbackDir)) patterns = ["packages/*"];
14816
15282
  }
14817
15283
  const packages = resolvePackages(root, patterns);
14818
15284
  return { type: "turborepo", root, packages };
14819
15285
  }
14820
15286
  const npmPkgJsonPath = path39.join(root, "package.json");
14821
- if (fs38.existsSync(npmPkgJsonPath)) {
15287
+ if (fs39.existsSync(npmPkgJsonPath)) {
14822
15288
  try {
14823
- const pkgJson = JSON.parse(fs38.readFileSync(npmPkgJsonPath, "utf-8"));
15289
+ const pkgJson = JSON.parse(fs39.readFileSync(npmPkgJsonPath, "utf-8"));
14824
15290
  if (pkgJson.workspaces) {
14825
15291
  const patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
14826
15292
  const packages = resolvePackages(root, patterns);
@@ -14830,10 +15296,10 @@ async function detectWorkspace(root) {
14830
15296
  }
14831
15297
  }
14832
15298
  const pnpmYamlPath = path39.join(root, "pnpm-workspace.yaml");
14833
- if (fs38.existsSync(pnpmYamlPath)) {
15299
+ if (fs39.existsSync(pnpmYamlPath)) {
14834
15300
  const patterns = [];
14835
15301
  try {
14836
- const content = fs38.readFileSync(pnpmYamlPath, "utf-8");
15302
+ const content = fs39.readFileSync(pnpmYamlPath, "utf-8");
14837
15303
  let inPackages = false;
14838
15304
  for (const line of content.split("\n")) {
14839
15305
  if (/^packages\s*:/.test(line)) {
@@ -14854,13 +15320,13 @@ async function detectWorkspace(root) {
14854
15320
  return { type: "pnpm", root, packages };
14855
15321
  }
14856
15322
  const nxJsonPath = path39.join(root, "nx.json");
14857
- if (fs38.existsSync(nxJsonPath)) {
15323
+ if (fs39.existsSync(nxJsonPath)) {
14858
15324
  const packages = [];
14859
15325
  const scanForProjects = (dir, depth) => {
14860
15326
  if (depth > 2) return;
14861
15327
  let entries;
14862
15328
  try {
14863
- entries = fs38.readdirSync(dir);
15329
+ entries = fs39.readdirSync(dir);
14864
15330
  } catch {
14865
15331
  return;
14866
15332
  }
@@ -14868,14 +15334,14 @@ async function detectWorkspace(root) {
14868
15334
  if (entry === "node_modules" || entry.startsWith(".")) continue;
14869
15335
  const fullPath = path39.join(dir, entry);
14870
15336
  try {
14871
- if (!fs38.statSync(fullPath).isDirectory()) continue;
15337
+ if (!fs39.statSync(fullPath).isDirectory()) continue;
14872
15338
  } catch {
14873
15339
  continue;
14874
15340
  }
14875
15341
  const projectJsonPath = path39.join(fullPath, "project.json");
14876
- if (fs38.existsSync(projectJsonPath)) {
15342
+ if (fs39.existsSync(projectJsonPath)) {
14877
15343
  try {
14878
- const proj = JSON.parse(fs38.readFileSync(projectJsonPath, "utf-8"));
15344
+ const proj = JSON.parse(fs39.readFileSync(projectJsonPath, "utf-8"));
14879
15345
  const name = proj.name ?? path39.basename(fullPath);
14880
15346
  packages.push({ name, path: fullPath });
14881
15347
  } catch {
@@ -14984,9 +15450,9 @@ var MigrationRunner = class {
14984
15450
  autoBackupBeforeMigration() {
14985
15451
  try {
14986
15452
  const dbFile = this.db.name;
14987
- if (!dbFile || !fs38.existsSync(dbFile)) return;
15453
+ if (!dbFile || !fs39.existsSync(dbFile)) return;
14988
15454
  const backupDir = path39.join(os13.homedir(), ".code-intel", "backups", "pre-migration");
14989
- fs38.mkdirSync(backupDir, { recursive: true });
15455
+ fs39.mkdirSync(backupDir, { recursive: true });
14990
15456
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
14991
15457
  const baseName = path39.basename(dbFile, ".db");
14992
15458
  const backupPath = path39.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
@@ -14994,7 +15460,7 @@ var MigrationRunner = class {
14994
15460
  this.db.pragma("wal_checkpoint(TRUNCATE)");
14995
15461
  } catch {
14996
15462
  }
14997
- fs38.copyFileSync(dbFile, backupPath);
15463
+ fs39.copyFileSync(dbFile, backupPath);
14998
15464
  } catch {
14999
15465
  }
15000
15466
  }
@@ -15144,7 +15610,7 @@ init_codes();
15144
15610
  var GLOBAL_DIR3 = path39.join(os13.homedir(), ".code-intel");
15145
15611
  function loadRepoPaths() {
15146
15612
  try {
15147
- const data = fs38.readFileSync(path39.join(GLOBAL_DIR3, "repos.json"), "utf-8");
15613
+ const data = fs39.readFileSync(path39.join(GLOBAL_DIR3, "repos.json"), "utf-8");
15148
15614
  const repos = JSON.parse(data);
15149
15615
  return repos.map((r) => r.path);
15150
15616
  } catch {
@@ -15154,7 +15620,7 @@ function loadRepoPaths() {
15154
15620
  function loadGroupNames() {
15155
15621
  const groupsDir = path39.join(GLOBAL_DIR3, "groups");
15156
15622
  try {
15157
- return fs38.readdirSync(groupsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
15623
+ return fs39.readdirSync(groupsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
15158
15624
  } catch {
15159
15625
  return [];
15160
15626
  }
@@ -15417,8 +15883,8 @@ function autoInstallCompletion() {
15417
15883
  if (shell === "fish") {
15418
15884
  const dir = path39.join(os13.homedir(), ".config", "fish", "completions");
15419
15885
  const dest = path39.join(dir, "code-intel.fish");
15420
- fs38.mkdirSync(dir, { recursive: true });
15421
- fs38.writeFileSync(dest, fishCompletion(), "utf-8");
15886
+ fs39.mkdirSync(dir, { recursive: true });
15887
+ fs39.writeFileSync(dest, fishCompletion(), "utf-8");
15422
15888
  console.log(` \u2705 Fish completion installed \u2192 ${dest}
15423
15889
  `);
15424
15890
  return;
@@ -15430,13 +15896,13 @@ source <(code-intel completion bash)
15430
15896
  `;
15431
15897
  const rcFile = shell === "zsh" ? path39.join(os13.homedir(), ".zshrc") : path39.join(os13.homedir(), ".bashrc");
15432
15898
  try {
15433
- const existing = fs38.existsSync(rcFile) ? fs38.readFileSync(rcFile, "utf-8") : "";
15899
+ const existing = fs39.existsSync(rcFile) ? fs39.readFileSync(rcFile, "utf-8") : "";
15434
15900
  if (existing.includes("code-intel completion")) {
15435
15901
  console.log(` \u2139 Completion already configured in ${rcFile}
15436
15902
  `);
15437
15903
  return;
15438
15904
  }
15439
- fs38.appendFileSync(rcFile, script, "utf-8");
15905
+ fs39.appendFileSync(rcFile, script, "utf-8");
15440
15906
  console.log(` \u2705 ${shell} completion added to ${rcFile}`);
15441
15907
  console.log(` Restart your shell or run: source ${rcFile}
15442
15908
  `);
@@ -15461,14 +15927,14 @@ var PACKAGE_NAME = "code-intel";
15461
15927
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
15462
15928
  function loadMeta() {
15463
15929
  try {
15464
- return JSON.parse(fs38.readFileSync(META_PATH, "utf-8"));
15930
+ return JSON.parse(fs39.readFileSync(META_PATH, "utf-8"));
15465
15931
  } catch {
15466
15932
  return null;
15467
15933
  }
15468
15934
  }
15469
15935
  function saveMeta(meta) {
15470
- fs38.mkdirSync(GLOBAL_DIR4, { recursive: true });
15471
- fs38.writeFileSync(META_PATH, JSON.stringify(meta, null, 2) + "\n", "utf-8");
15936
+ fs39.mkdirSync(GLOBAL_DIR4, { recursive: true });
15937
+ fs39.writeFileSync(META_PATH, JSON.stringify(meta, null, 2) + "\n", "utf-8");
15472
15938
  }
15473
15939
  function isNewer(current, candidate) {
15474
15940
  const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
@@ -15688,14 +16154,37 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
15688
16154
 
15689
16155
  Docs: https://github.com/vohongtho/code-intel-platform
15690
16156
  `);
16157
+ function ensureGitignore(workspaceRoot, silent) {
16158
+ const gitignorePath = path39.join(workspaceRoot, ".gitignore");
16159
+ const entry = ".code-intel/";
16160
+ try {
16161
+ let existing = "";
16162
+ if (fs39.existsSync(gitignorePath)) {
16163
+ existing = fs39.readFileSync(gitignorePath, "utf-8");
16164
+ }
16165
+ const lines = existing.split("\n").map((l) => l.trim());
16166
+ if (lines.includes(".code-intel/") || lines.includes(".code-intel")) {
16167
+ return;
16168
+ }
16169
+ const suffix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
16170
+ fs39.appendFileSync(gitignorePath, `${suffix}${entry}
16171
+ `, "utf-8");
16172
+ logger_default.info(".gitignore updated: added .code-intel/");
16173
+ if (!silent) {
16174
+ console.log(` \u2713 .gitignore: added .code-intel/`);
16175
+ }
16176
+ } catch (err) {
16177
+ logger_default.warn(`.gitignore update failed: ${err instanceof Error ? err.message : err}`);
16178
+ }
16179
+ }
15691
16180
  async function analyzeWorkspace(targetPath, options) {
15692
16181
  const workspaceRoot = path39.resolve(targetPath);
15693
- if (!fs38.existsSync(workspaceRoot)) {
16182
+ if (!fs39.existsSync(workspaceRoot)) {
15694
16183
  logger_default.error(`Path does not exist: ${workspaceRoot}`);
15695
16184
  console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
15696
16185
  process.exit(1);
15697
16186
  }
15698
- if (!fs38.statSync(workspaceRoot).isDirectory()) {
16187
+ if (!fs39.statSync(workspaceRoot).isDirectory()) {
15699
16188
  logger_default.error(`Path is not a directory: ${workspaceRoot}`);
15700
16189
  console.error(` \u2717 Path is not a directory: ${workspaceRoot}`);
15701
16190
  process.exit(1);
@@ -15720,14 +16209,14 @@ async function analyzeWorkspace(targetPath, options) {
15720
16209
  ];
15721
16210
  for (const f of wipeFiles) {
15722
16211
  try {
15723
- if (fs38.existsSync(f)) fs38.unlinkSync(f);
16212
+ if (fs39.existsSync(f)) fs39.unlinkSync(f);
15724
16213
  } catch {
15725
16214
  }
15726
16215
  }
15727
16216
  }
15728
16217
  if (!options?.skipGit) {
15729
16218
  const gitDir = path39.join(workspaceRoot, ".git");
15730
- if (!fs38.existsSync(gitDir)) {
16219
+ if (!fs39.existsSync(gitDir)) {
15731
16220
  logger_default.warn(`${workspaceRoot} is not a Git repository`);
15732
16221
  }
15733
16222
  }
@@ -15866,8 +16355,8 @@ async function analyzeWorkspace(targetPath, options) {
15866
16355
  };
15867
16356
  const profilePath = path39.join(workspaceRoot, ".code-intel", "profile.json");
15868
16357
  try {
15869
- fs38.mkdirSync(path39.join(workspaceRoot, ".code-intel"), { recursive: true });
15870
- fs38.writeFileSync(profilePath, JSON.stringify(profileJson, null, 2));
16358
+ fs39.mkdirSync(path39.join(workspaceRoot, ".code-intel"), { recursive: true });
16359
+ fs39.writeFileSync(profilePath, JSON.stringify(profileJson, null, 2));
15871
16360
  if (!options?.silent) console.log(` \u2713 Profile written: ${profilePath}`);
15872
16361
  } catch (err) {
15873
16362
  logger_default.warn(`Failed to write profile.json: ${err instanceof Error ? err.message : err}`);
@@ -15894,7 +16383,7 @@ async function analyzeWorkspace(targetPath, options) {
15894
16383
  }
15895
16384
  if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
15896
16385
  const dbPath = getDbPath(workspaceRoot);
15897
- if (fs38.existsSync(dbPath)) {
16386
+ if (fs39.existsSync(dbPath)) {
15898
16387
  try {
15899
16388
  const db = new DbManager(dbPath);
15900
16389
  await db.init();
@@ -15967,7 +16456,7 @@ async function analyzeWorkspace(targetPath, options) {
15967
16456
  ];
15968
16457
  for (const f of newStaleFiles) {
15969
16458
  try {
15970
- if (fs38.existsSync(f)) fs38.unlinkSync(f);
16459
+ if (fs39.existsSync(f)) fs39.unlinkSync(f);
15971
16460
  } catch {
15972
16461
  }
15973
16462
  }
@@ -15984,21 +16473,21 @@ async function analyzeWorkspace(targetPath, options) {
15984
16473
  ];
15985
16474
  for (const f of staleFiles) {
15986
16475
  try {
15987
- if (fs38.existsSync(f)) fs38.unlinkSync(f);
16476
+ if (fs39.existsSync(f)) fs39.unlinkSync(f);
15988
16477
  } catch {
15989
16478
  }
15990
16479
  }
15991
16480
  for (const f of newStaleFiles) {
15992
16481
  if (f === dbPathNew) continue;
15993
- if (fs38.existsSync(f)) {
16482
+ if (fs39.existsSync(f)) {
15994
16483
  const dest = f.replace(dbPathNew, dbPath);
15995
16484
  try {
15996
- fs38.renameSync(f, dest);
16485
+ fs39.renameSync(f, dest);
15997
16486
  } catch {
15998
16487
  }
15999
16488
  }
16000
16489
  }
16001
- fs38.renameSync(dbPathNew, dbPath);
16490
+ fs39.renameSync(dbPathNew, dbPath);
16002
16491
  stopSpinner();
16003
16492
  logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
16004
16493
  if (!options?.silent) {
@@ -16030,7 +16519,7 @@ async function analyzeWorkspace(targetPath, options) {
16030
16519
  const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
16031
16520
  for (const f of staleVdb) {
16032
16521
  try {
16033
- if (fs38.existsSync(f)) fs38.unlinkSync(f);
16522
+ if (fs39.existsSync(f)) fs39.unlinkSync(f);
16034
16523
  } catch {
16035
16524
  }
16036
16525
  }
@@ -16093,6 +16582,7 @@ async function analyzeWorkspace(targetPath, options) {
16093
16582
  logger_default.warn(`Context file write failed: ${err instanceof Error ? err.message : err}`);
16094
16583
  }
16095
16584
  }
16585
+ ensureGitignore(workspaceRoot, options?.silent ?? false);
16096
16586
  if (!options?.silent) {
16097
16587
  const dur = result.totalDuration;
16098
16588
  const durStr = dur >= 1e3 ? `${(dur / 1e3).toFixed(1)}s` : `${dur}ms`;
@@ -16247,8 +16737,8 @@ program.command("setup").description("Configure MCP server for your editors (one
16247
16737
  const configFile = `${configDir}/claude_desktop_config.json`;
16248
16738
  try {
16249
16739
  let existing = {};
16250
- if (fs38.existsSync(configFile)) {
16251
- existing = JSON.parse(fs38.readFileSync(configFile, "utf-8"));
16740
+ if (fs39.existsSync(configFile)) {
16741
+ existing = JSON.parse(fs39.readFileSync(configFile, "utf-8"));
16252
16742
  }
16253
16743
  const merged = {
16254
16744
  ...existing,
@@ -16257,8 +16747,8 @@ program.command("setup").description("Configure MCP server for your editors (one
16257
16747
  ...mcpConfig.mcpServers
16258
16748
  }
16259
16749
  };
16260
- fs38.mkdirSync(configDir, { recursive: true });
16261
- fs38.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
16750
+ fs39.mkdirSync(configDir, { recursive: true });
16751
+ fs39.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
16262
16752
  console.log(`
16263
16753
  \u2705 Written to ${configFile}`);
16264
16754
  } catch (err) {
@@ -16360,11 +16850,11 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
16360
16850
  const workspaceRoot = path39.resolve(targetPath);
16361
16851
  const repoName = path39.basename(workspaceRoot);
16362
16852
  const dbPath = getDbPath(workspaceRoot);
16363
- if (!fs38.existsSync(workspaceRoot) || !fs38.statSync(workspaceRoot).isDirectory()) {
16853
+ if (!fs39.existsSync(workspaceRoot) || !fs39.statSync(workspaceRoot).isDirectory()) {
16364
16854
  console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16365
16855
  process.exit(1);
16366
16856
  }
16367
- const existingIndex = fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16857
+ const existingIndex = fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16368
16858
  if (existingIndex) {
16369
16859
  const graph = createKnowledgeGraph();
16370
16860
  const db = new DbManager(dbPath, true);
@@ -16398,11 +16888,11 @@ program.command("serve").description("Start the local HTTP server + web UI for g
16398
16888
  const workspaceRoot = path39.resolve(targetPath);
16399
16889
  const repoName = path39.basename(workspaceRoot);
16400
16890
  const dbPath = getDbPath(workspaceRoot);
16401
- if (!fs38.existsSync(workspaceRoot) || !fs38.statSync(workspaceRoot).isDirectory()) {
16891
+ if (!fs39.existsSync(workspaceRoot) || !fs39.statSync(workspaceRoot).isDirectory()) {
16402
16892
  console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16403
16893
  process.exit(1);
16404
16894
  }
16405
- const existingIndex = !options.force && fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16895
+ const existingIndex = !options.force && fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16406
16896
  if (existingIndex) {
16407
16897
  const meta = loadMetadata(workspaceRoot);
16408
16898
  if (meta.parser === "regex" || meta.parser === void 0) {
@@ -16440,11 +16930,11 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
16440
16930
  const workspaceRoot = path39.resolve(targetPath);
16441
16931
  const repoName = path39.basename(workspaceRoot);
16442
16932
  const dbPath = getDbPath(workspaceRoot);
16443
- if (!fs38.existsSync(workspaceRoot) || !fs38.statSync(workspaceRoot).isDirectory()) {
16933
+ if (!fs39.existsSync(workspaceRoot) || !fs39.statSync(workspaceRoot).isDirectory()) {
16444
16934
  console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
16445
16935
  process.exit(1);
16446
16936
  }
16447
- const existingIndex = !options.force && fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16937
+ const existingIndex = !options.force && fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16448
16938
  let graph;
16449
16939
  if (existingIndex) {
16450
16940
  const meta = loadMetadata(workspaceRoot);
@@ -16564,16 +17054,16 @@ function trashDirName(repoPath) {
16564
17054
  }
16565
17055
  function softDeleteCodeIntel(repoPath) {
16566
17056
  const codeIntelDir = path39.join(repoPath, ".code-intel");
16567
- if (!fs38.existsSync(codeIntelDir)) return;
17057
+ if (!fs39.existsSync(codeIntelDir)) return;
16568
17058
  const trashName = trashDirName();
16569
17059
  const trashDir = path39.join(repoPath, trashName);
16570
17060
  let dest = trashDir;
16571
17061
  let counter = 1;
16572
- while (fs38.existsSync(dest)) {
17062
+ while (fs39.existsSync(dest)) {
16573
17063
  dest = `${trashDir}-${counter++}`;
16574
17064
  }
16575
- fs38.renameSync(codeIntelDir, dest);
16576
- fs38.writeFileSync(
17065
+ fs39.renameSync(codeIntelDir, dest);
17066
+ fs39.writeFileSync(
16577
17067
  path39.join(dest, "TRASH_META.json"),
16578
17068
  JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
16579
17069
  );
@@ -16583,15 +17073,15 @@ function softDeleteCodeIntel(repoPath) {
16583
17073
  function purgeStaleTrashes(repoPath) {
16584
17074
  const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
16585
17075
  try {
16586
- for (const entry of fs38.readdirSync(repoPath)) {
17076
+ for (const entry of fs39.readdirSync(repoPath)) {
16587
17077
  if (!entry.startsWith(".code-intel-trash-")) continue;
16588
17078
  const fullPath = path39.join(repoPath, entry);
16589
17079
  const metaPath = path39.join(fullPath, "TRASH_META.json");
16590
- if (fs38.existsSync(metaPath)) {
17080
+ if (fs39.existsSync(metaPath)) {
16591
17081
  try {
16592
- const meta = JSON.parse(fs38.readFileSync(metaPath, "utf-8"));
17082
+ const meta = JSON.parse(fs39.readFileSync(metaPath, "utf-8"));
16593
17083
  if (new Date(meta.deletedAt).getTime() < cutoff) {
16594
- fs38.rmSync(fullPath, { recursive: true, force: true });
17084
+ fs39.rmSync(fullPath, { recursive: true, force: true });
16595
17085
  console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
16596
17086
  }
16597
17087
  } catch {
@@ -16618,18 +17108,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
16618
17108
  if (opts.dryRun) {
16619
17109
  const showDryRun = (repoPath) => {
16620
17110
  const codeIntelDir = path39.join(path39.resolve(repoPath), ".code-intel");
16621
- if (!fs38.existsSync(codeIntelDir)) {
17111
+ if (!fs39.existsSync(codeIntelDir)) {
16622
17112
  console.log(` (no .code-intel/ found at ${repoPath})`);
16623
17113
  return;
16624
17114
  }
16625
17115
  let totalBytes = 0;
16626
17116
  const countDir = (dir) => {
16627
17117
  try {
16628
- for (const entry of fs38.readdirSync(dir, { withFileTypes: true })) {
17118
+ for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
16629
17119
  const full = path39.join(dir, entry.name);
16630
17120
  if (entry.isDirectory()) countDir(full);
16631
17121
  else try {
16632
- totalBytes += fs38.statSync(full).size;
17122
+ totalBytes += fs39.statSync(full).size;
16633
17123
  } catch {
16634
17124
  }
16635
17125
  }
@@ -16666,14 +17156,14 @@ program.command("clean").description("Soft-delete the knowledge graph index for
16666
17156
  let found = 0;
16667
17157
  for (const root of roots) {
16668
17158
  try {
16669
- for (const entry of fs38.readdirSync(root)) {
17159
+ for (const entry of fs39.readdirSync(root)) {
16670
17160
  if (!entry.startsWith(".code-intel-trash-")) continue;
16671
17161
  const fullPath = path39.join(root, entry);
16672
17162
  const metaPath = path39.join(fullPath, "TRASH_META.json");
16673
17163
  let deletedAt = "unknown";
16674
- if (fs38.existsSync(metaPath)) {
17164
+ if (fs39.existsSync(metaPath)) {
16675
17165
  try {
16676
- deletedAt = JSON.parse(fs38.readFileSync(metaPath, "utf-8")).deletedAt;
17166
+ deletedAt = JSON.parse(fs39.readFileSync(metaPath, "utf-8")).deletedAt;
16677
17167
  } catch {
16678
17168
  }
16679
17169
  }
@@ -16702,8 +17192,8 @@ program.command("clean").description("Soft-delete the knowledge graph index for
16702
17192
  for (const r of repos) {
16703
17193
  if (opts.purge) {
16704
17194
  const codeIntelDir = path39.join(r.path, ".code-intel");
16705
- if (fs38.existsSync(codeIntelDir)) {
16706
- fs38.rmSync(codeIntelDir, { recursive: true, force: true });
17195
+ if (fs39.existsSync(codeIntelDir)) {
17196
+ fs39.rmSync(codeIntelDir, { recursive: true, force: true });
16707
17197
  console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
16708
17198
  }
16709
17199
  } else {
@@ -16720,8 +17210,8 @@ program.command("clean").description("Soft-delete the knowledge graph index for
16720
17210
  const workspaceRoot = path39.resolve(targetPath);
16721
17211
  if (opts.purge) {
16722
17212
  const codeIntelDir = path39.join(workspaceRoot, ".code-intel");
16723
- if (fs38.existsSync(codeIntelDir)) {
16724
- fs38.rmSync(codeIntelDir, { recursive: true, force: true });
17213
+ if (fs39.existsSync(codeIntelDir)) {
17214
+ fs39.rmSync(codeIntelDir, { recursive: true, force: true });
16725
17215
  console.log(`
16726
17216
  \u2713 Hard-deleted ${codeIntelDir}`);
16727
17217
  }
@@ -16735,7 +17225,7 @@ program.command("clean").description("Soft-delete the knowledge graph index for
16735
17225
  async function loadOrAnalyzeWorkspace(targetPath) {
16736
17226
  const workspaceRoot = path39.resolve(targetPath);
16737
17227
  const dbPath = getDbPath(workspaceRoot);
16738
- const existingIndex = fs38.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
17228
+ const existingIndex = fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
16739
17229
  if (existingIndex) {
16740
17230
  const graph = createKnowledgeGraph();
16741
17231
  const db = new DbManager(dbPath, true);
@@ -17223,7 +17713,7 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
17223
17713
  }
17224
17714
  const metaPath = path39.join(regEntry.path, ".code-intel", "meta.json");
17225
17715
  try {
17226
- const meta = JSON.parse(fs38.readFileSync(metaPath, "utf-8"));
17716
+ const meta = JSON.parse(fs39.readFileSync(metaPath, "utf-8"));
17227
17717
  const indexedAt = meta.indexedAt;
17228
17718
  const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
17229
17719
  const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
@@ -17557,7 +18047,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
17557
18047
  Backups (${entries.length}):
17558
18048
  `);
17559
18049
  for (const e of entries) {
17560
- const exists = fs38.existsSync(e.path);
18050
+ const exists = fs39.existsSync(e.path);
17561
18051
  const status = exists ? "\u2713" : "\u2717 (missing)";
17562
18052
  console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
17563
18053
  }
@@ -17590,7 +18080,7 @@ program.command("migrate").description("Manage database schema migrations").opti
17590
18080
  $ code-intel migrate --rollback
17591
18081
  `).action((opts) => {
17592
18082
  const dbPath = opts.db ?? path39.join(os13.homedir(), ".code-intel", "users.db");
17593
- if (!fs38.existsSync(dbPath)) {
18083
+ if (!fs39.existsSync(dbPath)) {
17594
18084
  console.error(`
17595
18085
  \u2717 Database not found: ${dbPath}
17596
18086
  Run \`code-intel serve\` or \`code-intel user create\` first.
@@ -17712,8 +18202,8 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
17712
18202
  server: serverUrl,
17713
18203
  storedAt: (/* @__PURE__ */ new Date()).toISOString()
17714
18204
  };
17715
- fs38.mkdirSync(path39.dirname(tokenPath), { recursive: true });
17716
- fs38.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
18205
+ fs39.mkdirSync(path39.dirname(tokenPath), { recursive: true });
18206
+ fs39.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
17717
18207
  console.log(` \u2705 Authenticated successfully!`);
17718
18208
  console.log(` Token stored at: ${tokenPath}`);
17719
18209
  console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
@@ -17727,12 +18217,12 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
17727
18217
  });
17728
18218
  authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
17729
18219
  const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
17730
- if (!fs38.existsSync(tokenPath)) {
18220
+ if (!fs39.existsSync(tokenPath)) {
17731
18221
  console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
17732
18222
  return;
17733
18223
  }
17734
18224
  try {
17735
- const data = JSON.parse(fs38.readFileSync(tokenPath, "utf-8"));
18225
+ const data = JSON.parse(fs39.readFileSync(tokenPath, "utf-8"));
17736
18226
  console.log(`
17737
18227
  \u2705 OIDC token stored`);
17738
18228
  console.log(` Server : ${data.server ?? "unknown"}`);
@@ -17745,8 +18235,8 @@ authCmd.command("status").description("Show the current OIDC authentication stat
17745
18235
  });
17746
18236
  authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
17747
18237
  const tokenPath = path39.join(os13.homedir(), ".code-intel", "oidc-token.json");
17748
- if (fs38.existsSync(tokenPath)) {
17749
- fs38.unlinkSync(tokenPath);
18238
+ if (fs39.existsSync(tokenPath)) {
18239
+ fs39.unlinkSync(tokenPath);
17750
18240
  console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
17751
18241
  } else {
17752
18242
  console.log("\n No stored token found.\n");
@@ -17871,7 +18361,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
17871
18361
  $ code-intel config-validate ~/.code-intel/config.json
17872
18362
  `).action((file) => {
17873
18363
  const filePath = path39.resolve(file);
17874
- if (!fs38.existsSync(filePath)) {
18364
+ if (!fs39.existsSync(filePath)) {
17875
18365
  console.error(`
17876
18366
  \u2717 File not found: ${filePath}
17877
18367
  `);
@@ -17879,7 +18369,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
17879
18369
  }
17880
18370
  let cfg;
17881
18371
  try {
17882
- cfg = JSON.parse(fs38.readFileSync(filePath, "utf-8"));
18372
+ cfg = JSON.parse(fs39.readFileSync(filePath, "utf-8"));
17883
18373
  } catch (err) {
17884
18374
  console.error(`
17885
18375
  \u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
@@ -17920,7 +18410,7 @@ program.command("health").description("Run code health checks: dead code, circul
17920
18410
  const workspaceRoot = path39.resolve(targetPath);
17921
18411
  const dbPath = getDbPath(workspaceRoot);
17922
18412
  const meta = loadMetadata(workspaceRoot);
17923
- if (!meta || !fs38.existsSync(dbPath)) {
18413
+ if (!meta || !fs39.existsSync(dbPath)) {
17924
18414
  console.error(`
17925
18415
  \u2717 ${workspaceRoot} is not indexed.`);
17926
18416
  console.error(" Run `code-intel analyze` first to build the index.\n");
@@ -18065,13 +18555,13 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
18065
18555
  gqlInput = content;
18066
18556
  } else if (opts.file) {
18067
18557
  const filePath = path39.resolve(opts.file);
18068
- if (!fs38.existsSync(filePath)) {
18558
+ if (!fs39.existsSync(filePath)) {
18069
18559
  console.error(`
18070
18560
  \u2717 File not found: ${filePath}
18071
18561
  `);
18072
18562
  process.exit(1);
18073
18563
  }
18074
- gqlInput = fs38.readFileSync(filePath, "utf-8");
18564
+ gqlInput = fs39.readFileSync(filePath, "utf-8");
18075
18565
  } else if (gqlArg) {
18076
18566
  gqlInput = gqlArg;
18077
18567
  } else {
@@ -18599,7 +19089,7 @@ program.command("doctor").description("Run diagnostics \u2014 check Node.js, git
18599
19089
  continue;
18600
19090
  }
18601
19091
  const vdbPath = getVectorDbPath2(repo.path);
18602
- if (fs38.existsSync(vdbPath)) {
19092
+ if (fs39.existsSync(vdbPath)) {
18603
19093
  try {
18604
19094
  const Database22 = (await import('better-sqlite3')).default;
18605
19095
  const vdb = new Database22(vdbPath, { readonly: true, fileMustExist: true });
@@ -18636,6 +19126,75 @@ program.command("doctor").description("Run diagnostics \u2014 check Node.js, git
18636
19126
  console.log(" \u2705 All checks passed.\n");
18637
19127
  }
18638
19128
  });
19129
+ program.command("context").description("Build a token-efficient context document for a set of seed symbols").argument("<symbols...>", "One or more symbol names to use as seeds").option("-p, --path <path>", "Path to the repository (default: current directory)", ".").option("-l, --limit <n>", "Max seeds to resolve (default: 10)", "10").option("--intent <intent>", "Query intent: code | callers | architecture | auto (default: auto)", "auto").option("--max-tokens <n>", "Max total tokens for output (default: 6000)", "6000").option("--show-context", "Print per-block token breakdown after output").addHelpText("after", `
19130
+ Builds a structured [SUMMARY] / [LOGIC] / [RELATION] / [FOCUS CODE] context
19131
+ document for one or more symbols. Designed to give AI assistants dense,
19132
+ token-efficient context instead of raw file reads.
19133
+
19134
+ Examples:
19135
+ $ code-intel context UserService
19136
+ $ code-intel context createUser login --intent callers
19137
+ $ code-intel context AuthService --show-context
19138
+ $ code-intel context handlePayment --max-tokens 3000 --intent code
19139
+ `).action(async (symbols, opts) => {
19140
+ const { graph } = await loadOrAnalyzeWorkspace(opts.path);
19141
+ const { build: build2, detectQueryIntent: detectQueryIntent2 } = await Promise.resolve().then(() => (init_builder(), builder_exports));
19142
+ const { measureBlocks: measureBlocks2 } = await Promise.resolve().then(() => (init_token_counter(), token_counter_exports));
19143
+ const maxSeeds = parseInt(opts.limit, 10);
19144
+ const maxTokens = parseInt(opts.maxTokens, 10);
19145
+ const intent = ["code", "callers", "architecture", "auto"].includes(opts.intent) ? opts.intent : detectQueryIntent2(opts.intent);
19146
+ const seeds = [];
19147
+ for (const symbol of symbols.slice(0, maxSeeds)) {
19148
+ for (const node of graph.allNodes()) {
19149
+ if (node.name === symbol) {
19150
+ seeds.push({ nodeId: node.id, refinedScore: 1 });
19151
+ break;
19152
+ }
19153
+ }
19154
+ }
19155
+ if (seeds.length === 0) {
19156
+ console.log(`
19157
+ No symbols found for: ${symbols.join(", ")}
19158
+ `);
19159
+ console.log(' Try: code-intel search "<name>" to find symbol names.\n');
19160
+ return;
19161
+ }
19162
+ const doc = build2(seeds, graph, { maxTokens, queryIntent: intent });
19163
+ console.log("");
19164
+ if (doc.summary) {
19165
+ console.log(doc.summary);
19166
+ console.log("");
19167
+ }
19168
+ if (doc.logic) {
19169
+ console.log(doc.logic);
19170
+ console.log("");
19171
+ }
19172
+ if (doc.relation) {
19173
+ console.log(doc.relation);
19174
+ console.log("");
19175
+ }
19176
+ if (doc.focusCode) {
19177
+ console.log(doc.focusCode);
19178
+ console.log("");
19179
+ }
19180
+ if (doc.truncated) {
19181
+ console.log(" \u26A0 Output was truncated due to token budget. Use --max-tokens to increase.");
19182
+ console.log("");
19183
+ }
19184
+ if (opts.showContext) {
19185
+ const counts = measureBlocks2(doc);
19186
+ console.log(" \u2500\u2500 Token breakdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
19187
+ console.log(` [SUMMARY] ${String(counts.summary).padStart(5)} tokens`);
19188
+ console.log(` [LOGIC] ${String(counts.logic).padStart(5)} tokens`);
19189
+ console.log(` [RELATION] ${String(counts.relation).padStart(5)} tokens`);
19190
+ console.log(` [FOCUS CODE] ${String(counts.focusCode).padStart(5)} tokens`);
19191
+ console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
19192
+ console.log(` Total ${String(counts.total).padStart(5)} tokens (budget: ${maxTokens})`);
19193
+ console.log(` Intent ${doc.intent}`);
19194
+ console.log(` Truncated ${doc.truncated}`);
19195
+ console.log("");
19196
+ }
19197
+ });
18639
19198
  program.parse();
18640
19199
  //# sourceMappingURL=main.js.map
18641
19200
  //# sourceMappingURL=main.js.map