sverklo 0.2.12 → 0.2.14

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.
Files changed (116) hide show
  1. package/README.md +102 -13
  2. package/dist/bin/sverklo.js +69 -1
  3. package/dist/bin/sverklo.js.map +1 -1
  4. package/dist/src/audit-prompt.d.ts +2 -0
  5. package/dist/src/audit-prompt.js +91 -0
  6. package/dist/src/audit-prompt.js.map +1 -0
  7. package/dist/src/doctor.js +55 -0
  8. package/dist/src/doctor.js.map +1 -1
  9. package/dist/src/indexer/embedding-providers.d.ts +34 -0
  10. package/dist/src/indexer/embedding-providers.js +215 -0
  11. package/dist/src/indexer/embedding-providers.js.map +1 -0
  12. package/dist/src/indexer/embedding-providers.test.d.ts +1 -0
  13. package/dist/src/indexer/embedding-providers.test.js +83 -0
  14. package/dist/src/indexer/embedding-providers.test.js.map +1 -0
  15. package/dist/src/indexer/indexer-freshness.test.d.ts +1 -0
  16. package/dist/src/indexer/indexer-freshness.test.js +109 -0
  17. package/dist/src/indexer/indexer-freshness.test.js.map +1 -0
  18. package/dist/src/indexer/indexer-provider-integration.test.d.ts +1 -0
  19. package/dist/src/indexer/indexer-provider-integration.test.js +142 -0
  20. package/dist/src/indexer/indexer-provider-integration.test.js.map +1 -0
  21. package/dist/src/indexer/indexer.d.ts +44 -0
  22. package/dist/src/indexer/indexer.js +153 -10
  23. package/dist/src/indexer/indexer.js.map +1 -1
  24. package/dist/src/indexer/migration.test.d.ts +1 -0
  25. package/dist/src/indexer/migration.test.js +144 -0
  26. package/dist/src/indexer/migration.test.js.map +1 -0
  27. package/dist/src/indexer/symbol-extractor.js +17 -5
  28. package/dist/src/indexer/symbol-extractor.js.map +1 -1
  29. package/dist/src/indexer/symbol-extractor.test.d.ts +1 -0
  30. package/dist/src/indexer/symbol-extractor.test.js +88 -0
  31. package/dist/src/indexer/symbol-extractor.test.js.map +1 -0
  32. package/dist/src/indexer/watcher.js +4 -0
  33. package/dist/src/indexer/watcher.js.map +1 -1
  34. package/dist/src/memory/journal.d.ts +62 -0
  35. package/dist/src/memory/journal.js +136 -0
  36. package/dist/src/memory/journal.js.map +1 -0
  37. package/dist/src/memory/journal.test.d.ts +1 -0
  38. package/dist/src/memory/journal.test.js +98 -0
  39. package/dist/src/memory/journal.test.js.map +1 -0
  40. package/dist/src/modes.d.ts +16 -0
  41. package/dist/src/modes.js +104 -0
  42. package/dist/src/modes.js.map +1 -0
  43. package/dist/src/modes.test.d.ts +1 -0
  44. package/dist/src/modes.test.js +48 -0
  45. package/dist/src/modes.test.js.map +1 -0
  46. package/dist/src/search/hybrid-search.d.ts +22 -0
  47. package/dist/src/search/hybrid-search.js +169 -7
  48. package/dist/src/search/hybrid-search.js.map +1 -1
  49. package/dist/src/search/hybrid-search.test.d.ts +1 -0
  50. package/dist/src/search/hybrid-search.test.js +112 -0
  51. package/dist/src/search/hybrid-search.test.js.map +1 -0
  52. package/dist/src/search/token-budget.d.ts +1 -0
  53. package/dist/src/search/token-budget.js +34 -6
  54. package/dist/src/search/token-budget.js.map +1 -1
  55. package/dist/src/server/mcp-server.js +32 -25
  56. package/dist/src/server/mcp-server.js.map +1 -1
  57. package/dist/src/server/tool-overrides.d.ts +16 -0
  58. package/dist/src/server/tool-overrides.js +102 -0
  59. package/dist/src/server/tool-overrides.js.map +1 -0
  60. package/dist/src/server/tool-overrides.test.d.ts +1 -0
  61. package/dist/src/server/tool-overrides.test.js +124 -0
  62. package/dist/src/server/tool-overrides.test.js.map +1 -0
  63. package/dist/src/server/tools/context-budget.test.d.ts +1 -0
  64. package/dist/src/server/tools/context-budget.test.js +150 -0
  65. package/dist/src/server/tools/context-budget.test.js.map +1 -0
  66. package/dist/src/server/tools/context.d.ts +11 -1
  67. package/dist/src/server/tools/context.js +202 -8
  68. package/dist/src/server/tools/context.js.map +1 -1
  69. package/dist/src/server/tools/diff-heuristics.d.ts +28 -0
  70. package/dist/src/server/tools/diff-heuristics.js +213 -0
  71. package/dist/src/server/tools/diff-heuristics.js.map +1 -0
  72. package/dist/src/server/tools/diff-heuristics.test.d.ts +1 -0
  73. package/dist/src/server/tools/diff-heuristics.test.js +151 -0
  74. package/dist/src/server/tools/diff-heuristics.test.js.map +1 -0
  75. package/dist/src/server/tools/find-references.d.ts +4 -0
  76. package/dist/src/server/tools/find-references.js +46 -6
  77. package/dist/src/server/tools/find-references.js.map +1 -1
  78. package/dist/src/server/tools/find-references.test.d.ts +1 -0
  79. package/dist/src/server/tools/find-references.test.js +98 -0
  80. package/dist/src/server/tools/find-references.test.js.map +1 -0
  81. package/dist/src/server/tools/forget.js +3 -0
  82. package/dist/src/server/tools/forget.js.map +1 -1
  83. package/dist/src/server/tools/index-status.js +16 -0
  84. package/dist/src/server/tools/index-status.js.map +1 -1
  85. package/dist/src/server/tools/lookup.js +21 -13
  86. package/dist/src/server/tools/lookup.js.map +1 -1
  87. package/dist/src/server/tools/lookup.test.d.ts +1 -0
  88. package/dist/src/server/tools/lookup.test.js +110 -0
  89. package/dist/src/server/tools/lookup.test.js.map +1 -0
  90. package/dist/src/server/tools/recall.d.ts +5 -1
  91. package/dist/src/server/tools/recall.js +68 -6
  92. package/dist/src/server/tools/recall.js.map +1 -1
  93. package/dist/src/server/tools/recall.test.d.ts +1 -0
  94. package/dist/src/server/tools/recall.test.js +116 -0
  95. package/dist/src/server/tools/recall.test.js.map +1 -0
  96. package/dist/src/server/tools/remember.js +16 -2
  97. package/dist/src/server/tools/remember.js.map +1 -1
  98. package/dist/src/server/tools/review-diff.js +31 -0
  99. package/dist/src/server/tools/review-diff.js.map +1 -1
  100. package/dist/src/server/tools/search.js +29 -4
  101. package/dist/src/server/tools/search.js.map +1 -1
  102. package/dist/src/server/tools/search.test.d.ts +1 -0
  103. package/dist/src/server/tools/search.test.js +118 -0
  104. package/dist/src/server/tools/search.test.js.map +1 -0
  105. package/dist/src/server/tools/tier.js +25 -1
  106. package/dist/src/server/tools/tier.js.map +1 -1
  107. package/dist/src/storage/chunk-store.d.ts +12 -0
  108. package/dist/src/storage/chunk-store.js +16 -0
  109. package/dist/src/storage/chunk-store.js.map +1 -1
  110. package/dist/src/storage/chunk-store.test.d.ts +1 -0
  111. package/dist/src/storage/chunk-store.test.js +69 -0
  112. package/dist/src/storage/chunk-store.test.js.map +1 -0
  113. package/dist/src/storage/database.d.ts +13 -0
  114. package/dist/src/storage/database.js +47 -0
  115. package/dist/src/storage/database.js.map +1 -1
  116. package/package.json +5 -2
@@ -0,0 +1,151 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { findUnguardedStreamCalls, parseUnifiedDiff, runAllHeuristics, } from "./diff-heuristics.js";
3
+ // Regression tests for github.com/sverklo/sverklo/issues/5 — the diff
4
+ // review case that previously missed a class of production risk: a new
5
+ // call site introduced inside a stream pipeline with no enclosing
6
+ // try-catch. The heuristic is a proxy for the formal AST check we
7
+ // eventually want, and these tests lock in its behavior on the shapes
8
+ // we care about most.
9
+ describe("findUnguardedStreamCalls", () => {
10
+ it("flags a new call added inside a .map() without try-catch", () => {
11
+ // Realistic-shaped diff hunk: a production read path gains a new
12
+ // helper call inside its stream pipeline. No try-catch anywhere
13
+ // in the surrounding context — this is the bug we previously missed.
14
+ const diffText = [
15
+ "diff --git a/Service.java b/Service.java",
16
+ "--- a/Service.java",
17
+ "+++ b/Service.java",
18
+ "@@ -10,5 +10,6 @@",
19
+ " public List<Package> getAll() {",
20
+ " return repo.findAll().stream()",
21
+ " .map(p -> {",
22
+ "+ var feat = computeFeatures(p);",
23
+ " return toDto(p);",
24
+ " })",
25
+ " .collect(Collectors.toList());",
26
+ " }",
27
+ ].join("\n");
28
+ const hunks = parseUnifiedDiff(diffText);
29
+ expect(hunks.length).toBeGreaterThan(0);
30
+ const findings = findUnguardedStreamCalls(hunks);
31
+ expect(findings.length).toBe(1);
32
+ expect(findings[0].heuristic).toBe("unguarded-stream-call");
33
+ expect(findings[0].file).toBe("Service.java");
34
+ expect(findings[0].snippet).toContain("computeFeatures");
35
+ });
36
+ it("does NOT flag when a try-catch is visible in the hunk context", () => {
37
+ // Same pattern, but the enclosing method catches its own exceptions.
38
+ // The heuristic should back off — not perfect, but no false positive.
39
+ const diffText = [
40
+ "diff --git a/Service.java b/Service.java",
41
+ "--- a/Service.java",
42
+ "+++ b/Service.java",
43
+ "@@ -10,7 +10,8 @@",
44
+ " public List<Package> getAll() {",
45
+ " try {",
46
+ " return repo.findAll().stream()",
47
+ " .map(p -> {",
48
+ "+ var feat = computeFeatures(p);",
49
+ " return toDto(p);",
50
+ " })",
51
+ " .collect(Collectors.toList());",
52
+ " } catch (Exception e) { return List.of(); }",
53
+ " }",
54
+ ].join("\n");
55
+ const hunks = parseUnifiedDiff(diffText);
56
+ const findings = findUnguardedStreamCalls(hunks);
57
+ expect(findings.length).toBe(0);
58
+ });
59
+ it("does not flag added lines outside a stream pipeline", () => {
60
+ const diffText = [
61
+ "diff --git a/Util.ts b/Util.ts",
62
+ "--- a/Util.ts",
63
+ "+++ b/Util.ts",
64
+ "@@ -1,5 +1,6 @@",
65
+ " export function loadConfig() {",
66
+ " const raw = readFileSync('config.json');",
67
+ "+ const parsed = JSON.parse(raw);",
68
+ " return raw;",
69
+ " }",
70
+ ].join("\n");
71
+ const hunks = parseUnifiedDiff(diffText);
72
+ const findings = findUnguardedStreamCalls(hunks);
73
+ expect(findings.length).toBe(0);
74
+ });
75
+ it("flags at most once per stream block", () => {
76
+ // Ten new calls inside one .map() should produce exactly one
77
+ // finding, not ten. Noise control.
78
+ const diffText = [
79
+ "diff --git a/Batch.ts b/Batch.ts",
80
+ "--- a/Batch.ts",
81
+ "+++ b/Batch.ts",
82
+ "@@ -1,5 +1,14 @@",
83
+ " function processAll(items: Item[]) {",
84
+ " return items.map(it => {",
85
+ "+ const a = transformA(it);",
86
+ "+ const b = transformB(it);",
87
+ "+ const c = transformC(it);",
88
+ "+ const d = transformD(it);",
89
+ " return it;",
90
+ " });",
91
+ " }",
92
+ ].join("\n");
93
+ const hunks = parseUnifiedDiff(diffText);
94
+ const findings = findUnguardedStreamCalls(hunks);
95
+ expect(findings.length).toBe(1);
96
+ });
97
+ it("handles .forEach and .flatMap the same way as .map", () => {
98
+ const diffText = [
99
+ "diff --git a/Listener.ts b/Listener.ts",
100
+ "--- a/Listener.ts",
101
+ "+++ b/Listener.ts",
102
+ "@@ -1,4 +1,5 @@",
103
+ " function broadcast(events: Event[]) {",
104
+ " events.forEach(e => {",
105
+ "+ publishToQueue(e);",
106
+ " });",
107
+ " }",
108
+ ].join("\n");
109
+ const hunks = parseUnifiedDiff(diffText);
110
+ const findings = findUnguardedStreamCalls(hunks);
111
+ expect(findings.length).toBe(1);
112
+ expect(findings[0].snippet).toContain("publishToQueue");
113
+ });
114
+ });
115
+ describe("runAllHeuristics", () => {
116
+ it("returns empty array for empty hunks", () => {
117
+ expect(runAllHeuristics([])).toEqual([]);
118
+ });
119
+ it("does not throw if a hunk has malformed lines", () => {
120
+ // Sanity: a heuristic must never take down review even on weird input.
121
+ expect(() => runAllHeuristics([
122
+ {
123
+ filePath: "foo.ts",
124
+ oldStart: 1,
125
+ newStart: 1,
126
+ lines: ["not-a-valid-diff-line", "+ok"],
127
+ },
128
+ ])).not.toThrow();
129
+ });
130
+ });
131
+ describe("parseUnifiedDiff", () => {
132
+ it("extracts file path and hunk ranges from a standard git diff", () => {
133
+ const text = [
134
+ "diff --git a/src/foo.ts b/src/foo.ts",
135
+ "index abc..def 100644",
136
+ "--- a/src/foo.ts",
137
+ "+++ b/src/foo.ts",
138
+ "@@ -5,3 +5,4 @@",
139
+ " context",
140
+ "+added",
141
+ " context",
142
+ " context",
143
+ ].join("\n");
144
+ const hunks = parseUnifiedDiff(text);
145
+ expect(hunks.length).toBe(1);
146
+ expect(hunks[0].filePath).toBe("src/foo.ts");
147
+ expect(hunks[0].oldStart).toBe(5);
148
+ expect(hunks[0].newStart).toBe(5);
149
+ });
150
+ });
151
+ //# sourceMappingURL=diff-heuristics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-heuristics.test.js","sourceRoot":"","sources":["../../../../src/server/tools/diff-heuristics.test.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,10 @@ export declare const findReferencesTool: {
9
9
  type: string;
10
10
  description: string;
11
11
  };
12
+ exact: {
13
+ type: string;
14
+ description: string;
15
+ };
12
16
  token_budget: {
13
17
  type: string;
14
18
  description: string;
@@ -1,6 +1,6 @@
1
1
  export const findReferencesTool = {
2
2
  name: "sverklo_refs",
3
- description: "Find all references to a symbol across the codebase. Shows where a function, class, or type is imported, called, or used.",
3
+ description: "Find all references to a symbol across the codebase. Shows where a function, class, or type is imported, called, or used. Matches on identifier word boundaries by default — `embed` does NOT match `embeddingStore`. Pass `exact: false` to opt into substring matching.",
4
4
  inputSchema: {
5
5
  type: "object",
6
6
  properties: {
@@ -8,6 +8,10 @@ export const findReferencesTool = {
8
8
  type: "string",
9
9
  description: "Symbol name to find references for",
10
10
  },
11
+ exact: {
12
+ type: "boolean",
13
+ description: "When true (default), match on whole-identifier boundaries — `embed` won't match `embeddingStore`. When false, substring-match like the old behavior.",
14
+ },
11
15
  token_budget: {
12
16
  type: "number",
13
17
  description: "Max tokens to return (default: 1500)",
@@ -16,10 +20,44 @@ export const findReferencesTool = {
16
20
  required: ["symbol"],
17
21
  },
18
22
  };
23
+ /**
24
+ * Build a matcher function that decides whether a line references
25
+ * the symbol. Issue #14: short symbol names like `embed` were matching
26
+ * `embeddingStore` and dozens of other unrelated identifiers via
27
+ * substring, polluting the output. Word-boundary matching is the
28
+ * right default — users can opt out via `exact: false` for the rare
29
+ * case where they genuinely want substring.
30
+ *
31
+ * We escape the symbol before using it in a regex so that users can
32
+ * look up symbols that contain regex metacharacters (e.g. `$scope`,
33
+ * `Foo.bar`) without the query being reinterpreted.
34
+ */
35
+ function buildSymbolMatcher(symbol, exact) {
36
+ if (!exact) {
37
+ return (line) => line.includes(symbol);
38
+ }
39
+ // \b uses \w = [A-Za-z0-9_], which is what we want for
40
+ // JS/TS/Python/Go/Rust/Java/C/C++/Ruby/PHP identifiers. It's not
41
+ // perfect for Unicode identifiers (Kotlin, Swift can use non-ASCII)
42
+ // but for the supported languages it gives the right answer in
43
+ // every case we tested in the dogfood session.
44
+ const escaped = symbol.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
45
+ const re = new RegExp(`\\b${escaped}\\b`);
46
+ return (line) => re.test(line);
47
+ }
19
48
  export function handleFindReferences(indexer, args) {
20
49
  const symbol = args.symbol;
50
+ if (typeof symbol !== "string" || symbol.trim() === "") {
51
+ return 'Error: `symbol` is required. Usage: sverklo_refs symbol:"MyClass".';
52
+ }
53
+ const exact = args.exact !== false; // default true
21
54
  const tokenBudget = args.token_budget || 1500;
22
- // Use FTS to find all mentions of the symbol
55
+ const matches = buildSymbolMatcher(symbol, exact);
56
+ // Use FTS to find candidate chunks mentioning the symbol at all.
57
+ // FTS still does substring-ish matching on the chunk body, but we
58
+ // re-filter every candidate line against the matcher above before
59
+ // we keep it — so the output respects the exact/word-boundary
60
+ // contract even when FTS hands us noise.
23
61
  const ftsResults = indexer.chunkStore.searchFts(symbol, 50);
24
62
  const fileCache = new Map();
25
63
  for (const f of indexer.fileStore.getAll()) {
@@ -31,13 +69,15 @@ export function handleFindReferences(indexer, args) {
31
69
  const file = fileCache.get(chunk.file_id);
32
70
  if (!file)
33
71
  continue;
34
- // Check if this chunk actually contains the symbol name
35
- if (!chunk.content.includes(symbol))
72
+ // Early reject: if the symbol doesn't appear in the chunk at all
73
+ // (under the current matching mode), skip the per-line scan.
74
+ if (!matches(chunk.content))
36
75
  continue;
37
- // Find specific lines with the symbol
76
+ // Find specific lines that actually contain the symbol as a
77
+ // whole word (or substring, if exact=false).
38
78
  const lines = chunk.content.split("\n");
39
79
  for (let i = 0; i < lines.length; i++) {
40
- if (lines[i].includes(symbol)) {
80
+ if (matches(lines[i])) {
41
81
  const refs = byFile.get(file.path) || [];
42
82
  refs.push({
43
83
  line: chunk.start_line + i,
@@ -1 +1 @@
1
- {"version":3,"file":"find-references.js","sourceRoot":"","sources":["../../../../src/server/tools/find-references.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"find-references.js","sourceRoot":"","sources":["../../../../src/server/tools/find-references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Indexer } from "../../indexer/indexer.js";
6
+ import { getProjectConfig } from "../../utils/config.js";
7
+ import { handleFindReferences } from "./find-references.js";
8
+ // Regression tests for github.com/sverklo/sverklo/issues/14.
9
+ //
10
+ // sverklo_refs used to substring-match the symbol name, so a query
11
+ // for `embed` returned 48 hits that were mostly `embeddingStore`,
12
+ // `embeddingBatch`, `EmbeddingStore` class, etc. — dozens of false
13
+ // positives drowning the 5 real call sites.
14
+ //
15
+ // The fix: word-boundary matching by default, substring opt-in via
16
+ // `exact: false`.
17
+ describe("handleFindReferences — issue #14 regression", () => {
18
+ let tmpRoot;
19
+ let indexer;
20
+ beforeEach(async () => {
21
+ tmpRoot = mkdtempSync(join(tmpdir(), "sverklo-refs-"));
22
+ mkdirSync(join(tmpRoot, "src"), { recursive: true });
23
+ // Seed a codebase that reproduces the original noise pattern:
24
+ // one real identifier `embed` plus several longer identifiers
25
+ // that contain `embed` as a substring. The exact-match mode
26
+ // should return only the real ones.
27
+ writeFileSync(join(tmpRoot, "src", "indexer.ts"), [
28
+ "export class Indexer {",
29
+ " public embeddingStore: unknown;",
30
+ " public embeddingBatch: unknown[] = [];",
31
+ " async run() {",
32
+ " const vectors = await embed(['text']);",
33
+ " this.embeddingStore = vectors;",
34
+ " this.embeddingBatch.push(...vectors);",
35
+ " return embed(['another']);",
36
+ " }",
37
+ "}",
38
+ "declare function embed(texts: string[]): Promise<unknown>;",
39
+ ].join("\n"), "utf-8");
40
+ writeFileSync(join(tmpRoot, "src", "storage.ts"), [
41
+ "export class EmbeddingStore {",
42
+ " // stores vectors produced by the indexer",
43
+ " save() {}",
44
+ "}",
45
+ ].join("\n"), "utf-8");
46
+ const cfg = getProjectConfig(tmpRoot);
47
+ indexer = new Indexer(cfg);
48
+ await indexer.index();
49
+ });
50
+ afterEach(() => {
51
+ try {
52
+ indexer.close();
53
+ }
54
+ catch { }
55
+ try {
56
+ rmSync(tmpRoot, { recursive: true, force: true });
57
+ }
58
+ catch { }
59
+ });
60
+ it("default exact mode matches whole identifiers only", () => {
61
+ const out = handleFindReferences(indexer, { symbol: "embed" });
62
+ // The two real calls to embed() must appear
63
+ expect(out).toContain("await embed(['text'])");
64
+ expect(out).toContain("embed(['another'])");
65
+ // But `embeddingStore`, `embeddingBatch`, `EmbeddingStore` must NOT
66
+ // be reported as references to `embed`
67
+ expect(out).not.toContain("embeddingStore: unknown");
68
+ expect(out).not.toContain("embeddingBatch.push");
69
+ expect(out).not.toContain("class EmbeddingStore");
70
+ });
71
+ it("exact: false opts into substring behavior for edge cases", () => {
72
+ const out = handleFindReferences(indexer, { symbol: "embed", exact: false });
73
+ // In substring mode, the longer names do match
74
+ expect(out).toContain("embeddingStore");
75
+ // And the real calls are still there
76
+ expect(out).toContain("embed(['text'])");
77
+ });
78
+ it("rejects missing symbol with a clear error", () => {
79
+ const out = handleFindReferences(indexer, {});
80
+ expect(out).toContain("Error");
81
+ expect(out).toContain("symbol");
82
+ });
83
+ it("does not match inside longer identifiers that share a prefix", () => {
84
+ // `Embedding` has `embed` as a prefix but should not match
85
+ // in exact mode.
86
+ const out = handleFindReferences(indexer, { symbol: "embed" });
87
+ expect(out).not.toContain("EmbeddingStore");
88
+ });
89
+ it("matches exact identifier even when it contains regex metachars", () => {
90
+ // Names with dots / dollar signs / brackets must not break the
91
+ // regex builder.
92
+ const out = handleFindReferences(indexer, { symbol: "$invalid" });
93
+ // Should return "No references" for a non-existent symbol, not
94
+ // throw on regex construction.
95
+ expect(out).toContain("No references found");
96
+ });
97
+ });
98
+ //# sourceMappingURL=find-references.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-references.test.js","sourceRoot":"","sources":["../../../../src/server/tools/find-references.test.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,9 @@ export function handleForget(indexer, args) {
20
20
  }
21
21
  indexer.memoryStore.delete(id);
22
22
  indexer.memoryEmbeddingStore.delete(id);
23
+ // Mirror the delete as a tombstone in the JSONL journal so the
24
+ // journal stays replayable. Issue #7.
25
+ indexer.memoryJournal.forget(id);
23
26
  return `Deleted memory #${id} (${memory.category}): "${memory.content.slice(0, 80)}${memory.content.length > 80 ? "..." : ""}"`;
24
27
  }
25
28
  //# sourceMappingURL=forget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forget.js","sourceRoot":"","sources":["../../../../src/server/tools/forget.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,wBAAwB;IACrC,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,EAAE,EAAE;gBACF,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,2CAA2C;aACzD;SACF;QACD,QAAQ,EAAE,CAAC,IAAI,CAAC;KACjB;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,IAA6B;IAE7B,MAAM,EAAE,GAAG,IAAI,CAAC,EAAY,CAAC;IAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,WAAW,EAAE,aAAa,CAAC;IACpC,CAAC;IAED,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC/B,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxC,OAAO,mBAAmB,EAAE,KAAK,MAAM,CAAC,QAAQ,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;AAClI,CAAC"}
1
+ {"version":3,"file":"forget.js","sourceRoot":"","sources":["../../../../src/server/tools/forget.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,wBAAwB;IACrC,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,EAAE,EAAE;gBACF,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,2CAA2C;aACzD;SACF;QACD,QAAQ,EAAE,CAAC,IAAI,CAAC;KACjB;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,IAA6B;IAE7B,MAAM,EAAE,GAAG,IAAI,CAAC,EAAY,CAAC;IAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,WAAW,EAAE,aAAa,CAAC;IACpC,CAAC;IAED,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC/B,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxC,+DAA+D;IAC/D,sCAAsC;IACtC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjC,OAAO,mBAAmB,EAAE,KAAK,MAAM,CAAC,QAAQ,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;AAClI,CAAC"}
@@ -24,6 +24,22 @@ export function handleIndexStatus(indexer) {
24
24
  parts.push(`- ${status.fileCount} files · ${status.chunkCount} symbols · ${symbolRefCount} references`);
25
25
  parts.push(`- Languages: ${status.languages.join(", ") || "none"}`);
26
26
  parts.push(`- Status: ${status.indexing ? `indexing (${status.progress?.done}/${status.progress?.total})` : "ready"}`);
27
+ // Embedding provider: read from the live indexer so the displayed
28
+ // provider reflects what was *actually* selected, not what env vars
29
+ // *asked for*. If the user requested openai but the init failed and
30
+ // we fell back to default, this will correctly show default. Issue #9.
31
+ const requestedProvider = (process.env.SVERKLO_EMBEDDING_PROVIDER || "default").toLowerCase();
32
+ const activeProvider = indexer.embeddingProviderName;
33
+ const activeDims = indexer.embeddingDimensions;
34
+ if (requestedProvider !== "default" &&
35
+ requestedProvider !== "bundled" &&
36
+ requestedProvider !== "onnx" &&
37
+ activeProvider === "default") {
38
+ parts.push(`- Embedding provider: ${activeProvider} (${activeDims}d) ⚠️ requested '${requestedProvider}' but fell back — check SVERKLO_DEBUG for details`);
39
+ }
40
+ else {
41
+ parts.push(`- Embedding provider: ${activeProvider} (${activeDims}d)`);
42
+ }
27
43
  // Freshness signal — only meaningful once the index has something to compare
28
44
  // against. Skip the disk walk entirely on an empty index to avoid scaring
29
45
  // the agent with "everything is dirty" noise during initial bootstrap.
@@ -1 +1 @@
1
- {"version":3,"file":"index-status.js","sourceRoot":"","sources":["../../../../src/server/tools/index-status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index-status.js","sourceRoot":"","sources":["../../../../src/server/tools/index-status.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  import { formatLookup } from "../../search/token-budget.js";
2
+ // Issue #6: on the first call, sverklo_lookup paid a ~1.6s penalty while
3
+ // warming up prepared statements via fileStore.getAll() to build a
4
+ // pagerank-by-file map. The getByNameWithFile JOIN below returns the
5
+ // same shape in a single indexed query, eliminating the full scan.
2
6
  export const lookupTool = {
3
7
  name: "sverklo_lookup",
4
8
  description: "Look up a specific symbol (function, class, type, variable) by name. Returns its full definition, signature, and location.",
@@ -31,24 +35,28 @@ export const lookupTool = {
31
35
  },
32
36
  };
33
37
  export function handleLookup(indexer, args) {
38
+ // Bug A (issue #15 investigation): missing / wrong-named required
39
+ // params previously fell through to a SQL LIKE '%undefined%' and
40
+ // returned "No results found" — indistinguishable from "the symbol
41
+ // doesn't exist" and actively misleading. Fail loud instead so the
42
+ // caller knows it was their mistake, not the index's.
34
43
  const symbol = args.symbol;
44
+ if (typeof symbol !== "string" || symbol.trim() === "") {
45
+ return ('Error: `symbol` is required. Usage: sverklo_lookup symbol:"MyClass".\n' +
46
+ "The tool schema names this parameter `symbol`, not `name` — common typo.");
47
+ }
35
48
  const type = args.type || "any";
36
49
  const tokenBudget = args.token_budget || 1200;
37
- let chunks = indexer.chunkStore.getByName(symbol, 20);
50
+ // Single JOIN'd query — chunks come back pre-sorted by pagerank DESC
51
+ // and carry the containing file's path, so no full fileStore scan.
52
+ let chunks = indexer.chunkStore.getByNameWithFile(symbol, 20);
38
53
  if (type !== "any") {
39
54
  chunks = chunks.filter((c) => c.type === type);
40
55
  }
41
- // Get file data for PageRank ordering
42
- const fileCache = new Map();
43
- for (const f of indexer.fileStore.getAll()) {
44
- fileCache.set(f.id, f);
45
- }
46
- // Sort by PageRank of containing file
47
- chunks.sort((a, b) => {
48
- const fa = fileCache.get(a.file_id);
49
- const fb = fileCache.get(b.file_id);
50
- return (fb?.pagerank || 0) - (fa?.pagerank || 0);
51
- });
52
- return formatLookup(chunks, fileCache, tokenBudget);
56
+ // formatLookup only reads filePath / lang off the file map when the
57
+ // chunk itself doesn't carry filePath. Since our JOIN provides it,
58
+ // we can pass an empty map and avoid the scan.
59
+ const emptyFileMap = new Map();
60
+ return formatLookup(chunks, emptyFileMap, tokenBudget);
53
61
  }
54
62
  //# sourceMappingURL=lookup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../../../src/server/tools/lookup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG5D,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,4HAA4H;IAC9H,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,gDAAgD;aAC9D;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE;oBACJ,UAAU;oBACV,OAAO;oBACP,MAAM;oBACN,WAAW;oBACX,QAAQ;oBACR,UAAU;oBACV,KAAK;iBACN;gBACD,WAAW,EAAE,uBAAuB;aACrC;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,sCAAsC;aACpD;SACF;QACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;KACrB;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,IAA6B;IAE7B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAgB,CAAC;IACrC,MAAM,IAAI,GAAI,IAAI,CAAC,IAA0B,IAAI,KAAK,CAAC;IACvD,MAAM,WAAW,GAAI,IAAI,CAAC,YAAuB,IAAI,IAAI,CAAC;IAE1D,IAAI,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEtD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,sCAAsC;IACtC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAChD,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QAC3C,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,sCAAsC;IACtC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACnB,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACpC,OAAO,CAAC,EAAE,EAAE,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AACtD,CAAC"}
1
+ {"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../../../src/server/tools/lookup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG5D,yEAAyE;AACzE,mEAAmE;AACnE,qEAAqE;AACrE,mEAAmE;AAEnE,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,4HAA4H;IAC9H,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,gDAAgD;aAC9D;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE;oBACJ,UAAU;oBACV,OAAO;oBACP,MAAM;oBACN,WAAW;oBACX,QAAQ;oBACR,UAAU;oBACV,KAAK;iBACN;gBACD,WAAW,EAAE,uBAAuB;aACrC;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,sCAAsC;aACpD;SACF;QACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;KACrB;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,IAA6B;IAE7B,kEAAkE;IAClE,iEAAiE;IACjE,mEAAmE;IACnE,mEAAmE;IACnE,sDAAsD;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvD,OAAO,CACL,wEAAwE;YACxE,0EAA0E,CAC3E,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAI,IAAI,CAAC,IAA0B,IAAI,KAAK,CAAC;IACvD,MAAM,WAAW,GAAI,IAAI,CAAC,YAAuB,IAAI,IAAI,CAAC;IAE1D,qEAAqE;IACrE,mEAAmE;IACnE,IAAI,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE9D,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,oEAAoE;IACpE,mEAAmE;IACnE,+CAA+C;IAC/C,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAC;IACnD,OAAO,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Indexer } from "../../indexer/indexer.js";
6
+ import { getProjectConfig } from "../../utils/config.js";
7
+ import { handleLookup } from "./lookup.js";
8
+ // Regression tests for github.com/sverklo/sverklo/issues/15.
9
+ //
10
+ // Two bugs combined to silently lie about whether a symbol existed:
11
+ //
12
+ // A. Missing-required-parameter fallthrough. Calling the tool with
13
+ // the wrong param name (or no param) went straight to a SQL
14
+ // LIKE '%undefined%' which matches nothing, returning "No results
15
+ // found" — indistinguishable from "the symbol does not exist."
16
+ //
17
+ // B. Oversize-chunk silent drop. When a query matched both a small
18
+ // chunk (fits the budget) and a big one (doesn't), the big one
19
+ // was silently dropped. The user saw the small match and
20
+ // concluded the big one didn't exist. On a realistic codebase
21
+ // looking up a major class, this made the tool useless — the
22
+ // real class body is always the biggest match.
23
+ describe("handleLookup — issue #15 regression", () => {
24
+ let tmpRoot;
25
+ let indexer;
26
+ beforeEach(async () => {
27
+ tmpRoot = mkdtempSync(join(tmpdir(), "sverklo-lookup-"));
28
+ mkdirSync(join(tmpRoot, "src"), { recursive: true });
29
+ mkdirSync(join(tmpRoot, "test"), { recursive: true });
30
+ // A "big" class that exceeds the default token budget (1200).
31
+ // We pad the body with enough meaningful-looking code to push the
32
+ // chunk's estimated token count well above 1200.
33
+ const padLines = Array.from({ length: 200 }, (_, i) => ` method${i}(): void { return; }`).join("\n");
34
+ writeFileSync(join(tmpRoot, "src", "indexer.ts"), ["export class Indexer {", padLines, "}"].join("\n"), "utf-8");
35
+ // A "small" function that matches the same substring but fits
36
+ // the budget. Without the fix, this would hide the Indexer class.
37
+ writeFileSync(join(tmpRoot, "test", "fakes.ts"), "export function fakeIndexerWithCore() { return {}; }\n", "utf-8");
38
+ const cfg = getProjectConfig(tmpRoot);
39
+ indexer = new Indexer(cfg);
40
+ await indexer.index();
41
+ });
42
+ afterEach(() => {
43
+ try {
44
+ indexer.close();
45
+ }
46
+ catch { }
47
+ try {
48
+ rmSync(tmpRoot, { recursive: true, force: true });
49
+ }
50
+ catch { }
51
+ });
52
+ // ── Bug A ─────────────────────────────────────────────────────
53
+ it("returns a helpful error when `symbol` is missing (bug A)", () => {
54
+ const out = handleLookup(indexer, {});
55
+ expect(out).toContain("Error");
56
+ expect(out).toContain("symbol");
57
+ // The error should specifically mention the common typo so
58
+ // users don't have to read the schema.
59
+ expect(out).toMatch(/name.*typo|symbol.*not.*name/i);
60
+ });
61
+ it("returns a helpful error when `symbol` is an empty string (bug A)", () => {
62
+ const out = handleLookup(indexer, { symbol: "" });
63
+ expect(out).toContain("Error");
64
+ });
65
+ it("returns a helpful error when the wrong parameter name is used (bug A)", () => {
66
+ // Users commonly call this with `name: "Foo"` instead of
67
+ // `symbol: "Foo"`. Before the fix, that silently returned
68
+ // "No results found" — indistinguishable from a real miss.
69
+ const out = handleLookup(indexer, { name: "Indexer" });
70
+ expect(out).toContain("Error");
71
+ expect(out).toMatch(/name.*typo|symbol.*not.*name/i);
72
+ });
73
+ // ── Bug B ─────────────────────────────────────────────────────
74
+ it("surfaces oversize matches as locations, even when smaller matches fit (bug B)", () => {
75
+ // `Indexer` matches both: the big class (oversized) and the small
76
+ // fakeIndexerWithCore (fits). Before the fix, only the small one
77
+ // was shown and the big one was silently dropped.
78
+ const out = handleLookup(indexer, { symbol: "Indexer", token_budget: 1200 });
79
+ // The small match should be visible
80
+ expect(out).toContain("fakeIndexerWithCore");
81
+ // And the big match should ALSO be visible, at minimum as a
82
+ // locations-only entry pointing at indexer.ts.
83
+ expect(out).toContain("indexer.ts");
84
+ expect(out).toMatch(/class: Indexer|Indexer/);
85
+ // The "too large" explanation should be present so the caller
86
+ // knows the body was skipped on purpose, not missing.
87
+ expect(out).toMatch(/too large|exceed|token_budget/i);
88
+ });
89
+ it("surfaces all matches as locations when none fit", () => {
90
+ // With a tiny budget, nothing fits — but we still need the
91
+ // locations list, not "No results found."
92
+ const out = handleLookup(indexer, { symbol: "Indexer", token_budget: 80 });
93
+ expect(out).not.toBe("No results found.");
94
+ expect(out).toMatch(/exceed|token_budget|locations only/i);
95
+ expect(out).toContain("indexer.ts");
96
+ });
97
+ it("with a large budget, returns full bodies for both matches", () => {
98
+ const out = handleLookup(indexer, { symbol: "Indexer", token_budget: 20000 });
99
+ expect(out).toContain("fakeIndexerWithCore");
100
+ expect(out).toContain("class Indexer");
101
+ // With full bodies rendered, the "too large" warning should not
102
+ // appear.
103
+ expect(out).not.toMatch(/too large/i);
104
+ });
105
+ it("still returns 'No results found' when the symbol genuinely does not exist", () => {
106
+ const out = handleLookup(indexer, { symbol: "NonexistentSymbolXYZ" });
107
+ expect(out).toBe("No results found.");
108
+ });
109
+ });
110
+ //# sourceMappingURL=lookup.test.js.map