sverklo 0.2.11 → 0.2.13

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 (93) hide show
  1. package/README.md +115 -19
  2. package/dist/bin/sverklo.js +96 -6
  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.d.ts +10 -0
  19. package/dist/src/indexer/indexer.js +46 -4
  20. package/dist/src/indexer/indexer.js.map +1 -1
  21. package/dist/src/indexer/watcher.js +4 -0
  22. package/dist/src/indexer/watcher.js.map +1 -1
  23. package/dist/src/memory/journal.d.ts +62 -0
  24. package/dist/src/memory/journal.js +136 -0
  25. package/dist/src/memory/journal.js.map +1 -0
  26. package/dist/src/memory/journal.test.d.ts +1 -0
  27. package/dist/src/memory/journal.test.js +98 -0
  28. package/dist/src/memory/journal.test.js.map +1 -0
  29. package/dist/src/modes.d.ts +16 -0
  30. package/dist/src/modes.js +104 -0
  31. package/dist/src/modes.js.map +1 -0
  32. package/dist/src/modes.test.d.ts +1 -0
  33. package/dist/src/modes.test.js +48 -0
  34. package/dist/src/modes.test.js.map +1 -0
  35. package/dist/src/search/hybrid-search.d.ts +22 -0
  36. package/dist/src/search/hybrid-search.js +167 -5
  37. package/dist/src/search/hybrid-search.js.map +1 -1
  38. package/dist/src/search/hybrid-search.test.d.ts +1 -0
  39. package/dist/src/search/hybrid-search.test.js +109 -0
  40. package/dist/src/search/hybrid-search.test.js.map +1 -0
  41. package/dist/src/search/token-budget.d.ts +1 -0
  42. package/dist/src/search/token-budget.js +1 -1
  43. package/dist/src/search/token-budget.js.map +1 -1
  44. package/dist/src/server/mcp-server.js +32 -25
  45. package/dist/src/server/mcp-server.js.map +1 -1
  46. package/dist/src/server/tool-overrides.d.ts +16 -0
  47. package/dist/src/server/tool-overrides.js +102 -0
  48. package/dist/src/server/tool-overrides.js.map +1 -0
  49. package/dist/src/server/tool-overrides.test.d.ts +1 -0
  50. package/dist/src/server/tool-overrides.test.js +124 -0
  51. package/dist/src/server/tool-overrides.test.js.map +1 -0
  52. package/dist/src/server/tools/context-budget.test.d.ts +1 -0
  53. package/dist/src/server/tools/context-budget.test.js +150 -0
  54. package/dist/src/server/tools/context-budget.test.js.map +1 -0
  55. package/dist/src/server/tools/context.d.ts +11 -1
  56. package/dist/src/server/tools/context.js +202 -8
  57. package/dist/src/server/tools/context.js.map +1 -1
  58. package/dist/src/server/tools/diff-heuristics.d.ts +28 -0
  59. package/dist/src/server/tools/diff-heuristics.js +213 -0
  60. package/dist/src/server/tools/diff-heuristics.js.map +1 -0
  61. package/dist/src/server/tools/diff-heuristics.test.d.ts +1 -0
  62. package/dist/src/server/tools/diff-heuristics.test.js +151 -0
  63. package/dist/src/server/tools/diff-heuristics.test.js.map +1 -0
  64. package/dist/src/server/tools/forget.js +3 -0
  65. package/dist/src/server/tools/forget.js.map +1 -1
  66. package/dist/src/server/tools/index-status.js +16 -0
  67. package/dist/src/server/tools/index-status.js.map +1 -1
  68. package/dist/src/server/tools/lookup.js +12 -13
  69. package/dist/src/server/tools/lookup.js.map +1 -1
  70. package/dist/src/server/tools/recall.d.ts +5 -1
  71. package/dist/src/server/tools/recall.js +66 -4
  72. package/dist/src/server/tools/recall.js.map +1 -1
  73. package/dist/src/server/tools/recall.test.d.ts +1 -0
  74. package/dist/src/server/tools/recall.test.js +116 -0
  75. package/dist/src/server/tools/recall.test.js.map +1 -0
  76. package/dist/src/server/tools/remember.js +14 -0
  77. package/dist/src/server/tools/remember.js.map +1 -1
  78. package/dist/src/server/tools/review-diff.js +31 -0
  79. package/dist/src/server/tools/review-diff.js.map +1 -1
  80. package/dist/src/server/tools/search.js +29 -4
  81. package/dist/src/server/tools/search.js.map +1 -1
  82. package/dist/src/server/tools/search.test.d.ts +1 -0
  83. package/dist/src/server/tools/search.test.js +118 -0
  84. package/dist/src/server/tools/search.test.js.map +1 -0
  85. package/dist/src/server/tools/tier.js +25 -1
  86. package/dist/src/server/tools/tier.js.map +1 -1
  87. package/dist/src/storage/chunk-store.d.ts +12 -0
  88. package/dist/src/storage/chunk-store.js +16 -0
  89. package/dist/src/storage/chunk-store.js.map +1 -1
  90. package/dist/src/storage/chunk-store.test.d.ts +1 -0
  91. package/dist/src/storage/chunk-store.test.js +69 -0
  92. package/dist/src/storage/chunk-store.test.js.map +1 -0
  93. package/package.json +5 -2
@@ -0,0 +1,213 @@
1
+ // Structural heuristics that scan the actual diff text for specific classes
2
+ // of risk that symbol-level analysis alone will not catch. Kept as a
3
+ // separate module so new heuristics can be added without touching the
4
+ // main review-diff handler, and so each heuristic is unit-testable in
5
+ // isolation.
6
+ //
7
+ // Heuristics are *heuristics* — they trade some false positives for
8
+ // recall on real-world bugs. Each one attaches a short "why" so a human
9
+ // reviewer can quickly dismiss a false flag.
10
+ //
11
+ // Current heuristics:
12
+ // - unguarded-stream-call: a new call site introduced inside a stream
13
+ // pipeline (.map / .forEach / .flatMap / .filter / .reduce / etc.)
14
+ // where the enclosing function has no visible try-catch. One
15
+ // uncaught RuntimeException on a single element will abort the
16
+ // entire stream — a real outage risk on production read paths.
17
+ // Tracked in github.com/sverklo/sverklo/issues/5.
18
+ //
19
+ // Adding a heuristic:
20
+ // 1. Write a pure function that takes a DiffHunk[] and returns
21
+ // HeuristicFinding[]. No I/O, no git, no filesystem — the caller
22
+ // is responsible for producing hunks.
23
+ // 2. Register it in ALL_HEURISTICS below.
24
+ // 3. Unit-test it with representative fixtures.
25
+ import { execSync } from "node:child_process";
26
+ // ────────────────────────────────────────────────────────────────────
27
+ // Heuristic 1 — unguarded stream-pipeline call
28
+ // ────────────────────────────────────────────────────────────────────
29
+ // Stream pipeline markers we care about. The list is deliberately
30
+ // multi-language: Java / TS / Kotlin / Scala all share the same shape.
31
+ const STREAM_METHODS = [
32
+ "map",
33
+ "forEach",
34
+ "flatMap",
35
+ "filter",
36
+ "reduce",
37
+ "mapToInt",
38
+ "mapToLong",
39
+ "mapToDouble",
40
+ "peek",
41
+ "collect",
42
+ ];
43
+ // A line is "entering a stream pipeline" if it matches `.<method>(`
44
+ // where <method> is one of the above. We accept optional whitespace.
45
+ const STREAM_ENTRY_RE = new RegExp(`\\.(${STREAM_METHODS.join("|")})\\s*\\(`);
46
+ // A line looks like a call that may throw if it contains `.<name>(`
47
+ // for an identifier-shaped name. This is broad on purpose — most method
48
+ // calls *can* throw, so any introduced call inside a stream pipeline
49
+ // without a try-catch is the pattern we want to flag.
50
+ const ANY_CALL_RE = /\b\w+\s*\(/;
51
+ // A try-catch is "visible" in the hunk if we see a `try` or `catch`
52
+ // token on any context line. This is a conservative proxy for "the
53
+ // enclosing method catches its exceptions" — an AST-based check would
54
+ // be more accurate but would require parsing the entire file per diff.
55
+ const TRY_OR_CATCH_RE = /\b(try|catch)\b/;
56
+ export function findUnguardedStreamCalls(hunks) {
57
+ const findings = [];
58
+ for (const hunk of hunks) {
59
+ // Is there a visible try / catch anywhere in the hunk's context?
60
+ // If so, skip the whole hunk — the enclosing method probably
61
+ // catches. False negatives here are fine; we care about loud wins.
62
+ const hasTryCatch = hunk.lines.some((l) => TRY_OR_CATCH_RE.test(l));
63
+ if (hasTryCatch)
64
+ continue;
65
+ // Walk the hunk line by line. Track whether we've seen a stream
66
+ // entry on a preceding context/added line, and whether we're still
67
+ // "inside" it (approximated by nesting depth on the same or next
68
+ // lines in the hunk).
69
+ let insideStreamDepth = 0;
70
+ let streamFileLine = hunk.newStart;
71
+ let currentNewLine = hunk.newStart;
72
+ for (const rawLine of hunk.lines) {
73
+ const prefix = rawLine.charAt(0);
74
+ const content = rawLine.slice(1);
75
+ // Track whether we're in a stream block, using a crude brace count
76
+ // scoped to the hunk. This misses pipelines that span more than the
77
+ // hunk window — we accept that as a recall tradeoff.
78
+ if (STREAM_ENTRY_RE.test(content)) {
79
+ insideStreamDepth = 1;
80
+ streamFileLine = currentNewLine;
81
+ }
82
+ else if (insideStreamDepth > 0) {
83
+ const opens = (content.match(/\(/g) || []).length;
84
+ const closes = (content.match(/\)/g) || []).length;
85
+ insideStreamDepth += opens - closes;
86
+ if (insideStreamDepth < 0)
87
+ insideStreamDepth = 0;
88
+ }
89
+ // Only flag on added lines (new risks), not context. Context
90
+ // lines merely help us track state.
91
+ if (prefix === "+" && insideStreamDepth > 0 && ANY_CALL_RE.test(content)) {
92
+ // Skip the stream-entry line itself — we only flag the *body*
93
+ // calls inside the pipeline, not the pipeline declaration.
94
+ if (!STREAM_ENTRY_RE.test(content)) {
95
+ findings.push({
96
+ heuristic: "unguarded-stream-call",
97
+ severity: "medium",
98
+ file: hunk.filePath,
99
+ line: currentNewLine,
100
+ snippet: content.trim().slice(0, 120),
101
+ message: "New call inside a stream pipeline with no visible try-catch in the hunk. " +
102
+ "A single RuntimeException on one element will abort the entire pipeline — " +
103
+ "on a production read path this is an outage. Wrap the lambda body in " +
104
+ "try-catch or pre-filter elements that could throw.",
105
+ });
106
+ // Flag once per stream block to avoid spamming — a block with
107
+ // ten calls is one finding, not ten.
108
+ insideStreamDepth = 0;
109
+ }
110
+ }
111
+ // Advance the running new-file line counter. Context and added
112
+ // lines both consume new-file line numbers; removed lines do not.
113
+ if (prefix === "+" || prefix === " ")
114
+ currentNewLine++;
115
+ }
116
+ }
117
+ return findings;
118
+ }
119
+ // ────────────────────────────────────────────────────────────────────
120
+ // Registry + driver
121
+ // ────────────────────────────────────────────────────────────────────
122
+ export const ALL_HEURISTICS = [
123
+ findUnguardedStreamCalls,
124
+ ];
125
+ export function runAllHeuristics(hunks) {
126
+ const all = [];
127
+ for (const fn of ALL_HEURISTICS) {
128
+ try {
129
+ all.push(...fn(hunks));
130
+ }
131
+ catch {
132
+ // One broken heuristic must not take down review. Swallow and move on.
133
+ }
134
+ }
135
+ return all;
136
+ }
137
+ // ────────────────────────────────────────────────────────────────────
138
+ // Parser — turn `git diff --unified=N` text into DiffHunk[]
139
+ // ────────────────────────────────────────────────────────────────────
140
+ /**
141
+ * Parse a unified-diff string (output of `git diff -U5 ref`) into hunks.
142
+ * Very permissive — anything that doesn't match the expected header is
143
+ * skipped rather than raising.
144
+ */
145
+ export function parseUnifiedDiff(diffText) {
146
+ const hunks = [];
147
+ const lines = diffText.split("\n");
148
+ let currentFile = null;
149
+ let currentHunk = null;
150
+ for (const line of lines) {
151
+ // New file header: "diff --git a/foo b/foo"
152
+ if (line.startsWith("diff --git ")) {
153
+ if (currentHunk) {
154
+ hunks.push(currentHunk);
155
+ currentHunk = null;
156
+ }
157
+ currentFile = null;
158
+ continue;
159
+ }
160
+ // "+++ b/path" gives us the new file path
161
+ if (line.startsWith("+++ ")) {
162
+ const path = line.slice(4).replace(/^b\//, "").trim();
163
+ currentFile = path === "/dev/null" ? null : path;
164
+ continue;
165
+ }
166
+ // Hunk header: "@@ -12,5 +34,7 @@ optional-context"
167
+ if (line.startsWith("@@ ")) {
168
+ if (currentHunk)
169
+ hunks.push(currentHunk);
170
+ const match = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
171
+ if (match && currentFile) {
172
+ currentHunk = {
173
+ filePath: currentFile,
174
+ oldStart: parseInt(match[1], 10),
175
+ newStart: parseInt(match[2], 10),
176
+ lines: [],
177
+ };
178
+ }
179
+ else {
180
+ currentHunk = null;
181
+ }
182
+ continue;
183
+ }
184
+ // Body lines: "+", "-", or " " prefixed
185
+ if (currentHunk && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) {
186
+ // Skip the file-metadata lines "+++" / "---" which we already handled above
187
+ if (line.startsWith("+++") || line.startsWith("---"))
188
+ continue;
189
+ currentHunk.lines.push(line);
190
+ }
191
+ }
192
+ if (currentHunk)
193
+ hunks.push(currentHunk);
194
+ return hunks;
195
+ }
196
+ /**
197
+ * Convenience: pull a unified diff from git and parse it.
198
+ */
199
+ export function getDiffHunks(rootPath, ref) {
200
+ try {
201
+ const out = execSync(`git diff --unified=10 ${ref}`, {
202
+ cwd: rootPath,
203
+ encoding: "utf-8",
204
+ timeout: 8000,
205
+ maxBuffer: 10 * 1024 * 1024,
206
+ });
207
+ return parseUnifiedDiff(out);
208
+ }
209
+ catch {
210
+ return [];
211
+ }
212
+ }
213
+ //# sourceMappingURL=diff-heuristics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-heuristics.js","sourceRoot":"","sources":["../../../../src/server/tools/diff-heuristics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -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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"}
@@ -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 selection — read from env so users can verify
28
+ // their SVERKLO_EMBEDDING_PROVIDER setting took effect. Issue #9.
29
+ const providerEnv = (process.env.SVERKLO_EMBEDDING_PROVIDER || "default").toLowerCase();
30
+ if (providerEnv !== "default" && providerEnv !== "bundled" && providerEnv !== "onnx") {
31
+ const extra = [];
32
+ if (providerEnv === "openai" && process.env.SVERKLO_OPENAI_MODEL) {
33
+ extra.push(process.env.SVERKLO_OPENAI_MODEL);
34
+ }
35
+ if (providerEnv === "ollama" && process.env.SVERKLO_OLLAMA_MODEL) {
36
+ extra.push(process.env.SVERKLO_OLLAMA_MODEL);
37
+ }
38
+ parts.push(`- Embedding provider: ${providerEnv}${extra.length > 0 ? ` (${extra.join(", ")})` : ""}`);
39
+ }
40
+ else {
41
+ parts.push(`- Embedding provider: default (bundled ONNX, 384d)`);
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.",
@@ -34,21 +38,16 @@ export function handleLookup(indexer, args) {
34
38
  const symbol = args.symbol;
35
39
  const type = args.type || "any";
36
40
  const tokenBudget = args.token_budget || 1200;
37
- let chunks = indexer.chunkStore.getByName(symbol, 20);
41
+ // Single JOIN'd query — chunks come back pre-sorted by pagerank DESC
42
+ // and carry the containing file's path, so no full fileStore scan.
43
+ let chunks = indexer.chunkStore.getByNameWithFile(symbol, 20);
38
44
  if (type !== "any") {
39
45
  chunks = chunks.filter((c) => c.type === type);
40
46
  }
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);
47
+ // formatLookup only reads filePath / lang off the file map when the
48
+ // chunk itself doesn't carry filePath. Since our JOIN provides it,
49
+ // we can pass an empty map and avoid the scan.
50
+ const emptyFileMap = new Map();
51
+ return formatLookup(chunks, emptyFileMap, tokenBudget);
53
52
  }
54
53
  //# 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,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,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"}
@@ -9,6 +9,11 @@ export declare const recallTool: {
9
9
  type: string;
10
10
  description: string;
11
11
  };
12
+ mode: {
13
+ type: string;
14
+ enum: string[];
15
+ description: string;
16
+ };
12
17
  category: {
13
18
  type: string;
14
19
  enum: string[];
@@ -23,7 +28,6 @@ export declare const recallTool: {
23
28
  description: string;
24
29
  };
25
30
  };
26
- required: string[];
27
31
  };
28
32
  };
29
33
  export declare function handleRecall(indexer: Indexer, args: Record<string, unknown>): Promise<string>;
@@ -2,15 +2,37 @@ import { embed, cosineSimilarity } from "../../indexer/embedder.js";
2
2
  import { checkStaleness } from "../../memory/staleness.js";
3
3
  import { track } from "../../telemetry/index.js";
4
4
  const RRF_K = 60;
5
+ // Issue #11: core vs archival memory. Core memories are always-on
6
+ // project invariants auto-injected at session start. Archival memories
7
+ // are the searchable long tail. The mode parameter lets callers pick:
8
+ //
9
+ // - mode=core → core tier only, returned in priority order
10
+ // - mode=archival → project + archive tiers, full semantic search
11
+ // - mode=all → default, searches everything
12
+ //
13
+ // Core tier is soft-capped at 25 memories — LLM context windows don't
14
+ // appreciate a 500-line system prompt. Exceeding the cap emits a
15
+ // warning in the recall output but does not block writes.
16
+ const CORE_TIER_SOFT_LIMIT = 25;
5
17
  export const recallTool = {
6
18
  name: "sverklo_recall",
7
- description: "Search memories semantically. Finds past decisions, preferences, and patterns relevant to a query.",
19
+ description: "Search memories semantically. Finds past decisions, preferences, and patterns relevant to a query. " +
20
+ "Supports two specialized modes: `mode=core` returns only the always-on project invariants (fast, " +
21
+ "no query needed — use at session start); `mode=archival` searches the full archive with semantic " +
22
+ "ranking; `mode=all` (default) searches both. Use core for 'what are the project-wide rules I must " +
23
+ "not violate' and archival for 'what did we decide about X on this codebase'.",
8
24
  inputSchema: {
9
25
  type: "object",
10
26
  properties: {
11
27
  query: {
12
28
  type: "string",
13
- description: "What to search for in memories",
29
+ description: "What to search for in memories (optional when mode=core)",
30
+ },
31
+ mode: {
32
+ type: "string",
33
+ enum: ["core", "archival", "all"],
34
+ description: "Which memory tier to search. 'core' = always-on invariants only, 'archival' = " +
35
+ "searchable long tail, 'all' = both (default).",
14
36
  },
15
37
  category: {
16
38
  type: "string",
@@ -26,14 +48,52 @@ export const recallTool = {
26
48
  description: "Include stale memories (default: false)",
27
49
  },
28
50
  },
29
- required: ["query"],
30
51
  },
31
52
  };
32
53
  export async function handleRecall(indexer, args) {
33
- const query = args.query;
54
+ const query = args.query || "";
55
+ const mode = args.mode || "all";
34
56
  const category = args.category || "any";
35
57
  const limit = args.limit || 10;
36
58
  const includeStale = args.include_stale || false;
59
+ // Mode: core — return the always-on invariants without semantic
60
+ // ranking. This is the session-start fast path; agents should call
61
+ // this (or read sverklo://context) at the top of every session.
62
+ if (mode === "core") {
63
+ const coreMemories = indexer.memoryStore.getCore(limit);
64
+ const filtered = category === "any"
65
+ ? coreMemories
66
+ : coreMemories.filter((m) => m.category === category);
67
+ void track("memory.read");
68
+ if (filtered.length === 0) {
69
+ return ("No core memories yet. Core memories are always-on project invariants " +
70
+ "that auto-load each session. Promote an existing memory with " +
71
+ "`sverklo_promote id:<n> tier:core`, or save a new one with " +
72
+ "`sverklo_remember ... tier:core`.");
73
+ }
74
+ const parts = ["# Core memories (always-on project invariants)", ""];
75
+ for (const m of filtered) {
76
+ const staleFlag = m.is_stale ? " [STALE]" : "";
77
+ parts.push(`- **[${m.category}]**${staleFlag} ${m.content}`);
78
+ }
79
+ parts.push("");
80
+ // Soft-limit warning: too many core memories is an anti-pattern.
81
+ const totalCore = indexer.memoryStore.getCore(1000).length;
82
+ if (totalCore > CORE_TIER_SOFT_LIMIT) {
83
+ parts.push(`⚠️ ${totalCore} core memories — exceeds the soft limit of ${CORE_TIER_SOFT_LIMIT}. ` +
84
+ "Core memories are injected into every session prompt; too many crowds the context " +
85
+ "window. Demote the least-critical ones with `sverklo_demote id:<n>`.");
86
+ }
87
+ return parts.join("\n");
88
+ }
89
+ // Mode: archival | all — full semantic search over the selected
90
+ // tier(s). If the caller passed mode=archival we exclude core; if
91
+ // mode=all we include everything.
92
+ if (!query) {
93
+ return ("A `query` is required for archival/all recall. Pass `mode:core` if you want " +
94
+ "to list always-on invariants without a query.");
95
+ }
96
+ const excludeTiers = mode === "archival" ? ["core"] : [];
37
97
  // Signal A: FTS text search
38
98
  const ftsResults = indexer.memoryStore.searchFts(query, 30);
39
99
  // Signal B: Vector similarity
@@ -64,6 +124,8 @@ export async function handleRecall(indexer, args) {
64
124
  continue;
65
125
  if (category !== "any" && memory.category !== category)
66
126
  continue;
127
+ if (excludeTiers.includes(memory.tier))
128
+ continue;
67
129
  // Staleness check (lazy)
68
130
  const stale = checkStaleness(memory, indexer.fileStore, indexer.memoryStore);
69
131
  if (stale)