lens-engine 0.1.20 → 0.1.21

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/daemon.js CHANGED
@@ -7605,6 +7605,7 @@ var init_esm2 = __esm({
7605
7605
  var dist_exports = {};
7606
7606
  __export(dist_exports, {
7607
7607
  DEFAULT_CHUNKING_PARAMS: () => DEFAULT_CHUNKING_PARAMS,
7608
+ GOLD_DATASET: () => GOLD_DATASET,
7608
7609
  RequestTrace: () => RequestTrace,
7609
7610
  agglomerativeCluster: () => agglomerativeCluster,
7610
7611
  analyzeGitHistory: () => analyzeGitHistory,
@@ -7616,10 +7617,12 @@ __export(dist_exports, {
7616
7617
  closeDb: () => closeDb,
7617
7618
  cochangeQueries: () => cochangeQueries,
7618
7619
  computeMaxImportDepth: () => computeMaxImportDepth,
7620
+ computeMetrics: () => computeMetrics,
7619
7621
  cosine: () => cosine2,
7620
7622
  deriveIdentityKey: () => deriveIdentityKey,
7621
7623
  detectLanguage: () => detectLanguage,
7622
7624
  diffScan: () => diffScan,
7625
+ discoverTestFiles: () => discoverTestFiles,
7623
7626
  enrichPurpose: () => enrichPurpose,
7624
7627
  ensureEmbedded: () => ensureEmbedded,
7625
7628
  ensureIndexed: () => ensureIndexed,
@@ -7654,10 +7657,13 @@ __export(dist_exports, {
7654
7657
  metadataQueries: () => metadataQueries,
7655
7658
  openDb: () => openDb,
7656
7659
  parseGitLog: () => parseGitLog,
7660
+ parseQuery: () => parseQuery,
7657
7661
  registerRepo: () => registerRepo,
7658
7662
  removeRepo: () => removeRepo,
7659
7663
  repoQueries: () => repoQueries,
7660
7664
  resolveImport: () => resolveImport,
7665
+ resolveSnippets: () => resolveSnippets,
7666
+ runEval: () => runEval,
7661
7667
  runIndex: () => runIndex,
7662
7668
  setTelemetryEnabled: () => setTelemetryEnabled,
7663
7669
  settingsQueries: () => settingsQueries,
@@ -7704,6 +7710,49 @@ function fromEmbeddingBlob(blob2) {
7704
7710
  const buf = blob2 instanceof Buffer ? blob2 : Buffer.from(blob2);
7705
7711
  return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
7706
7712
  }
7713
+ function extractSlice(chunks2, targetLine) {
7714
+ const chunk = chunks2.find((c) => c.start_line <= targetLine && targetLine <= c.end_line);
7715
+ if (!chunk) return null;
7716
+ const lines = chunk.content.split("\n");
7717
+ const offset = targetLine - chunk.start_line;
7718
+ const from = Math.max(0, offset - RADIUS);
7719
+ const to = Math.min(lines.length, offset + RADIUS + 1);
7720
+ const sliced = lines.slice(from, to);
7721
+ if (!sliced.length) return null;
7722
+ return {
7723
+ startLine: chunk.start_line + from,
7724
+ endLine: chunk.start_line + to - 1,
7725
+ code: sliced.join("\n")
7726
+ };
7727
+ }
7728
+ function sliceContext(db, repoId, snippets, queryKind) {
7729
+ const result = /* @__PURE__ */ new Map();
7730
+ const limit = MAX_SLICES[queryKind] ?? 3;
7731
+ const candidates = [...snippets.values()].filter((s) => s.line !== null).slice(0, limit);
7732
+ if (!candidates.length) return result;
7733
+ const paths = candidates.map((c) => c.path);
7734
+ const allChunks = chunkQueries.getByRepoPaths(db, repoId, paths);
7735
+ const chunksByPath = /* @__PURE__ */ new Map();
7736
+ for (const c of allChunks) {
7737
+ const arr = chunksByPath.get(c.path) ?? [];
7738
+ arr.push({ start_line: c.start_line, end_line: c.end_line, content: c.content });
7739
+ chunksByPath.set(c.path, arr);
7740
+ }
7741
+ for (const snip of candidates) {
7742
+ const chunks2 = chunksByPath.get(snip.path);
7743
+ if (!chunks2) continue;
7744
+ const slice = extractSlice(chunks2, snip.line);
7745
+ if (!slice) continue;
7746
+ result.set(snip.path, {
7747
+ path: snip.path,
7748
+ startLine: slice.startLine,
7749
+ endLine: slice.endLine,
7750
+ code: slice.code,
7751
+ symbol: snip.symbol
7752
+ });
7753
+ }
7754
+ return result;
7755
+ }
7707
7756
  function setTelemetryEnabled(enabled) {
7708
7757
  _enabled = enabled;
7709
7758
  }
@@ -8445,101 +8494,372 @@ async function ensureIndexed(db, repoId, caps) {
8445
8494
  if (repo.last_indexed_commit === head) return null;
8446
8495
  return runIndex(db, repoId, caps);
8447
8496
  }
8497
+ function estimateTokens(s) {
8498
+ return Math.ceil(s.length / 4);
8499
+ }
8448
8500
  function basename3(p) {
8449
8501
  const i = p.lastIndexOf("/");
8450
8502
  return i >= 0 ? p.substring(i + 1) : p;
8451
8503
  }
8452
- function daysAgo(d) {
8453
- if (!d) return "unknown";
8454
- const days = Math.round((Date.now() - d.getTime()) / 864e5);
8455
- if (days === 0) return "today";
8456
- if (days === 1) return "1d ago";
8457
- return `${days}d ago`;
8504
+ function guessLang(path) {
8505
+ const dot = path.lastIndexOf(".");
8506
+ if (dot < 0) return "";
8507
+ return LANG_EXT[path.substring(dot + 1)] ?? "";
8458
8508
  }
8459
- function formatContextPack(data) {
8460
- const L = [];
8461
- const files = data.files.slice(0, 15);
8462
- const exportsMap = /* @__PURE__ */ new Map();
8509
+ function getPurpose(path, data) {
8463
8510
  for (const m of data.metadata) {
8464
- if (m.exports?.length) exportsMap.set(m.path, m.exports);
8511
+ if (m.path === path && m.purpose) return m.purpose;
8465
8512
  }
8513
+ return "";
8514
+ }
8515
+ function renderImports(path, data) {
8516
+ const fwd = data.forwardImports.get(path)?.slice(0, 3) ?? [];
8517
+ const rev = data.reverseImports.get(path)?.slice(0, 3) ?? [];
8518
+ const parts = [];
8519
+ if (rev.length) parts.push(`\u2190 ${rev.join(", ")}`);
8520
+ if (fwd.length) parts.push(`\u2192 ${fwd.join(", ")}`);
8521
+ return parts.join(" | ");
8522
+ }
8523
+ function renderCochanges(path, cochanges, limit = 2) {
8524
+ const partners = [];
8525
+ for (const cc of cochanges) {
8526
+ if (cc.path === path) partners.push({ name: basename3(cc.partner), count: cc.count });
8527
+ else if (cc.partner === path) partners.push({ name: basename3(cc.path), count: cc.count });
8528
+ }
8529
+ partners.sort((a, b) => b.count - a.count);
8530
+ return partners.slice(0, limit).map((p) => `${p.name} (${p.count}x)`).join(", ");
8531
+ }
8532
+ function getExports(path, data) {
8533
+ for (const m of data.metadata) {
8534
+ if (m.path === path) return m.exports ?? [];
8535
+ }
8536
+ return [];
8537
+ }
8538
+ function fileLabel(path, snippet) {
8539
+ const line = snippet?.line ? `:${snippet.line}` : "";
8540
+ return `${path}${line}`;
8541
+ }
8542
+ function renderSlice(path, data) {
8543
+ const slice = data.slices?.get(path);
8544
+ if (!slice) return null;
8545
+ const lang = guessLang(path);
8546
+ return ` \`\`\`${lang}
8547
+ ${slice.code}
8548
+ \`\`\``;
8549
+ }
8550
+ function formatNatural(data) {
8551
+ const L = [];
8552
+ const files = data.files.slice(0, 7);
8553
+ const snippets = data.snippets ?? /* @__PURE__ */ new Map();
8466
8554
  L.push(`# ${data.goal}`);
8467
8555
  L.push("");
8468
- if (files.length > 0) {
8469
- L.push("## Files");
8470
- for (let i = 0; i < files.length; i++) {
8556
+ L.push("## Key Files");
8557
+ for (let i = 0; i < files.length; i++) {
8558
+ const f = files[i];
8559
+ const snip = snippets.get(f.path);
8560
+ const purpose = getPurpose(f.path, data);
8561
+ const label = fileLabel(f.path, snip);
8562
+ L.push(`${i + 1}. **${label}**${purpose ? ` \u2014 ${purpose}` : ""}`);
8563
+ const exports = getExports(f.path, data);
8564
+ if (exports.length) L.push(` Exports: ${exports.slice(0, 5).join(", ")}`);
8565
+ const imports = renderImports(f.path, data);
8566
+ if (imports) L.push(` ${imports}`);
8567
+ const cochangeStr = renderCochanges(f.path, data.cochanges);
8568
+ if (cochangeStr) L.push(` Co-changes: ${cochangeStr}`);
8569
+ const sliceStr = renderSlice(f.path, data);
8570
+ if (sliceStr) L.push(sliceStr);
8571
+ }
8572
+ const testLines = [];
8573
+ const testFiles = data.testFiles;
8574
+ if (testFiles) {
8575
+ const seen = /* @__PURE__ */ new Set();
8576
+ for (const f of files.slice(0, 5)) {
8577
+ const tests = testFiles.get(f.path);
8578
+ if (tests) {
8579
+ for (const t of tests) {
8580
+ if (!seen.has(t)) {
8581
+ seen.add(t);
8582
+ testLines.push(`- ${t}`);
8583
+ }
8584
+ }
8585
+ }
8586
+ }
8587
+ }
8588
+ if (testLines.length) {
8589
+ L.push("");
8590
+ L.push("## Tests");
8591
+ for (const t of testLines) L.push(t);
8592
+ }
8593
+ return L.join("\n");
8594
+ }
8595
+ function formatSymbol(data) {
8596
+ const L = [];
8597
+ const files = data.files;
8598
+ const snippets = data.snippets ?? /* @__PURE__ */ new Map();
8599
+ const top = files[0];
8600
+ if (!top) return `# ${data.goal}
8601
+
8602
+ No files found.`;
8603
+ const snip = snippets.get(top.path);
8604
+ const purpose = getPurpose(top.path, data);
8605
+ L.push(`# Symbol: ${data.goal}`);
8606
+ L.push("");
8607
+ L.push("## Definition");
8608
+ L.push(`**${fileLabel(top.path, snip)}**${purpose ? ` \u2014 ${purpose}` : ""}`);
8609
+ if (snip?.symbol) {
8610
+ L.push(` \`${snip.symbol}\``);
8611
+ } else {
8612
+ const exports = getExports(top.path, data);
8613
+ if (exports.length) L.push(` Exports: ${exports.slice(0, 5).join(", ")}`);
8614
+ }
8615
+ const sliceStr = renderSlice(top.path, data);
8616
+ if (sliceStr) L.push(sliceStr);
8617
+ const rev = data.reverseImports.get(top.path);
8618
+ if (rev?.length) {
8619
+ L.push("");
8620
+ L.push("## Dependents");
8621
+ L.push(`\u2190 ${rev.slice(0, 5).join(", ")}`);
8622
+ }
8623
+ const cochangeStr = renderCochanges(top.path, data.cochanges, 3);
8624
+ if (cochangeStr) {
8625
+ L.push("");
8626
+ L.push("## Co-changes");
8627
+ L.push(cochangeStr);
8628
+ }
8629
+ if (files.length > 1) {
8630
+ L.push("");
8631
+ L.push("## Also relevant");
8632
+ for (let i = 1; i < Math.min(files.length, 5); i++) {
8471
8633
  const f = files[i];
8472
- const exports = exportsMap.get(f.path);
8473
- const exportStr = exports?.length ? ` \u2014 exports: ${exports.slice(0, 5).join(", ")}` : "";
8474
- L.push(`${i + 1}. ${f.path}${exportStr}`);
8634
+ const p = getPurpose(f.path, data);
8635
+ L.push(`${i + 1}. ${f.path}${p ? ` \u2014 ${p}` : ""}`);
8475
8636
  }
8637
+ }
8638
+ return L.join("\n");
8639
+ }
8640
+ function formatError(data) {
8641
+ const L = [];
8642
+ const files = data.files;
8643
+ const snippets = data.snippets ?? /* @__PURE__ */ new Map();
8644
+ L.push(`# Error: ${data.goal}`);
8645
+ L.push("");
8646
+ if (files.length === 0) {
8647
+ L.push("No matching files found.");
8648
+ return L.join("\n");
8649
+ }
8650
+ const top = files[0];
8651
+ const snip = snippets.get(top.path);
8652
+ const purpose = getPurpose(top.path, data);
8653
+ L.push("## Error Source");
8654
+ L.push(`1. **${fileLabel(top.path, snip)}**${purpose ? ` \u2014 ${purpose}` : ""}`);
8655
+ const exports = getExports(top.path, data);
8656
+ if (exports.length) L.push(` Exports: ${exports.slice(0, 5).join(", ")}`);
8657
+ const imports = renderImports(top.path, data);
8658
+ if (imports) L.push(` ${imports}`);
8659
+ const sliceStr = renderSlice(top.path, data);
8660
+ if (sliceStr) L.push(sliceStr);
8661
+ if (files.length > 1) {
8476
8662
  L.push("");
8663
+ L.push("## Also References");
8664
+ for (let i = 1; i < Math.min(files.length, 5); i++) {
8665
+ const f = files[i];
8666
+ const s = snippets.get(f.path);
8667
+ const p = getPurpose(f.path, data);
8668
+ L.push(`${i + 1}. **${fileLabel(f.path, s)}**${p ? ` \u2014 ${p}` : ""}`);
8669
+ }
8477
8670
  }
8478
- const depLines = [];
8479
- for (const f of files.slice(0, 5)) {
8480
- const fwd = data.forwardImports.get(f.path);
8481
- const rev = data.reverseImports.get(f.path);
8482
- const hop2 = data.hop2Deps.get(f.path);
8483
- if (!fwd?.length && !rev?.length) continue;
8484
- depLines.push(f.path);
8485
- if (fwd?.length) {
8486
- depLines.push(` imports: ${fwd.slice(0, 5).map(basename3).join(", ")}`);
8671
+ return L.join("\n");
8672
+ }
8673
+ function formatStackTrace(data) {
8674
+ const L = [];
8675
+ const files = data.files;
8676
+ const snippets = data.snippets ?? /* @__PURE__ */ new Map();
8677
+ L.push("# Stack Trace");
8678
+ L.push("");
8679
+ if (files.length === 0) {
8680
+ L.push("No matching files found.");
8681
+ return L.join("\n");
8682
+ }
8683
+ const top = files[0];
8684
+ const snip = snippets.get(top.path);
8685
+ const purpose = getPurpose(top.path, data);
8686
+ L.push("## Crash Point");
8687
+ L.push(`**${fileLabel(top.path, snip)}**${purpose ? ` \u2014 ${purpose}` : ""}`);
8688
+ if (snip?.symbol) {
8689
+ L.push(` \`${snip.symbol}\``);
8690
+ }
8691
+ const sliceStr = renderSlice(top.path, data);
8692
+ if (sliceStr) L.push(sliceStr);
8693
+ const chain = [];
8694
+ const fwd = data.forwardImports.get(top.path);
8695
+ const rev = data.reverseImports.get(top.path);
8696
+ if (rev?.length || fwd?.length) {
8697
+ const callers = rev?.slice(0, 2) ?? [];
8698
+ const deps = fwd?.slice(0, 2) ?? [];
8699
+ if (callers.length || deps.length) {
8700
+ const parts = [...callers.map((c) => `${c} \u2192`), top.path, ...deps.map((d) => `\u2192 ${d}`)];
8701
+ chain.push(parts.join(" "));
8702
+ }
8703
+ }
8704
+ if (chain.length) {
8705
+ L.push("");
8706
+ L.push("## Call Chain");
8707
+ for (const c of chain) L.push(c);
8708
+ }
8709
+ if (files.length > 1) {
8710
+ L.push("");
8711
+ L.push("## Related");
8712
+ for (let i = 1; i < Math.min(files.length, 5); i++) {
8713
+ const f = files[i];
8714
+ const p = getPurpose(f.path, data);
8715
+ const cochangeStr = renderCochanges(f.path, data.cochanges, 1);
8716
+ L.push(`- ${f.path}${p ? ` \u2014 ${p}` : ""}${cochangeStr ? ` (${cochangeStr})` : ""}`);
8717
+ const relSlice = renderSlice(f.path, data);
8718
+ if (relSlice) L.push(relSlice);
8719
+ }
8720
+ }
8721
+ return L.join("\n");
8722
+ }
8723
+ function stripCodeSlices(lines) {
8724
+ const result = [];
8725
+ let inFence = false;
8726
+ for (const line of lines) {
8727
+ if (line.trimStart().startsWith("```") && !inFence) {
8728
+ inFence = true;
8729
+ continue;
8487
8730
  }
8488
- if (rev?.length) {
8489
- let revStr = rev.slice(0, 4).map(basename3).join(", ");
8490
- if (hop2?.length) {
8491
- revStr += ` \u2192 ${hop2.slice(0, 3).map(basename3).join(", ")}`;
8731
+ if (inFence && line.trimStart().startsWith("```")) {
8732
+ inFence = false;
8733
+ continue;
8734
+ }
8735
+ if (!inFence) result.push(line);
8736
+ }
8737
+ return result;
8738
+ }
8739
+ function progressiveStrip(output, _data) {
8740
+ let lines = output.split("\n");
8741
+ if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
8742
+ lines = stripCodeSlices(lines);
8743
+ }
8744
+ if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
8745
+ lines = lines.map((l) => {
8746
+ if (l.trimStart().startsWith("Co-changes:")) {
8747
+ const match2 = l.match(/Co-changes:\s*([^,]+)/);
8748
+ if (match2) return `${l.substring(0, l.indexOf("Co-changes:"))}Co-changes: ${match2[1].trim()}`;
8492
8749
  }
8493
- depLines.push(` imported by: ${revStr}`);
8750
+ return l;
8751
+ });
8752
+ }
8753
+ if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
8754
+ const testIdx = lines.findIndex((l) => l.startsWith("## Tests"));
8755
+ if (testIdx >= 0) {
8756
+ let endIdx = lines.findIndex((l, i) => i > testIdx && l.startsWith("## "));
8757
+ if (endIdx < 0) endIdx = lines.length;
8758
+ lines.splice(testIdx, endIdx - testIdx);
8494
8759
  }
8495
8760
  }
8496
- if (depLines.length > 0) {
8497
- L.push("## Dependency Graph");
8498
- for (const line of depLines) L.push(line);
8499
- L.push("");
8761
+ if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
8762
+ lines = lines.filter((l) => {
8763
+ const trimmed = l.trimStart();
8764
+ return !(trimmed.startsWith("\u2190") || trimmed.startsWith("\u2192")) || !trimmed.includes("|");
8765
+ });
8500
8766
  }
8501
- if (data.cochanges.length > 0) {
8502
- const clusters = buildClusters(data.cochanges);
8503
- if (clusters.length > 0) {
8504
- L.push("## Co-change Clusters");
8505
- for (const c of clusters) {
8506
- L.push(`[${c.members.map(basename3).join(", ")}] \u2014 ${c.count} co-commits`);
8767
+ if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
8768
+ const reduced = [];
8769
+ let fileCount = 0;
8770
+ for (const line of lines) {
8771
+ if (/^\d+\./.test(line.trimStart())) {
8772
+ fileCount++;
8773
+ if (fileCount > 5) continue;
8507
8774
  }
8508
- L.push("");
8775
+ reduced.push(line);
8509
8776
  }
8777
+ lines = reduced;
8510
8778
  }
8511
- const activityLines = [];
8512
- for (const f of files.slice(0, 5)) {
8513
- const stat32 = data.fileStats.get(f.path);
8514
- if (!stat32 || stat32.commit_count === 0) continue;
8515
- activityLines.push(
8516
- `${basename3(f.path)}: ${stat32.commit_count} commits, ${stat32.recent_count}/90d, last: ${daysAgo(stat32.last_modified)}`
8517
- );
8779
+ if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
8780
+ lines = lines.map((l) => {
8781
+ const dashIdx = l.indexOf(" \u2014 ");
8782
+ if (dashIdx > 0 && (l.includes("**") || /^\d+\./.test(l.trimStart()))) {
8783
+ return l.substring(0, dashIdx);
8784
+ }
8785
+ return l;
8786
+ });
8787
+ }
8788
+ return lines.join("\n");
8789
+ }
8790
+ function filterByScoreRelevance(data) {
8791
+ if (!data.scores || data.scores.size === 0) return data;
8792
+ const topScore = Math.max(...data.scores.values());
8793
+ if (topScore <= 0) return data;
8794
+ const threshold = topScore * 0.15;
8795
+ const filtered = data.files.filter((f) => {
8796
+ const score = data.scores?.get(f.path);
8797
+ if (score === void 0) return data.files.indexOf(f) < 5;
8798
+ return score >= threshold;
8799
+ });
8800
+ return { ...data, files: filtered };
8801
+ }
8802
+ function formatContextPack(data) {
8803
+ const filtered = filterByScoreRelevance(data);
8804
+ let output;
8805
+ switch (filtered.queryKind) {
8806
+ case "symbol":
8807
+ output = formatSymbol(filtered);
8808
+ break;
8809
+ case "error_message":
8810
+ output = formatError(filtered);
8811
+ break;
8812
+ case "stack_trace":
8813
+ output = formatStackTrace(filtered);
8814
+ break;
8815
+ default:
8816
+ output = formatNatural(filtered);
8817
+ break;
8518
8818
  }
8519
- if (activityLines.length > 0) {
8520
- L.push("## Activity");
8521
- for (const line of activityLines) L.push(line);
8819
+ if (estimateTokens(output) > TOKEN_CAP) {
8820
+ output = progressiveStrip(output, filtered);
8522
8821
  }
8523
- return L.join("\n");
8822
+ return output;
8524
8823
  }
8525
- function buildClusters(cochanges) {
8526
- const clusters = [];
8527
- for (const cc of cochanges) {
8528
- let merged = false;
8529
- for (const c of clusters) {
8530
- if (c.members.includes(cc.path) || c.members.includes(cc.partner)) {
8531
- if (!c.members.includes(cc.path)) c.members.push(cc.path);
8532
- if (!c.members.includes(cc.partner)) c.members.push(cc.partner);
8533
- c.count = Math.max(c.count, cc.count);
8534
- merged = true;
8535
- break;
8824
+ function extractFrames(query) {
8825
+ const frames = [];
8826
+ const seen = /* @__PURE__ */ new Set();
8827
+ for (const pattern of FRAME_PATTERNS) {
8828
+ pattern.lastIndex = 0;
8829
+ let m;
8830
+ while (m = pattern.exec(query)) {
8831
+ const raw2 = m[1].replace(/^\.\//, "").replace(/^\/+/, "");
8832
+ const line = Number.parseInt(m[2], 10);
8833
+ const key = `${raw2}:${line}`;
8834
+ if (!seen.has(key)) {
8835
+ seen.add(key);
8836
+ frames.push({ path: raw2, line });
8536
8837
  }
8537
8838
  }
8538
- if (!merged) {
8539
- clusters.push({ members: [cc.path, cc.partner], count: cc.count });
8540
- }
8541
8839
  }
8542
- return clusters.filter((c) => c.count >= 2).sort((a, b) => b.count - a.count).slice(0, 5);
8840
+ return frames;
8841
+ }
8842
+ function tokenize(query) {
8843
+ return query.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
8844
+ }
8845
+ function parseQuery(query) {
8846
+ const naturalTokens = tokenize(query);
8847
+ const frames = extractFrames(query);
8848
+ if (frames.length >= 2) {
8849
+ return { kind: "stack_trace", raw: query, frames, symbol: null, errorToken: null, naturalTokens };
8850
+ }
8851
+ const trimmed = query.trim();
8852
+ if (SYMBOL_RE.test(trimmed) && trimmed.length >= 4 && CASING_BOUNDARY.test(trimmed)) {
8853
+ return { kind: "symbol", raw: query, frames: [], symbol: trimmed, errorToken: null, naturalTokens };
8854
+ }
8855
+ const errorCode = query.match(ERROR_CODE_RE)?.[0];
8856
+ const errorSuffix = query.match(ERROR_SUFFIX_RE)?.[0];
8857
+ const errorPrefix = query.match(ERROR_PREFIX_RE)?.[1];
8858
+ const errorToken = errorCode || errorSuffix || errorPrefix || null;
8859
+ if (errorToken) {
8860
+ return { kind: "error_message", raw: query, frames, symbol: null, errorToken, naturalTokens };
8861
+ }
8862
+ return { kind: "natural", raw: query, frames, symbol: null, errorToken: null, naturalTokens };
8543
8863
  }
8544
8864
  function stem(word) {
8545
8865
  if (word.length <= 4) return word;
@@ -8547,6 +8867,7 @@ function stem(word) {
8547
8867
  }
8548
8868
  function expandKeywords(words, clusters) {
8549
8869
  const exact = /* @__PURE__ */ new Set();
8870
+ const expanded = /* @__PURE__ */ new Set();
8550
8871
  const stemmed = /* @__PURE__ */ new Set();
8551
8872
  const clusterFiles = /* @__PURE__ */ new Set();
8552
8873
  for (const w of words) {
@@ -8555,7 +8876,7 @@ function expandKeywords(words, clusters) {
8555
8876
  if (s.length >= 3 && s !== w) stemmed.add(s);
8556
8877
  if (w.includes("-")) {
8557
8878
  for (const part of w.split("-")) {
8558
- if (part.length > 2 && !STOPWORDS.has(part)) {
8879
+ if (part.length > 2 && !STOPWORDS2.has(part)) {
8559
8880
  exact.add(part);
8560
8881
  const ps = stem(part);
8561
8882
  if (ps.length >= 3 && ps !== part) stemmed.add(ps);
@@ -8566,21 +8887,26 @@ function expandKeywords(words, clusters) {
8566
8887
  for (const w of words) {
8567
8888
  const synonyms = CONCEPT_SYNONYMS[w];
8568
8889
  if (synonyms) {
8569
- for (const syn of synonyms) exact.add(syn);
8890
+ for (const syn of synonyms) {
8891
+ if (!exact.has(syn)) expanded.add(syn);
8892
+ }
8570
8893
  }
8571
8894
  }
8572
8895
  if (clusters) {
8573
8896
  for (const w of words) {
8574
8897
  for (const cluster of clusters) {
8575
8898
  if (cluster.terms.includes(w) || cluster.terms.some((t) => stem(t) === stem(w))) {
8576
- for (const t of cluster.terms) exact.add(t);
8899
+ for (const t of cluster.terms) {
8900
+ if (!exact.has(t)) expanded.add(t);
8901
+ }
8577
8902
  for (const f of cluster.files) clusterFiles.add(f);
8578
8903
  }
8579
8904
  }
8580
8905
  }
8581
8906
  }
8582
8907
  for (const e of exact) stemmed.delete(e);
8583
- return { exact: [...exact], stemmed: [...stemmed], clusterFiles };
8908
+ for (const e of expanded) stemmed.delete(e);
8909
+ return { exact: [...exact], expanded, stemmed: [...stemmed], clusterFiles };
8584
8910
  }
8585
8911
  function isNoisePath(path) {
8586
8912
  const lower = path.toLowerCase();
@@ -8604,17 +8930,17 @@ function buildTermWeights(words, metadata) {
8604
8930
  }
8605
8931
  return weights;
8606
8932
  }
8607
- function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, maxImportDepth) {
8608
- const rawWords = query.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
8609
- const { exact, stemmed, clusterFiles } = expandKeywords(rawWords, vocabClusters ?? null);
8610
- const allTerms = [...exact, ...stemmed];
8933
+ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, maxImportDepth, parsed) {
8934
+ const rawWords = query.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS2.has(w));
8935
+ const { exact, expanded, stemmed, clusterFiles } = expandKeywords(rawWords, vocabClusters ?? null);
8936
+ const allTerms = [...exact, ...expanded, ...stemmed];
8611
8937
  const termWeights = buildTermWeights(allTerms, metadata);
8612
8938
  const exportTokensMap = /* @__PURE__ */ new Map();
8613
8939
  for (const f of metadata) {
8614
8940
  const tokens = /* @__PURE__ */ new Set();
8615
8941
  for (const exp of f.exports ?? []) {
8616
8942
  for (const part of exp.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[\s_-]+/)) {
8617
- if (part.length >= 3 && !STOPWORDS.has(part)) tokens.add(part);
8943
+ if (part.length >= 3 && !STOPWORDS2.has(part)) tokens.add(part);
8618
8944
  }
8619
8945
  }
8620
8946
  exportTokensMap.set(f.path, tokens);
@@ -8634,6 +8960,32 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
8634
8960
  const dirTokens = pathSegments.slice(-4, -1).flatMap((s) => s.replace(/\./g, " ").split(/\s+/)).filter((t) => t.length >= 2);
8635
8961
  const pathTokenSet = /* @__PURE__ */ new Set([...fileTokens, ...dirTokens]);
8636
8962
  const expTokens = exportTokensMap.get(f.path);
8963
+ if (parsed) {
8964
+ if (parsed.kind === "stack_trace") {
8965
+ for (const frame of parsed.frames) {
8966
+ if (f.path.endsWith(frame.path) || frame.path.endsWith(f.path)) {
8967
+ score += 50;
8968
+ }
8969
+ }
8970
+ } else if (parsed.kind === "symbol" && parsed.symbol) {
8971
+ const sym = parsed.symbol;
8972
+ if ((f.exports ?? []).includes(sym)) score += 100;
8973
+ else if ((f.internals ?? []).includes(sym)) score += 80;
8974
+ } else if (parsed.kind === "error_message") {
8975
+ if (parsed.errorToken) {
8976
+ const token = parsed.errorToken.toLowerCase();
8977
+ if (exportsLower.includes(token) || internalsLower.includes(token) || pathLower.includes(token)) {
8978
+ score += 30;
8979
+ }
8980
+ }
8981
+ const rawError = parsed.raw.replace(/^(Error|TypeError|ReferenceError|SyntaxError|RangeError|FATAL|WARN|ERR):\s*/i, "").toLowerCase().trim();
8982
+ if (rawError.length >= 5) {
8983
+ if (docLower.includes(rawError) || sectionsLower.includes(rawError) || internalsLower.includes(rawError) || purposeLower.includes(rawError)) {
8984
+ score += 40;
8985
+ }
8986
+ }
8987
+ }
8988
+ }
8637
8989
  for (const w of exact) {
8638
8990
  const weight = termWeights.get(w) ?? 1;
8639
8991
  let termScore = 0;
@@ -8647,6 +8999,16 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
8647
8999
  if (termScore > 0) matchedTerms++;
8648
9000
  score += termScore;
8649
9001
  }
9002
+ for (const w of expanded) {
9003
+ const weight = (termWeights.get(w) ?? 1) * 0.5;
9004
+ let termScore = 0;
9005
+ if (expTokens?.has(w)) termScore += 2 * weight;
9006
+ else if (exportsLower.includes(w)) termScore += 1.5 * weight;
9007
+ if (docLower.includes(w) || purposeLower.includes(w)) termScore += 1 * weight;
9008
+ if (internalsLower.includes(w)) termScore += 1 * weight;
9009
+ if (termScore > 0) matchedTerms++;
9010
+ score += termScore;
9011
+ }
8650
9012
  for (const w of stemmed) {
8651
9013
  const weight = termWeights.get(w) ?? 1;
8652
9014
  let termScore = 0;
@@ -8662,7 +9024,7 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
8662
9024
  score *= 1 + coverage * coverage;
8663
9025
  }
8664
9026
  if (score > 0 && isNoisePath(f.path)) {
8665
- score *= 0.3;
9027
+ score = 0;
8666
9028
  }
8667
9029
  const exportCount = (f.exports ?? []).length;
8668
9030
  if (exportCount > 5 && score > 0) {
@@ -8696,14 +9058,27 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
8696
9058
  deduped.push(f);
8697
9059
  if (deduped.length >= fileCap) break;
8698
9060
  }
9061
+ const scores = /* @__PURE__ */ new Map();
9062
+ for (const f of deduped) scores.set(f.path, f.score);
8699
9063
  return {
8700
9064
  fileCap,
9065
+ scores,
8701
9066
  files: deduped.map((f) => {
8702
- const reason = f.docstring?.slice(0, 80) || (f.exports?.length ? `exports: ${f.exports.slice(0, 4).join(", ")}` : "path match");
9067
+ const reason = truncateAtWord(f.docstring, 80) || formatExports(f.exports) || f.path.split("/").pop() || "";
8703
9068
  return { path: f.path, reason };
8704
9069
  })
8705
9070
  };
8706
9071
  }
9072
+ function truncateAtWord(s, max) {
9073
+ if (!s) return "";
9074
+ if (s.length <= max) return s;
9075
+ const cut = s.lastIndexOf(" ", max);
9076
+ return cut > max * 0.5 ? `${s.slice(0, cut)}...` : `${s.slice(0, max)}...`;
9077
+ }
9078
+ function formatExports(exports) {
9079
+ if (!exports?.length) return "";
9080
+ return `exports: ${exports.slice(0, 4).join(", ")}`;
9081
+ }
8707
9082
  function siblingKey(path) {
8708
9083
  const lastSlash = path.lastIndexOf("/");
8709
9084
  const dir = lastSlash >= 0 ? path.substring(0, lastSlash) : "";
@@ -8712,6 +9087,99 @@ function siblingKey(path) {
8712
9087
  const tokens = stem2.split(/[-.]/).slice(0, 3).join("-");
8713
9088
  return `${dir}/${tokens}`;
8714
9089
  }
9090
+ function findSymbolLine(symbol, chunkRows) {
9091
+ const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9092
+ const re = new RegExp(`\\b${escaped}\\b`);
9093
+ for (const chunk of chunkRows) {
9094
+ const lines = chunk.content.split("\n");
9095
+ for (let i = 0; i < lines.length; i++) {
9096
+ if (re.test(lines[i]) && SYMBOL_DEF_RE.test(lines[i])) {
9097
+ return chunk.start_line + i;
9098
+ }
9099
+ }
9100
+ }
9101
+ for (const chunk of chunkRows) {
9102
+ const lines = chunk.content.split("\n");
9103
+ for (let i = 0; i < lines.length; i++) {
9104
+ if (re.test(lines[i])) {
9105
+ return chunk.start_line + i;
9106
+ }
9107
+ }
9108
+ }
9109
+ return null;
9110
+ }
9111
+ function pickBestExport(exports, queryTokens) {
9112
+ if (exports.length <= 1 || !queryTokens.length) return exports[0];
9113
+ let best = exports[0];
9114
+ let bestScore = 0;
9115
+ for (const exp of exports) {
9116
+ const lower = exp.toLowerCase();
9117
+ const expTokens = lower.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[\s_-]+/);
9118
+ let score = 0;
9119
+ for (const qt of queryTokens) {
9120
+ if (lower.includes(qt)) score += 2;
9121
+ else if (expTokens.some((t) => t.includes(qt) || qt.includes(t))) score++;
9122
+ }
9123
+ if (score > bestScore) {
9124
+ bestScore = score;
9125
+ best = exp;
9126
+ }
9127
+ }
9128
+ return best;
9129
+ }
9130
+ function resolveSnippets(db, repoId, files, metadata, parsed, limit = 5) {
9131
+ const result = /* @__PURE__ */ new Map();
9132
+ const topFiles = files.slice(0, limit);
9133
+ const topPaths = topFiles.map((f) => f.path);
9134
+ const allChunks = chunkQueries.getByRepoPaths(db, repoId, topPaths);
9135
+ const chunksByPath = /* @__PURE__ */ new Map();
9136
+ for (const c of allChunks) {
9137
+ const arr = chunksByPath.get(c.path) ?? [];
9138
+ arr.push({ start_line: c.start_line, content: c.content });
9139
+ chunksByPath.set(c.path, arr);
9140
+ }
9141
+ const metaMap = /* @__PURE__ */ new Map();
9142
+ for (const m of metadata) metaMap.set(m.path, m);
9143
+ const frameByPath = /* @__PURE__ */ new Map();
9144
+ for (const frame of parsed.frames) {
9145
+ for (const p of topPaths) {
9146
+ if (p.endsWith(frame.path) || frame.path.endsWith(p)) {
9147
+ frameByPath.set(p, frame.line);
9148
+ }
9149
+ }
9150
+ }
9151
+ for (const f of topFiles) {
9152
+ const chunks2 = chunksByPath.get(f.path) ?? [];
9153
+ const meta = metaMap.get(f.path);
9154
+ const frameLine = frameByPath.get(f.path);
9155
+ if (frameLine !== void 0) {
9156
+ result.set(f.path, { path: f.path, symbol: null, line: frameLine, matchKind: "frame" });
9157
+ continue;
9158
+ }
9159
+ if (parsed.symbol) {
9160
+ const exports = meta?.exports ?? [];
9161
+ const internals = meta?.internals ?? [];
9162
+ if (exports.includes(parsed.symbol)) {
9163
+ const line = findSymbolLine(parsed.symbol, chunks2);
9164
+ result.set(f.path, { path: f.path, symbol: parsed.symbol, line, matchKind: "export" });
9165
+ continue;
9166
+ }
9167
+ if (internals.includes(parsed.symbol)) {
9168
+ const line = findSymbolLine(parsed.symbol, chunks2);
9169
+ result.set(f.path, { path: f.path, symbol: parsed.symbol, line, matchKind: "internal" });
9170
+ continue;
9171
+ }
9172
+ }
9173
+ if (meta?.exports?.length) {
9174
+ const bestExport = pickBestExport(meta.exports, parsed.naturalTokens);
9175
+ const line = findSymbolLine(bestExport, chunks2);
9176
+ result.set(f.path, { path: f.path, symbol: bestExport, line, matchKind: "export" });
9177
+ continue;
9178
+ }
9179
+ result.set(f.path, { path: f.path, symbol: null, line: null, matchKind: null });
9180
+ }
9181
+ return result;
9182
+ }
8715
9183
  function loadFileMetadata(db, repoId) {
8716
9184
  return metadataQueries.getByRepo(db, repoId);
8717
9185
  }
@@ -8774,6 +9242,37 @@ function getIndegrees(db, repoId) {
8774
9242
  function getCochangePartners(db, repoId, paths, minCount = 5, limit = 10) {
8775
9243
  return cochangeQueries.getPartners(db, repoId, paths, minCount, limit);
8776
9244
  }
9245
+ function discoverTestFiles(reverseImports, cochanges, metadataPaths, sourcePaths) {
9246
+ const result = /* @__PURE__ */ new Map();
9247
+ for (const src of sourcePaths) {
9248
+ const tests = /* @__PURE__ */ new Set();
9249
+ const importers = reverseImports.get(src) ?? [];
9250
+ for (const imp of importers) {
9251
+ if (TEST_PATTERN.test(imp)) tests.add(imp);
9252
+ }
9253
+ for (const cc of cochanges) {
9254
+ const partner = cc.path === src ? cc.partner : cc.partner === src ? cc.path : null;
9255
+ if (partner && TEST_PATTERN.test(partner)) tests.add(partner);
9256
+ }
9257
+ const ext = src.slice(src.lastIndexOf("."));
9258
+ const base = src.slice(0, src.lastIndexOf("."));
9259
+ const dir = src.slice(0, src.lastIndexOf("/"));
9260
+ const name = src.slice(src.lastIndexOf("/") + 1, src.lastIndexOf("."));
9261
+ const candidates = [
9262
+ `${base}.test${ext}`,
9263
+ `${base}.spec${ext}`,
9264
+ `${dir}/__tests__/${name}${ext}`,
9265
+ `${dir}/__tests__/${name}.test${ext}`
9266
+ ];
9267
+ for (const c of candidates) {
9268
+ if (metadataPaths.has(c)) tests.add(c);
9269
+ }
9270
+ if (tests.size > 0) {
9271
+ result.set(src, [...tests].slice(0, 3));
9272
+ }
9273
+ }
9274
+ return result;
9275
+ }
8777
9276
  function loadVocabClusters(db, repoId) {
8778
9277
  const repo = repoQueries.getById(db, repoId);
8779
9278
  if (!repo?.vocab_clusters) return null;
@@ -8834,18 +9333,21 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
8834
9333
  const repo = repoQueries.getById(db, repoId);
8835
9334
  const commit = repo?.last_indexed_commit ?? null;
8836
9335
  const useEmb = options?.useEmbeddings !== false;
8837
- const key = cacheKey(repoId, goal, commit, useEmb);
8838
- const cached2 = cacheGet(key);
8839
- if (cached2) {
8840
- trace?.add("cache", 0, "HIT");
8841
- track(db, "context", {
8842
- duration_ms: Date.now() - start,
8843
- result_count: cached2.stats.files_in_context,
8844
- cache_hit: true
8845
- });
8846
- return { ...cached2, stats: { ...cached2.stats, cached: true, duration_ms: Date.now() - start } };
9336
+ if (!options?.skipCache) {
9337
+ const key = cacheKey(repoId, goal, commit, useEmb);
9338
+ const cached2 = cacheGet(key);
9339
+ if (cached2) {
9340
+ trace?.add("cache", 0, "HIT");
9341
+ track(db, "context", {
9342
+ duration_ms: Date.now() - start,
9343
+ result_count: cached2.stats.files_in_context,
9344
+ cache_hit: true
9345
+ });
9346
+ return { ...cached2, stats: { ...cached2.stats, cached: true, duration_ms: Date.now() - start } };
9347
+ }
8847
9348
  }
8848
9349
  const embAvailable = useEmb && (await Promise.resolve().then(() => (init_queries(), queries_exports))).chunkQueries.hasEmbeddings(db, repoId);
9350
+ const parsed = parseQuery(goal);
8849
9351
  trace?.step("loadStructural");
8850
9352
  const metadata = loadFileMetadata(db, repoId);
8851
9353
  const allStats = getAllFileStats(db, repoId);
@@ -8861,11 +9363,44 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
8861
9363
  statsForInterpreter.set(path, { commit_count: stat32.commit_count, recent_count: stat32.recent_count });
8862
9364
  }
8863
9365
  trace?.step("interpretQuery");
8864
- const interpreted = interpretQuery(goal, metadata, statsForInterpreter, vocabClusters, indegreeMap, maxImportDepth);
8865
- trace?.end("interpretQuery", `${interpreted.files.length} files`);
9366
+ const interpreted = interpretQuery(
9367
+ goal,
9368
+ metadata,
9369
+ statsForInterpreter,
9370
+ vocabClusters,
9371
+ indegreeMap,
9372
+ maxImportDepth,
9373
+ parsed
9374
+ );
9375
+ trace?.end("interpretQuery", `${interpreted.files.length} files (${parsed.kind})`);
9376
+ if (parsed.kind === "error_message") {
9377
+ trace?.step("errorContentSearch");
9378
+ const rawError = parsed.raw.replace(/^(Error|TypeError|ReferenceError|SyntaxError|RangeError|FATAL|WARN|ERR):\s*/i, "").trim();
9379
+ if (rawError.length >= 5) {
9380
+ const contentHits = chunkQueries.searchContent(db, repoId, rawError).filter((p) => !isNoisePath(p));
9381
+ const scored = contentHits.filter((p) => (interpreted.scores.get(p) ?? 0) > 0);
9382
+ const unscored = contentHits.filter((p) => (interpreted.scores.get(p) ?? 0) === 0);
9383
+ const capped = [...scored.slice(0, 3), ...unscored.slice(0, Math.max(0, 3 - scored.length))];
9384
+ const existingSet = new Set(interpreted.files.map((f) => f.path));
9385
+ for (const p of capped) {
9386
+ const currentScore = interpreted.scores.get(p) ?? 0;
9387
+ interpreted.scores.set(p, currentScore + 40);
9388
+ if (!existingSet.has(p)) {
9389
+ const meta = metadata.find((m) => m.path === p);
9390
+ interpreted.files.push({
9391
+ path: p,
9392
+ reason: meta?.docstring?.slice(0, 80) || `contains "${rawError.slice(0, 30)}"`
9393
+ });
9394
+ existingSet.add(p);
9395
+ }
9396
+ }
9397
+ interpreted.files.sort((a, b) => (interpreted.scores.get(b.path) ?? 0) - (interpreted.scores.get(a.path) ?? 0));
9398
+ }
9399
+ trace?.end("errorContentSearch");
9400
+ }
8866
9401
  trace?.step("cochangePromotion");
8867
9402
  const topForCochange = interpreted.files.slice(0, 5).map((f) => f.path);
8868
- const cochangePartners = getCochangePartners(db, repoId, topForCochange, 3, 20);
9403
+ const cochangePartners = getCochangePartners(db, repoId, topForCochange, 5, 15);
8869
9404
  const existingPathSet = new Set(interpreted.files.map((f) => f.path));
8870
9405
  let promoted = 0;
8871
9406
  for (const cp of cochangePartners) {
@@ -8945,6 +9480,20 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
8945
9480
  }
8946
9481
  }
8947
9482
  trace?.end("structuralEnrichment");
9483
+ trace?.step("resolveSnippets");
9484
+ const snippets = resolveSnippets(db, repoId, interpreted.files, metadata, parsed, 5);
9485
+ const metadataPaths = new Set(metadata.map((m) => m.path));
9486
+ const testFiles = discoverTestFiles(
9487
+ reverseImports,
9488
+ cochanges,
9489
+ metadataPaths,
9490
+ interpreted.files.slice(0, 5).map((f) => f.path)
9491
+ );
9492
+ trace?.end("resolveSnippets", `${snippets.size} snippets, ${testFiles.size} test maps`);
9493
+ trace?.step("sliceContext");
9494
+ const { sliceContext: sliceCtx } = await Promise.resolve().then(() => (init_slicer(), slicer_exports));
9495
+ const slices = sliceCtx(db, repoId, snippets, parsed.kind);
9496
+ trace?.end("sliceContext", `${slices.size} slices`);
8948
9497
  trace?.step("formatContextPack");
8949
9498
  const data = {
8950
9499
  goal,
@@ -8954,7 +9503,12 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
8954
9503
  forwardImports,
8955
9504
  hop2Deps,
8956
9505
  cochanges,
8957
- fileStats: allStats
9506
+ fileStats: allStats,
9507
+ scores: interpreted.scores,
9508
+ snippets,
9509
+ slices,
9510
+ testFiles,
9511
+ queryKind: parsed.kind
8958
9512
  };
8959
9513
  const contextPack = formatContextPack(data);
8960
9514
  trace?.end("formatContextPack");
@@ -8967,7 +9521,18 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
8967
9521
  cached: false
8968
9522
  }
8969
9523
  };
8970
- cacheSet(key, response);
9524
+ if (options?.includeRankedFiles) {
9525
+ response.ranked_files = interpreted.files.map((f) => ({
9526
+ path: f.path,
9527
+ reason: f.reason,
9528
+ score: interpreted.scores.get(f.path) ?? 0
9529
+ }));
9530
+ response.query_kind = parsed.kind;
9531
+ }
9532
+ if (!options?.skipCache) {
9533
+ const key = cacheKey(repoId, goal, commit, useEmb);
9534
+ cacheSet(key, response);
9535
+ }
8971
9536
  track(db, "context", {
8972
9537
  duration_ms: response.stats.duration_ms,
8973
9538
  result_count: response.stats.files_in_context,
@@ -9166,7 +9731,102 @@ function createTablesSql() {
9166
9731
  CREATE INDEX IF NOT EXISTS idx_telemetry_events_synced ON telemetry_events(synced_at);
9167
9732
  `;
9168
9733
  }
9169
- function estimateTokens(text22) {
9734
+ function hitAtN(expected, returned, n) {
9735
+ const top = returned.slice(0, n);
9736
+ return expected.some((e) => top.includes(e));
9737
+ }
9738
+ function entryHitAtN(entry, returned, n) {
9739
+ if (!entry) return false;
9740
+ return returned.slice(0, n).includes(entry);
9741
+ }
9742
+ function recallAtK(expected, returned, k) {
9743
+ if (expected.length === 0) return 1;
9744
+ const top = new Set(returned.slice(0, k));
9745
+ const hits = expected.filter((e) => top.has(e)).length;
9746
+ return hits / expected.length;
9747
+ }
9748
+ function computeMetrics(results) {
9749
+ const total = results.length;
9750
+ if (total === 0) {
9751
+ return {
9752
+ total: 0,
9753
+ hit_at_1: 0,
9754
+ hit_at_3: 0,
9755
+ entry_hit_at_1: 0,
9756
+ entry_hit_at_3: 0,
9757
+ avg_recall_at_5: 0,
9758
+ avg_duration_ms: 0,
9759
+ by_kind: [],
9760
+ results
9761
+ };
9762
+ }
9763
+ const hit1 = results.filter((r) => r.hit_at_1).length;
9764
+ const hit3 = results.filter((r) => r.hit_at_3).length;
9765
+ const entryHit1 = results.filter((r) => r.entry_hit_at_1).length;
9766
+ const entryHit3 = results.filter((r) => r.entry_hit_at_3).length;
9767
+ const sumRecall = results.reduce((s, r) => s + r.recall_at_5, 0);
9768
+ const sumDuration = results.reduce((s, r) => s + r.duration_ms, 0);
9769
+ const kinds = [...new Set(results.map((r) => r.kind))];
9770
+ const by_kind = kinds.map((kind) => {
9771
+ const group = results.filter((r) => r.kind === kind);
9772
+ const n = group.length;
9773
+ return {
9774
+ kind,
9775
+ count: n,
9776
+ hit_at_1: group.filter((r) => r.hit_at_1).length / n,
9777
+ hit_at_3: group.filter((r) => r.hit_at_3).length / n,
9778
+ entry_hit_at_1: group.filter((r) => r.entry_hit_at_1).length / n,
9779
+ entry_hit_at_3: group.filter((r) => r.entry_hit_at_3).length / n,
9780
+ avg_recall_at_5: group.reduce((s, r) => s + r.recall_at_5, 0) / n,
9781
+ avg_duration_ms: Math.round(group.reduce((s, r) => s + r.duration_ms, 0) / n)
9782
+ };
9783
+ });
9784
+ return {
9785
+ total,
9786
+ hit_at_1: hit1 / total,
9787
+ hit_at_3: hit3 / total,
9788
+ entry_hit_at_1: entryHit1 / total,
9789
+ entry_hit_at_3: entryHit3 / total,
9790
+ avg_recall_at_5: sumRecall / total,
9791
+ avg_duration_ms: Math.round(sumDuration / total),
9792
+ by_kind,
9793
+ results
9794
+ };
9795
+ }
9796
+ async function runEval(db, repoId, options) {
9797
+ let queries = GOLD_DATASET;
9798
+ if (options?.filterKind) {
9799
+ queries = queries.filter((q) => q.kind === options.filterKind);
9800
+ }
9801
+ const results = [];
9802
+ for (const gold of queries) {
9803
+ const start = Date.now();
9804
+ const response = await buildContext(db, repoId, gold.query, void 0, void 0, {
9805
+ useEmbeddings: false,
9806
+ includeRankedFiles: true,
9807
+ skipCache: true
9808
+ });
9809
+ const duration3 = Date.now() - start;
9810
+ const returned = (response.ranked_files ?? []).map((f) => f.path);
9811
+ results.push({
9812
+ id: gold.id,
9813
+ query: gold.query,
9814
+ kind: gold.kind,
9815
+ expected_files: gold.expected_files,
9816
+ expected_entry: gold.expected_entry,
9817
+ returned_files: returned,
9818
+ hit_at_1: hitAtN(gold.expected_files, returned, 1),
9819
+ hit_at_3: hitAtN(gold.expected_files, returned, 3),
9820
+ entry_hit_at_1: entryHitAtN(gold.expected_entry, returned, 1),
9821
+ entry_hit_at_3: entryHitAtN(gold.expected_entry, returned, 3),
9822
+ recall_at_5: recallAtK(gold.expected_files, returned, 5),
9823
+ duration_ms: duration3,
9824
+ file_count: returned.length
9825
+ });
9826
+ }
9827
+ return computeMetrics(results);
9828
+ }
9829
+ function estimateTokens2(text22) {
9170
9830
  return Math.ceil(text22.length / CHARS_PER_TOKEN);
9171
9831
  }
9172
9832
  async function ensureEmbedded(db, repoId, caps) {
@@ -9192,7 +9852,7 @@ async function ensureEmbedded(db, repoId, caps) {
9192
9852
  const batch = [];
9193
9853
  let batchTokens = 0;
9194
9854
  while (offset < validRows.length) {
9195
- const est = estimateTokens(validRows[offset].content);
9855
+ const est = estimateTokens2(validRows[offset].content);
9196
9856
  if (batch.length > 0 && batchTokens + est > MAX_BATCH_TOKENS) break;
9197
9857
  batch.push(validRows[offset]);
9198
9858
  batchTokens += est;
@@ -9255,7 +9915,7 @@ async function enrichPurpose(db, repoId, caps, fullRun = false) {
9255
9915
  return { enriched: totalEnriched, skipped: totalSkipped, duration_ms: Date.now() - start };
9256
9916
  }
9257
9917
  function splitIdentifier(name) {
9258
- return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_\-./\\]/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length >= 3 && !STOPWORDS2.has(w));
9918
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_\-./\\]/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length >= 3 && !STOPWORDS3.has(w));
9259
9919
  }
9260
9920
  function extractVocab(files) {
9261
9921
  const termToFiles = /* @__PURE__ */ new Map();
@@ -9596,7 +10256,7 @@ async function getRepoStatus(db, id) {
9596
10256
  vocab_cluster_count: Array.isArray(vocabClusters) ? vocabClusters.length : 0
9597
10257
  };
9598
10258
  }
9599
- var __defProp2, __getOwnPropNames2, __esm2, __export2, schema_exports, uuid, now, updatedAt, repos, chunks, fileMetadata, fileImports, fileStats, fileCochanges, usageCounters, requestLogs, settings, telemetryEvents, init_schema, queries_exports, repoQueries, chunkQueries, metadataQueries, importQueries, statsQueries, cochangeQueries, logQueries, usageQueries, telemetryQueries, settingsQueries, init_queries, _enabled, DEFAULT_CHUNKING_PARAMS, execFileAsync, MAX_FILE_SIZE, BINARY_EXTENSIONS, DOCS_EXTENSIONS, LANG_MAP, TS_IMPORT_RE, PY_IMPORT_RE, GO_IMPORT_RE, RUST_USE_RE, TS_EXTENSIONS, PY_EXTENSIONS, TS_EXPORT_RE, PY_EXPORT_RE, GO_EXPORT_RE, RUST_EXPORT_RE, CSHARP_EXPORT_RE, CSHARP_EXPORT_METHOD_RE, JAVA_EXPORT_RE, JSDOC_RE, PY_DOCSTRING_RE, CSHARP_DOC_RE, GO_PKG_RE, RUST_DOC_RE, SECTION_SINGLE_RE, SECTION_BLOCK_RE, UNIVERSAL_DECL_RES, EXPORT_LINE_RE, SKIP_NAMES, execFileAsync2, MAX_COMMITS, MAX_FILES_PER_COMMIT, RECENT_DAYS, MAX_CHUNKS_PER_REPO, locks, STOPWORDS, NOISE_EXTENSIONS, NOISE_PATHS, CONCEPT_SYNONYMS, CACHE_TTL, CACHE_MAX, cache, _db, _raw, MAX_API_CALLS, POOL_SIZE, MAX_BATCH_TOKENS, CHARS_PER_TOKEN, PURPOSE_BATCH_LIMIT, PURPOSE_CONCURRENCY, TERM_BATCH_SIZE, SIMILARITY_THRESHOLD, MAX_TERMS, MAX_CLUSTERS, MAX_CLUSTER_SIZE, STOPWORDS2, watchers, debounceTimers, IGNORED, DEBOUNCE_MS, RequestTrace;
10259
+ var __defProp2, __getOwnPropNames2, __esm2, __export2, schema_exports, uuid, now, updatedAt, repos, chunks, fileMetadata, fileImports, fileStats, fileCochanges, usageCounters, requestLogs, settings, telemetryEvents, init_schema, queries_exports, repoQueries, chunkQueries, metadataQueries, importQueries, statsQueries, cochangeQueries, logQueries, usageQueries, telemetryQueries, settingsQueries, init_queries, slicer_exports, RADIUS, MAX_SLICES, init_slicer, _enabled, DEFAULT_CHUNKING_PARAMS, execFileAsync, MAX_FILE_SIZE, BINARY_EXTENSIONS, DOCS_EXTENSIONS, LANG_MAP, TS_IMPORT_RE, PY_IMPORT_RE, GO_IMPORT_RE, RUST_USE_RE, TS_EXTENSIONS, PY_EXTENSIONS, TS_EXPORT_RE, PY_EXPORT_RE, GO_EXPORT_RE, RUST_EXPORT_RE, CSHARP_EXPORT_RE, CSHARP_EXPORT_METHOD_RE, JAVA_EXPORT_RE, JSDOC_RE, PY_DOCSTRING_RE, CSHARP_DOC_RE, GO_PKG_RE, RUST_DOC_RE, SECTION_SINGLE_RE, SECTION_BLOCK_RE, UNIVERSAL_DECL_RES, EXPORT_LINE_RE, SKIP_NAMES, execFileAsync2, MAX_COMMITS, MAX_FILES_PER_COMMIT, RECENT_DAYS, MAX_CHUNKS_PER_REPO, locks, TOKEN_CAP, LANG_EXT, FRAME_PATTERNS, CASING_BOUNDARY, SYMBOL_RE, ERROR_CODE_RE, ERROR_SUFFIX_RE, ERROR_PREFIX_RE, STOPWORDS, STOPWORDS2, NOISE_EXTENSIONS, NOISE_PATHS, CONCEPT_SYNONYMS, SYMBOL_DEF_RE, TEST_PATTERN, CACHE_TTL, CACHE_MAX, cache, _db, _raw, GOLD_DATASET, MAX_API_CALLS, POOL_SIZE, MAX_BATCH_TOKENS, CHARS_PER_TOKEN, PURPOSE_BATCH_LIMIT, PURPOSE_CONCURRENCY, TERM_BATCH_SIZE, SIMILARITY_THRESHOLD, MAX_TERMS, MAX_CLUSTERS, MAX_CLUSTER_SIZE, STOPWORDS3, watchers, debounceTimers, IGNORED, DEBOUNCE_MS, RequestTrace;
9600
10260
  var init_dist = __esm({
9601
10261
  "packages/engine/dist/index.js"() {
9602
10262
  "use strict";
@@ -9982,6 +10642,21 @@ var init_dist = __esm({
9982
10642
  const row = db.select({ id: chunks.id }).from(chunks).where(and(eq(chunks.repo_id, repoId), sql`embedding IS NOT NULL`)).limit(1).get();
9983
10643
  return !!row;
9984
10644
  },
10645
+ getByRepoPaths(db, repoId, paths) {
10646
+ if (!paths.length) return [];
10647
+ return db.select({
10648
+ path: chunks.path,
10649
+ chunk_index: chunks.chunk_index,
10650
+ start_line: chunks.start_line,
10651
+ end_line: chunks.end_line,
10652
+ content: chunks.content
10653
+ }).from(chunks).where(and(eq(chunks.repo_id, repoId), inArray(chunks.path, paths))).orderBy(chunks.path, chunks.chunk_index).all();
10654
+ },
10655
+ searchContent(db, repoId, searchString) {
10656
+ if (!searchString || searchString.length < 4) return [];
10657
+ const rows = db.select({ path: chunks.path }).from(chunks).where(and(eq(chunks.repo_id, repoId), sql`INSTR(LOWER(${chunks.content}), ${searchString.toLowerCase()}) > 0`)).groupBy(chunks.path).all();
10658
+ return rows.map((r) => r.path);
10659
+ },
9985
10660
  getStats(db, repoId) {
9986
10661
  const row = db.select({
9987
10662
  chunk_count: sql`count(*)`,
@@ -10328,6 +11003,23 @@ var init_dist = __esm({
10328
11003
  };
10329
11004
  }
10330
11005
  });
11006
+ slicer_exports = {};
11007
+ __export2(slicer_exports, {
11008
+ sliceContext: () => sliceContext
11009
+ });
11010
+ init_slicer = __esm2({
11011
+ "src/context/slicer.ts"() {
11012
+ "use strict";
11013
+ init_queries();
11014
+ RADIUS = 10;
11015
+ MAX_SLICES = {
11016
+ symbol: 1,
11017
+ error_message: 1,
11018
+ stack_trace: 2,
11019
+ natural: 3
11020
+ };
11021
+ }
11022
+ });
10331
11023
  init_queries();
10332
11024
  init_queries();
10333
11025
  init_queries();
@@ -10476,6 +11168,40 @@ var init_dist = __esm({
10476
11168
  init_queries();
10477
11169
  MAX_CHUNKS_PER_REPO = 1e5;
10478
11170
  locks = /* @__PURE__ */ new Map();
11171
+ TOKEN_CAP = 2e3;
11172
+ LANG_EXT = {
11173
+ ts: "typescript",
11174
+ tsx: "typescript",
11175
+ js: "javascript",
11176
+ jsx: "javascript",
11177
+ py: "python",
11178
+ rb: "ruby",
11179
+ go: "go",
11180
+ rs: "rust",
11181
+ java: "java",
11182
+ kt: "kotlin",
11183
+ cs: "csharp",
11184
+ cpp: "cpp",
11185
+ c: "c",
11186
+ h: "c",
11187
+ swift: "swift",
11188
+ php: "php",
11189
+ sql: "sql",
11190
+ sh: "shell"
11191
+ };
11192
+ FRAME_PATTERNS = [
11193
+ // JS/TS: at fn (path:line:col) or at path:line:col
11194
+ /at\s+(?:\S+\s+\()?([^():]+):(\d+):\d+\)?/g,
11195
+ // Python: File "path", line N
11196
+ /File\s+"([^"]+)",\s+line\s+(\d+)/g,
11197
+ // C#/Java: at namespace(path:line)
11198
+ /at\s+\S+\(([^():]+):(\d+)\)/g
11199
+ ];
11200
+ CASING_BOUNDARY = /[a-z][A-Z]|_/;
11201
+ SYMBOL_RE = /^[a-zA-Z_]\w+$/;
11202
+ ERROR_CODE_RE = /\b[A-Z][A-Z0-9_]{2,}\b/;
11203
+ ERROR_SUFFIX_RE = /\w+(Exception|Error)\b/;
11204
+ ERROR_PREFIX_RE = /\b(TypeError|ReferenceError|SyntaxError|RangeError|Error|panic|FATAL|WARN|ERR):/;
10479
11205
  STOPWORDS = /* @__PURE__ */ new Set([
10480
11206
  "the",
10481
11207
  "a",
@@ -10577,6 +11303,139 @@ var init_dist = __esm({
10577
11303
  "mock",
10578
11304
  "module"
10579
11305
  ]);
11306
+ STOPWORDS2 = /* @__PURE__ */ new Set([
11307
+ "the",
11308
+ "a",
11309
+ "an",
11310
+ "is",
11311
+ "are",
11312
+ "was",
11313
+ "were",
11314
+ "be",
11315
+ "been",
11316
+ "being",
11317
+ "have",
11318
+ "has",
11319
+ "had",
11320
+ "do",
11321
+ "does",
11322
+ "did",
11323
+ "will",
11324
+ "would",
11325
+ "could",
11326
+ "should",
11327
+ "may",
11328
+ "might",
11329
+ "can",
11330
+ "need",
11331
+ "must",
11332
+ "to",
11333
+ "of",
11334
+ "in",
11335
+ "for",
11336
+ "on",
11337
+ "with",
11338
+ "at",
11339
+ "by",
11340
+ "from",
11341
+ "as",
11342
+ "into",
11343
+ "through",
11344
+ "and",
11345
+ "but",
11346
+ "or",
11347
+ "not",
11348
+ "no",
11349
+ "so",
11350
+ "if",
11351
+ "then",
11352
+ "than",
11353
+ "that",
11354
+ "this",
11355
+ "it",
11356
+ "its",
11357
+ "all",
11358
+ "each",
11359
+ "every",
11360
+ "any",
11361
+ "some",
11362
+ "how",
11363
+ "what",
11364
+ "which",
11365
+ "who",
11366
+ "when",
11367
+ "where",
11368
+ "why",
11369
+ "get",
11370
+ "set",
11371
+ "new",
11372
+ "null",
11373
+ "true",
11374
+ "false",
11375
+ "void",
11376
+ "type",
11377
+ "var",
11378
+ "let",
11379
+ "const",
11380
+ "return",
11381
+ "import",
11382
+ "export",
11383
+ "default",
11384
+ "class",
11385
+ "function",
11386
+ "string",
11387
+ "number",
11388
+ "boolean",
11389
+ "object",
11390
+ "array",
11391
+ "index",
11392
+ "data",
11393
+ "value",
11394
+ "result",
11395
+ "item",
11396
+ "list",
11397
+ "name",
11398
+ "id",
11399
+ "key",
11400
+ "src",
11401
+ "lib",
11402
+ "app",
11403
+ "spec",
11404
+ "mock",
11405
+ "module",
11406
+ "work",
11407
+ "works",
11408
+ "working",
11409
+ "make",
11410
+ "makes",
11411
+ "use",
11412
+ "uses",
11413
+ "used",
11414
+ "using",
11415
+ "find",
11416
+ "run",
11417
+ "runs",
11418
+ "call",
11419
+ "calls",
11420
+ "look",
11421
+ "like",
11422
+ "just",
11423
+ "also",
11424
+ "about",
11425
+ "there",
11426
+ "here",
11427
+ "define",
11428
+ "defined",
11429
+ "handle",
11430
+ "handles",
11431
+ "add",
11432
+ "adds",
11433
+ "create",
11434
+ "serve",
11435
+ "does",
11436
+ "file",
11437
+ "files"
11438
+ ]);
10580
11439
  NOISE_EXTENSIONS = /* @__PURE__ */ new Set([
10581
11440
  ".md",
10582
11441
  ".json",
@@ -10611,6 +11470,7 @@ var init_dist = __esm({
10611
11470
  "node_modules/",
10612
11471
  "dist/",
10613
11472
  "build/",
11473
+ "publish/",
10614
11474
  "vendor/",
10615
11475
  "vendors/",
10616
11476
  "/scripts/",
@@ -10649,6 +11509,9 @@ var init_dist = __esm({
10649
11509
  api: ["controller", "endpoint", "route", "middleware", "interceptor"]
10650
11510
  };
10651
11511
  init_queries();
11512
+ SYMBOL_DEF_RE = /^.*?\b(?:function|def|fn|class|const|let|var|type|interface|struct|enum|export\s+(?:function|class|const|type|interface|default))\s+/;
11513
+ init_queries();
11514
+ TEST_PATTERN = /\.(?:test|spec)\.|__tests__\/|_test\./;
10652
11515
  init_queries();
10653
11516
  CACHE_TTL = 12e4;
10654
11517
  CACHE_MAX = 20;
@@ -10657,6 +11520,159 @@ var init_dist = __esm({
10657
11520
  _db = null;
10658
11521
  _raw = null;
10659
11522
  init_queries();
11523
+ GOLD_DATASET = [
11524
+ // --- natural (12) ---
11525
+ {
11526
+ id: "nat-01",
11527
+ query: "How does the context pack pipeline work?",
11528
+ kind: "natural",
11529
+ expected_files: [
11530
+ "packages/engine/src/context/context.ts",
11531
+ "packages/engine/src/context/formatter.ts",
11532
+ "packages/engine/src/context/query-interpreter.ts"
11533
+ ],
11534
+ expected_entry: "packages/engine/src/context/context.ts"
11535
+ },
11536
+ {
11537
+ id: "nat-02",
11538
+ query: "How does file indexing work?",
11539
+ kind: "natural",
11540
+ expected_files: [
11541
+ "packages/engine/src/index/engine.ts",
11542
+ "packages/engine/src/index/chunker.ts",
11543
+ "packages/engine/src/index/discovery.ts"
11544
+ ],
11545
+ expected_entry: "packages/engine/src/index/engine.ts"
11546
+ },
11547
+ {
11548
+ id: "nat-03",
11549
+ query: "How are imports resolved and the import graph built?",
11550
+ kind: "natural",
11551
+ expected_files: ["packages/engine/src/index/import-graph.ts", "packages/engine/src/index/imports.ts"],
11552
+ expected_entry: "packages/engine/src/index/import-graph.ts"
11553
+ },
11554
+ {
11555
+ id: "nat-04",
11556
+ query: "How does the daemon HTTP server handle requests?",
11557
+ kind: "natural",
11558
+ expected_files: ["apps/daemon/src/server.ts", "apps/daemon/src/index.ts"],
11559
+ expected_entry: "apps/daemon/src/server.ts"
11560
+ },
11561
+ {
11562
+ id: "nat-05",
11563
+ query: "How does the CLI register a repo?",
11564
+ kind: "natural",
11565
+ expected_files: ["packages/cli/src/commands/register.ts", "packages/engine/src/repo/repo.ts"],
11566
+ expected_entry: "packages/cli/src/commands/register.ts"
11567
+ },
11568
+ {
11569
+ id: "nat-06",
11570
+ query: "How does git history analysis and co-change detection work?",
11571
+ kind: "natural",
11572
+ expected_files: ["packages/engine/src/index/git-analysis.ts"],
11573
+ expected_entry: "packages/engine/src/index/git-analysis.ts"
11574
+ },
11575
+ {
11576
+ id: "nat-07",
11577
+ query: "How does the MCP stdio server work?",
11578
+ kind: "natural",
11579
+ expected_files: ["apps/daemon/src/mcp.ts"],
11580
+ expected_entry: "apps/daemon/src/mcp.ts"
11581
+ },
11582
+ {
11583
+ id: "nat-08",
11584
+ query: "How does TF-IDF scoring work in the query interpreter?",
11585
+ kind: "natural",
11586
+ expected_files: ["packages/engine/src/context/query-interpreter.ts"],
11587
+ expected_entry: "packages/engine/src/context/query-interpreter.ts"
11588
+ },
11589
+ {
11590
+ id: "nat-09",
11591
+ query: "How are file metadata (exports, docstrings, sections) extracted?",
11592
+ kind: "natural",
11593
+ expected_files: ["packages/engine/src/index/extract-metadata.ts"],
11594
+ expected_entry: "packages/engine/src/index/extract-metadata.ts"
11595
+ },
11596
+ {
11597
+ id: "nat-10",
11598
+ query: "How does vector/semantic search work?",
11599
+ kind: "natural",
11600
+ expected_files: ["packages/engine/src/context/vector.ts", "packages/engine/src/index/embed.ts"],
11601
+ expected_entry: "packages/engine/src/context/vector.ts"
11602
+ },
11603
+ {
11604
+ id: "nat-11",
11605
+ query: "How does the file watcher detect changes?",
11606
+ kind: "natural",
11607
+ expected_files: ["packages/engine/src/index/watcher.ts"],
11608
+ expected_entry: "packages/engine/src/index/watcher.ts"
11609
+ },
11610
+ {
11611
+ id: "nat-12",
11612
+ query: "How is the database schema defined?",
11613
+ kind: "natural",
11614
+ expected_files: ["packages/engine/src/db/schema.ts", "packages/engine/src/db/connection.ts"],
11615
+ expected_entry: "packages/engine/src/db/schema.ts"
11616
+ },
11617
+ // --- symbol (5) ---
11618
+ {
11619
+ id: "sym-01",
11620
+ query: "buildContext",
11621
+ kind: "symbol",
11622
+ expected_files: ["packages/engine/src/context/context.ts"],
11623
+ expected_entry: "packages/engine/src/context/context.ts"
11624
+ },
11625
+ {
11626
+ id: "sym-02",
11627
+ query: "interpretQuery",
11628
+ kind: "symbol",
11629
+ expected_files: ["packages/engine/src/context/query-interpreter.ts"],
11630
+ expected_entry: "packages/engine/src/context/query-interpreter.ts"
11631
+ },
11632
+ {
11633
+ id: "sym-03",
11634
+ query: "runIndex",
11635
+ kind: "symbol",
11636
+ expected_files: ["packages/engine/src/index/engine.ts"],
11637
+ expected_entry: "packages/engine/src/index/engine.ts"
11638
+ },
11639
+ {
11640
+ id: "sym-04",
11641
+ query: "extractFileMetadata",
11642
+ kind: "symbol",
11643
+ expected_files: ["packages/engine/src/index/extract-metadata.ts"],
11644
+ expected_entry: "packages/engine/src/index/extract-metadata.ts"
11645
+ },
11646
+ {
11647
+ id: "sym-05",
11648
+ query: "formatContextPack",
11649
+ kind: "symbol",
11650
+ expected_files: ["packages/engine/src/context/formatter.ts"],
11651
+ expected_entry: "packages/engine/src/context/formatter.ts"
11652
+ },
11653
+ // --- error_message (3) ---
11654
+ {
11655
+ id: "err-01",
11656
+ query: "Error: repo not found",
11657
+ kind: "error_message",
11658
+ expected_files: ["packages/engine/src/repo/repo.ts", "apps/daemon/src/server.ts"],
11659
+ expected_entry: "packages/engine/src/repo/repo.ts"
11660
+ },
11661
+ {
11662
+ id: "err-02",
11663
+ query: "LENS daemon is not running",
11664
+ kind: "error_message",
11665
+ expected_files: ["packages/cli/src/util/client.ts"],
11666
+ expected_entry: "packages/cli/src/util/client.ts"
11667
+ },
11668
+ {
11669
+ id: "err-03",
11670
+ query: "Context generation failed",
11671
+ kind: "error_message",
11672
+ expected_files: ["packages/engine/src/context/context.ts"],
11673
+ expected_entry: "packages/engine/src/context/context.ts"
11674
+ }
11675
+ ];
10660
11676
  init_queries();
10661
11677
  MAX_API_CALLS = 2e3;
10662
11678
  POOL_SIZE = 32;
@@ -10671,7 +11687,7 @@ var init_dist = __esm({
10671
11687
  MAX_TERMS = 1500;
10672
11688
  MAX_CLUSTERS = 200;
10673
11689
  MAX_CLUSTER_SIZE = 12;
10674
- STOPWORDS2 = /* @__PURE__ */ new Set([
11690
+ STOPWORDS3 = /* @__PURE__ */ new Set([
10675
11691
  "the",
10676
11692
  "a",
10677
11693
  "an",
@@ -10888,31 +11904,45 @@ var cloud_capabilities_exports = {};
10888
11904
  __export(cloud_capabilities_exports, {
10889
11905
  createCloudCapabilities: () => createCloudCapabilities
10890
11906
  });
10891
- function createCloudCapabilities(apiKey, trackUsage, logRequest) {
11907
+ function createCloudCapabilities(getKey, refreshKey, trackUsage, logRequest) {
10892
11908
  const CLOUD_API_URL = getCloudUrl();
10893
- const headers = {
10894
- Authorization: `Bearer ${apiKey}`,
10895
- "Content-Type": "application/json"
10896
- };
11909
+ async function cloudFetch(path, reqBody) {
11910
+ const apiKey = await getKey();
11911
+ if (!apiKey) throw new Error("No API key available");
11912
+ const start = performance.now();
11913
+ let res = await fetch(`${CLOUD_API_URL}${path}`, {
11914
+ method: "POST",
11915
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
11916
+ body: reqBody
11917
+ });
11918
+ let resText = await res.text();
11919
+ let duration3 = Math.round(performance.now() - start);
11920
+ logRequest?.("POST", path, res.status, duration3, "cloud", reqBody, resText);
11921
+ if (res.status === 401) {
11922
+ const newKey = await refreshKey();
11923
+ if (newKey) {
11924
+ const retryStart = performance.now();
11925
+ res = await fetch(`${CLOUD_API_URL}${path}`, {
11926
+ method: "POST",
11927
+ headers: { Authorization: `Bearer ${newKey}`, "Content-Type": "application/json" },
11928
+ body: reqBody
11929
+ });
11930
+ resText = await res.text();
11931
+ duration3 = Math.round(performance.now() - retryStart);
11932
+ logRequest?.("POST", path, res.status, duration3, "cloud-retry", reqBody, resText);
11933
+ }
11934
+ }
11935
+ return { res, resText };
11936
+ }
10897
11937
  return {
10898
11938
  async embedTexts(texts, isQuery) {
10899
- const start = performance.now();
10900
11939
  const reqBody = JSON.stringify({
10901
11940
  input: texts,
10902
11941
  model: "voyage-code-3",
10903
11942
  input_type: isQuery ? "query" : "document"
10904
11943
  });
10905
- const res = await fetch(`${CLOUD_API_URL}/api/proxy/embed`, {
10906
- method: "POST",
10907
- headers,
10908
- body: reqBody
10909
- });
10910
- const resText = await res.text();
10911
- const duration3 = Math.round(performance.now() - start);
10912
- logRequest?.("POST", "/api/proxy/embed", res.status, duration3, "cloud", reqBody, resText);
10913
- if (!res.ok) {
10914
- throw new Error(`Cloud embed failed (${res.status}): ${resText}`);
10915
- }
11944
+ const { res, resText } = await cloudFetch("/api/proxy/embed", reqBody);
11945
+ if (!res.ok) throw new Error(`Cloud embed failed (${res.status}): ${resText}`);
10916
11946
  const data = JSON.parse(resText);
10917
11947
  trackUsage?.("embedding_requests");
10918
11948
  trackUsage?.("embedding_chunks", texts.length);
@@ -10926,7 +11956,6 @@ function createCloudCapabilities(apiKey, trackUsage, logRequest) {
10926
11956
  "",
10927
11957
  content.slice(0, 2e3)
10928
11958
  ].filter(Boolean).join("\n");
10929
- const start = performance.now();
10930
11959
  const reqBody = JSON.stringify({
10931
11960
  messages: [
10932
11961
  {
@@ -10937,17 +11966,8 @@ function createCloudCapabilities(apiKey, trackUsage, logRequest) {
10937
11966
  ],
10938
11967
  max_tokens: 128
10939
11968
  });
10940
- const res = await fetch(`${CLOUD_API_URL}/api/proxy/chat`, {
10941
- method: "POST",
10942
- headers,
10943
- body: reqBody
10944
- });
10945
- const resText = await res.text();
10946
- const duration3 = Math.round(performance.now() - start);
10947
- logRequest?.("POST", "/api/proxy/chat", res.status, duration3, "cloud", reqBody, resText);
10948
- if (!res.ok) {
10949
- throw new Error(`Cloud purpose failed (${res.status}): ${resText}`);
10950
- }
11969
+ const { res, resText } = await cloudFetch("/api/proxy/chat", reqBody);
11970
+ if (!res.ok) throw new Error(`Cloud purpose failed (${res.status}): ${resText}`);
10951
11971
  const data = JSON.parse(resText);
10952
11972
  trackUsage?.("purpose_requests");
10953
11973
  return data.choices?.[0]?.message?.content?.trim() ?? "";
@@ -15670,7 +16690,7 @@ function flattenError(error3, mapper = (issue2) => issue2.message) {
15670
16690
  }
15671
16691
  return { formErrors, fieldErrors };
15672
16692
  }
15673
- function formatError(error3, _mapper) {
16693
+ function formatError2(error3, _mapper) {
15674
16694
  const mapper = _mapper || function(issue2) {
15675
16695
  return issue2.message;
15676
16696
  };
@@ -19316,7 +20336,7 @@ var init_errors4 = __esm({
19316
20336
  inst.name = "ZodError";
19317
20337
  Object.defineProperties(inst, {
19318
20338
  format: {
19319
- value: (mapper) => formatError(inst, mapper)
20339
+ value: (mapper) => formatError2(inst, mapper)
19320
20340
  // enumerable: false,
19321
20341
  },
19322
20342
  flatten: {
@@ -32426,7 +33446,7 @@ function createMcpServer(db, caps) {
32426
33446
  server.registerTool(
32427
33447
  "get_context",
32428
33448
  {
32429
- description: "Prefer this tool over Grep/Glob when searching for files relevant to a task across the codebase. Returns ranked files, imports, and co-change clusters for a development goal. Skip for simple lookups where you already know the file path or location.",
33449
+ description: "Returns ranked files with import dependency chains and git co-change clusters for a development goal. Surfaces structural relationships (imports, co-changes, exports) that keyword search alone won't find. Use when exploring architecture, tracing data flow, assessing change impact, or navigating an unfamiliar codebase. Skip for simple lookups where you already know the file path.",
32430
33450
  inputSchema: { repo_path: external_exports.string(), goal: external_exports.string() }
32431
33451
  },
32432
33452
  async ({ repo_path, goal }) => {
@@ -34986,21 +36006,6 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
34986
36006
  const trace = c.get("trace");
34987
36007
  const { root_path, name, remote_url } = await c.req.json();
34988
36008
  if (!root_path) return c.json({ error: "root_path required" }, 400);
34989
- trace.step("quotaCheck");
34990
- const currentRepos = listRepos(db).length;
34991
- const maxRepos = quotaCache?.quota?.maxRepos ?? 50;
34992
- trace.end("quotaCheck", `${currentRepos}/${maxRepos}`);
34993
- if (currentRepos >= maxRepos) {
34994
- return c.json(
34995
- {
34996
- error: "Repo limit reached",
34997
- current: currentRepos,
34998
- limit: maxRepos,
34999
- plan: quotaCache?.plan ?? "unknown"
35000
- },
35001
- 429
35002
- );
35003
- }
35004
36009
  trace.step("registerRepo");
35005
36010
  const result = registerRepo(db, root_path, name, remote_url);
35006
36011
  trace.end("registerRepo", result.created ? "created" : "existing");
@@ -35190,6 +36195,20 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
35190
36195
  return c.json({ error: e.message }, 500);
35191
36196
  }
35192
36197
  });
36198
+ trackRoute("POST", "/eval/run");
36199
+ app.post("/eval/run", async (c) => {
36200
+ try {
36201
+ const trace = c.get("trace");
36202
+ const { repo_id, filter_kind } = await c.req.json();
36203
+ if (!repo_id) return c.json({ error: "repo_id required" }, 400);
36204
+ trace.step("runEval");
36205
+ const summary = await runEval(db, repo_id, { filterKind: filter_kind });
36206
+ trace.end("runEval", `${summary.total} queries`);
36207
+ return c.json(summary);
36208
+ } catch (e) {
36209
+ return c.json({ error: e.message }, 500);
36210
+ }
36211
+ });
35193
36212
  trackRoute("POST", "/index/run");
35194
36213
  app.post("/index/run", async (c) => {
35195
36214
  try {
@@ -35396,24 +36415,34 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
35396
36415
  expires_at: Math.floor(Date.now() / 1e3) + (data.expires_in ?? 3600)
35397
36416
  };
35398
36417
  }
35399
- const repoClients = /* @__PURE__ */ new Set();
36418
+ const sseClients = /* @__PURE__ */ new Set();
35400
36419
  const encoder = new TextEncoder();
35401
- function emitRepoEvent() {
35402
- for (const ctrl of repoClients) {
36420
+ function emitSSE(type) {
36421
+ const payload = encoder.encode(`data: ${JSON.stringify({ type })}
36422
+
36423
+ `);
36424
+ for (const ctrl of sseClients) {
35403
36425
  try {
35404
- ctrl.enqueue(encoder.encode("data: repo-changed\n\n"));
36426
+ ctrl.enqueue(payload);
35405
36427
  } catch {
35406
- repoClients.delete(ctrl);
36428
+ sseClients.delete(ctrl);
35407
36429
  }
35408
36430
  }
35409
36431
  }
35410
- trackRoute("GET", "/api/repo/events");
35411
- app.get("/api/repo/events", (_c) => {
36432
+ function emitRepoEvent() {
36433
+ emitSSE("repo");
36434
+ }
36435
+ function emitAuthEvent() {
36436
+ emitSSE("auth");
36437
+ }
36438
+ trackRoute("GET", "/api/events");
36439
+ app.get("/api/events", (_c) => {
35412
36440
  const stream = new ReadableStream({
35413
36441
  start(ctrl) {
35414
- repoClients.add(ctrl);
36442
+ sseClients.add(ctrl);
35415
36443
  },
35416
- cancel() {
36444
+ cancel(ctrl) {
36445
+ sseClients.delete(ctrl);
35417
36446
  }
35418
36447
  });
35419
36448
  return new Response(stream, {
@@ -35424,17 +36453,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
35424
36453
  }
35425
36454
  });
35426
36455
  });
35427
- const authClients = /* @__PURE__ */ new Set();
35428
36456
  const authDir = join5(homedir3(), ".lens");
35429
- function emitAuthEvent() {
35430
- for (const ctrl of authClients) {
35431
- try {
35432
- ctrl.enqueue(encoder.encode("data: auth-changed\n\n"));
35433
- } catch {
35434
- authClients.delete(ctrl);
35435
- }
35436
- }
35437
- }
35438
36457
  try {
35439
36458
  watch2(authDir, (_, filename) => {
35440
36459
  if (filename && filename !== "auth.json") return;
@@ -35442,23 +36461,6 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
35442
36461
  });
35443
36462
  } catch {
35444
36463
  }
35445
- trackRoute("GET", "/api/auth/events");
35446
- app.get("/api/auth/events", (_c) => {
35447
- const stream = new ReadableStream({
35448
- start(ctrl) {
35449
- authClients.add(ctrl);
35450
- },
35451
- cancel() {
35452
- }
35453
- });
35454
- return new Response(stream, {
35455
- headers: {
35456
- "Content-Type": "text/event-stream",
35457
- "Cache-Control": "no-cache",
35458
- Connection: "keep-alive"
35459
- }
35460
- });
35461
- });
35462
36464
  trackRoute("POST", "/api/auth/notify");
35463
36465
  app.post("/api/auth/notify", async (c) => {
35464
36466
  await refreshQuotaCache();
@@ -35995,7 +36997,8 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
35995
36997
  if (apiKey) {
35996
36998
  const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
35997
36999
  caps = createCloudCapabilities2(
35998
- apiKey,
37000
+ () => readApiKey(),
37001
+ provisionApiKey,
35999
37002
  (counter, amount) => {
36000
37003
  try {
36001
37004
  usageQueries.increment(db, counter, amount);
@@ -36082,10 +37085,9 @@ var init_server3 = __esm({
36082
37085
  TEMPLATE = `<!-- LENS \u2014 Repo Context Daemon -->
36083
37086
  ## LENS Context
36084
37087
 
36085
- This repo is indexed by LENS. Use the MCP context tool or run:
36086
- \`\`\`
36087
- lens context "<your goal>"
36088
- \`\`\``;
37088
+ This repo is indexed by LENS. When exploring architecture, tracing data flow, or assessing change impact, call \`mcp__lens__get_context\` \u2014 it returns ranked files with import chains and co-change clusters that keyword search alone won't surface.
37089
+
37090
+ CLI alternative: \`lens context "<your goal>"\``;
36089
37091
  startedAt = Date.now();
36090
37092
  MIME_TYPES = {
36091
37093
  ".html": "text/html",
@@ -36678,6 +37680,7 @@ import { join as join6 } from "path";
36678
37680
  var LENS_DIR = join6(homedir4(), ".lens");
36679
37681
  var PID_FILE = join6(LENS_DIR, "daemon.pid");
36680
37682
  var LOG_FILE = join6(LENS_DIR, "daemon.log");
37683
+ var AUTH_FILE = join6(LENS_DIR, "auth.json");
36681
37684
  function writePid() {
36682
37685
  writeFileSync3(PID_FILE, String(process.pid));
36683
37686
  }
@@ -36687,76 +37690,97 @@ function removePid() {
36687
37690
  } catch {
36688
37691
  }
36689
37692
  }
36690
- async function ensureApiKey(data) {
36691
- if (data.api_key) return data.api_key;
36692
- if (!data.access_token) return void 0;
36693
- const cloudUrl = getCloudUrl();
37693
+ function readApiKeyFromDisk() {
36694
37694
  try {
37695
+ const data = JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
37696
+ return data.api_key || null;
37697
+ } catch {
37698
+ return null;
37699
+ }
37700
+ }
37701
+ async function provisionNewKey() {
37702
+ try {
37703
+ const data = JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
37704
+ if (!data.access_token) return null;
37705
+ const cloudUrl = getCloudUrl();
36695
37706
  const res = await fetch(`${cloudUrl}/auth/key`, {
36696
37707
  headers: { Authorization: `Bearer ${data.access_token}` }
36697
37708
  });
36698
- if (!res.ok) {
36699
- console.error(`[LENS] API key provisioning failed (${res.status})`);
36700
- return void 0;
36701
- }
37709
+ if (!res.ok) return null;
36702
37710
  const { api_key } = await res.json();
36703
37711
  data.api_key = api_key;
36704
- const authPath = join6(homedir4(), ".lens", "auth.json");
36705
- writeFileSync3(authPath, JSON.stringify(data, null, 2), { mode: 384 });
36706
- console.error("[LENS] API key auto-provisioned");
37712
+ writeFileSync3(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
37713
+ console.error("[LENS] API key re-provisioned");
36707
37714
  return api_key;
36708
- } catch (e) {
36709
- console.error(`[LENS] API key provisioning error: ${e?.message}`);
36710
- return void 0;
37715
+ } catch {
37716
+ return null;
36711
37717
  }
36712
37718
  }
36713
37719
  async function loadCapabilities(db) {
36714
37720
  try {
36715
- const authPath = join6(homedir4(), ".lens", "auth.json");
36716
- const data = JSON.parse(readFileSync3(authPath, "utf-8"));
36717
- const apiKey = await ensureApiKey(data);
36718
- if (!apiKey) return {};
37721
+ let apiKey = readApiKeyFromDisk();
37722
+ if (!apiKey) {
37723
+ apiKey = await provisionNewKey();
37724
+ if (!apiKey) return {};
37725
+ }
36719
37726
  const cloudUrl = getCloudUrl();
36720
37727
  const res = await fetch(`${cloudUrl}/api/usage/current`, {
36721
37728
  headers: { Authorization: `Bearer ${apiKey}` }
36722
37729
  });
37730
+ if (res.status === 401) {
37731
+ apiKey = await provisionNewKey();
37732
+ if (!apiKey) return {};
37733
+ const retry = await fetch(`${cloudUrl}/api/usage/current`, {
37734
+ headers: { Authorization: `Bearer ${apiKey}` }
37735
+ });
37736
+ if (!retry.ok) {
37737
+ console.error(`[LENS] Plan check failed after re-provision (${retry.status})`);
37738
+ return {};
37739
+ }
37740
+ const usageData2 = await retry.json();
37741
+ return buildCapsResult(db, usageData2);
37742
+ }
36723
37743
  if (!res.ok) {
36724
37744
  console.error(`[LENS] Plan check failed (${res.status}), capabilities disabled`);
36725
37745
  return {};
36726
37746
  }
36727
37747
  const usageData = await res.json();
36728
- const planData = {
36729
- plan: usageData.plan ?? "free",
36730
- usage: usageData.usage ?? {},
36731
- quota: usageData.quota ?? {}
36732
- };
36733
- if (usageData.plan !== "pro") {
36734
- console.error(`[LENS] Plan: ${usageData.plan ?? "free"} \u2014 Pro features disabled`);
36735
- return { planData };
36736
- }
36737
- const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
36738
- const { usageQueries: usageQueries2, logQueries: logQueries2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
36739
- const caps = createCloudCapabilities2(
36740
- apiKey,
36741
- (counter, amount) => {
36742
- try {
36743
- usageQueries2.increment(db, counter, amount);
36744
- } catch {
36745
- }
36746
- },
36747
- (method, path, status, duration3, source, reqBody, resBody) => {
36748
- try {
36749
- logQueries2.insert(db, method, path, status, duration3, source, reqBody, resBody?.length, resBody);
36750
- } catch {
36751
- }
36752
- }
36753
- );
36754
- console.error("[LENS] Cloud capabilities enabled (Pro plan)");
36755
- return { caps, planData };
37748
+ return buildCapsResult(db, usageData);
36756
37749
  } catch {
36757
37750
  return {};
36758
37751
  }
36759
37752
  }
37753
+ async function buildCapsResult(db, usageData) {
37754
+ const planData = {
37755
+ plan: usageData.plan ?? "free",
37756
+ usage: usageData.usage ?? {},
37757
+ quota: usageData.quota ?? {}
37758
+ };
37759
+ if (usageData.plan !== "pro") {
37760
+ console.error(`[LENS] Plan: ${usageData.plan ?? "free"} \u2014 Pro features disabled`);
37761
+ return { planData };
37762
+ }
37763
+ const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
37764
+ const { usageQueries: usageQueries2, logQueries: logQueries2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
37765
+ const caps = createCloudCapabilities2(
37766
+ () => Promise.resolve(readApiKeyFromDisk()),
37767
+ provisionNewKey,
37768
+ (counter, amount) => {
37769
+ try {
37770
+ usageQueries2.increment(db, counter, amount);
37771
+ } catch {
37772
+ }
37773
+ },
37774
+ (method, path, status, duration3, source, reqBody, resBody) => {
37775
+ try {
37776
+ logQueries2.insert(db, method, path, status, duration3, source, reqBody, resBody?.length, resBody);
37777
+ } catch {
37778
+ }
37779
+ }
37780
+ );
37781
+ console.error("[LENS] Cloud capabilities enabled (Pro plan)");
37782
+ return { caps, planData };
37783
+ }
36760
37784
  async function main() {
36761
37785
  const db = openDb();
36762
37786
  const { caps, planData } = await loadCapabilities(db);