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,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { extractReferences } from "./symbol-extractor.js";
3
+ // Regression test for github.com/sverklo/sverklo/issues/13.
4
+ //
5
+ // The old implementation used a chunk-wide `seen` set to dedupe
6
+ // references. That made sverklo_impact lossy: a symbol called N
7
+ // times in the same function returned only one row, pointing at
8
+ // the first call site. For refactor safety — the whole point of
9
+ // the impact tool — this was the worst possible failure mode:
10
+ // the user trusted the output, made the refactor, missed N-1 real
11
+ // call sites, shipped the bug.
12
+ //
13
+ // The fix: per-line dedupe (still prevents double-counting when
14
+ // two regexes both fire on the same line, e.g. `new Foo()` or
15
+ // `Foo(Foo())`) while letting repeat calls across lines each
16
+ // contribute their own symbol_ref row.
17
+ describe("extractReferences — issue #13 regression", () => {
18
+ it("records multiple call sites of the same symbol across lines", () => {
19
+ const content = [
20
+ "function index() {",
21
+ " const vectors = await embed(texts);",
22
+ " // ...",
23
+ " const [vector] = await embed([text]);",
24
+ " return vectors;",
25
+ "}",
26
+ ].join("\n");
27
+ const refs = extractReferences(content, "index");
28
+ const embedRefs = refs.filter((r) => r.name === "embed");
29
+ expect(embedRefs.length).toBe(2);
30
+ expect(embedRefs[0].line).toBe(1);
31
+ expect(embedRefs[1].line).toBe(3);
32
+ });
33
+ it("still dedupes the same symbol on the same line (regex collision guard)", () => {
34
+ // A line with two occurrences of the same identifier should
35
+ // still produce only one ref entry — per-line dedupe survives.
36
+ const content = ["function outer() {", " return foo(foo(x));", "}"].join("\n");
37
+ const refs = extractReferences(content, "outer");
38
+ const fooRefs = refs.filter((r) => r.name === "foo");
39
+ // Exactly one foo ref on line 1 — not two from the nested call.
40
+ expect(fooRefs.length).toBe(1);
41
+ });
42
+ it("records calls in distinct branches as distinct refs", () => {
43
+ const content = [
44
+ "function handle(x) {",
45
+ " if (x > 0) {",
46
+ " publish(x);",
47
+ " } else {",
48
+ " publish(null);",
49
+ " }",
50
+ "}",
51
+ ].join("\n");
52
+ const refs = extractReferences(content, "handle");
53
+ const publishRefs = refs.filter((r) => r.name === "publish");
54
+ expect(publishRefs.length).toBe(2);
55
+ });
56
+ it("does not record a self-reference when selfName matches", () => {
57
+ const content = ["function fact(n) {", " return n * fact(n - 1);", "}"].join("\n");
58
+ const refs = extractReferences(content, "fact");
59
+ expect(refs.filter((r) => r.name === "fact").length).toBe(0);
60
+ });
61
+ it("strips string literals before matching", () => {
62
+ // `foo` in a string is not a real call — must not be recorded.
63
+ const content = [
64
+ "function log() {",
65
+ " const msg = 'foo was called here';",
66
+ ' const other = "foo";',
67
+ " foo();",
68
+ "}",
69
+ ].join("\n");
70
+ const refs = extractReferences(content, "log");
71
+ const fooRefs = refs.filter((r) => r.name === "foo");
72
+ // Only the real call on the last line.
73
+ expect(fooRefs.length).toBe(1);
74
+ expect(fooRefs[0].line).toBe(3);
75
+ });
76
+ it("strips line comments before matching", () => {
77
+ const content = [
78
+ "function test() {",
79
+ " // foo() is not actually called here",
80
+ " bar();",
81
+ "}",
82
+ ].join("\n");
83
+ const refs = extractReferences(content, "test");
84
+ expect(refs.some((r) => r.name === "foo")).toBe(false);
85
+ expect(refs.some((r) => r.name === "bar")).toBe(true);
86
+ });
87
+ });
88
+ //# sourceMappingURL=symbol-extractor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-extractor.test.js","sourceRoot":"","sources":["../../../src/indexer/symbol-extractor.test.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,9 @@ export function startWatcher(indexer, rootPath) {
29
29
  const lang = detectLanguage(absolutePath);
30
30
  if (!lang)
31
31
  return;
32
+ // Any real change invalidates the freshness cache immediately so the
33
+ // next sverklo_status reflects reality without waiting for the TTL.
34
+ indexer.invalidateFreshnessCache();
32
35
  // Debounce
33
36
  const existing = pending.get(rel);
34
37
  if (existing)
@@ -44,6 +47,7 @@ export function startWatcher(indexer, rootPath) {
44
47
  watcher.on("unlink", (absolutePath) => {
45
48
  const rel = relative(rootPath, absolutePath);
46
49
  log(`File removed: ${rel}`);
50
+ indexer.invalidateFreshnessCache();
47
51
  indexer.removeFile(rel);
48
52
  });
49
53
  log("File watcher started");
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/indexer/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAGzC,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,QAAgB;IAC7D,MAAM,YAAY,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAElD,gCAAgC;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAClD,MAAM,WAAW,GAAG,GAAG,CAAC;IAExB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE;QAC9B,OAAO,EAAE,CAAC,IAAY,EAAE,EAAE;YACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG;gBAAE,OAAO,KAAK,CAAC;YACvB,IAAI,CAAC;gBACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,EAAE,kBAAkB,EAAE,GAAG,EAAE;KAC9C,CAAC,CAAC;IAEH,SAAS,YAAY,CAAC,YAAoB;QACxC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,WAAW;QACX,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,QAAQ;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QAErC,OAAO,CAAC,GAAG,CACT,GAAG,EACH,UAAU,CAAC,KAAK,IAAI,EAAE;YACpB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,GAAG,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;YAC5B,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC,EAAE,WAAW,CAAC,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,YAAoB,EAAE,EAAE;QAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7C,GAAG,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;QAC5B,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,sBAAsB,CAAC,CAAC;AAC9B,CAAC"}
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/indexer/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAGzC,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,QAAgB;IAC7D,MAAM,YAAY,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAElD,gCAAgC;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAClD,MAAM,WAAW,GAAG,GAAG,CAAC;IAExB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE;QAC9B,OAAO,EAAE,CAAC,IAAY,EAAE,EAAE;YACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG;gBAAE,OAAO,KAAK,CAAC;YACvB,IAAI,CAAC;gBACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,EAAE,kBAAkB,EAAE,GAAG,EAAE;KAC9C,CAAC,CAAC;IAEH,SAAS,YAAY,CAAC,YAAoB;QACxC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,qEAAqE;QACrE,oEAAoE;QACpE,OAAO,CAAC,wBAAwB,EAAE,CAAC;QAEnC,WAAW;QACX,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,QAAQ;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QAErC,OAAO,CAAC,GAAG,CACT,GAAG,EACH,UAAU,CAAC,KAAK,IAAI,EAAE;YACpB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,GAAG,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;YAC5B,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC,EAAE,WAAW,CAAC,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,YAAoB,EAAE,EAAE;QAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7C,GAAG,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;QAC5B,OAAO,CAAC,wBAAwB,EAAE,CAAC;QACnC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,sBAAsB,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,62 @@
1
+ export type JournalOp = "remember" | "forget" | "invalidate" | "promote" | "demote" | "touch";
2
+ export interface JournalEntry {
3
+ op: JournalOp;
4
+ id: number;
5
+ ts: string;
6
+ content?: string;
7
+ category?: string;
8
+ tags?: string[] | null;
9
+ confidence?: number;
10
+ git_sha?: string | null;
11
+ git_branch?: string | null;
12
+ related_files?: string[] | null;
13
+ tier?: string;
14
+ invalidated_at_sha?: string | null;
15
+ replaced_by_id?: number | null;
16
+ new_tier?: string;
17
+ }
18
+ /**
19
+ * MemoryJournal writes append-only JSONL to <project>/.sverklo/memories.jsonl.
20
+ *
21
+ * Writes are synchronous on purpose: the journal must be committed
22
+ * before the corresponding SQLite write returns to the caller, so we
23
+ * never have a window where SQLite succeeds and the journal is missing
24
+ * the record. Agent workloads are low-frequency on the memory side
25
+ * (dozens of writes per session, not thousands) so sync writes are
26
+ * fine. If memory write volume grows we can batch.
27
+ *
28
+ * Errors are swallowed with a log. The journal is advisory for 0.2.x —
29
+ * a broken journal must not break memory.
30
+ */
31
+ export declare class MemoryJournal {
32
+ private path;
33
+ private ready;
34
+ constructor(projectRoot: string);
35
+ private ensureDir;
36
+ private write;
37
+ remember(params: {
38
+ id: number;
39
+ content: string;
40
+ category: string;
41
+ tags?: string[] | null;
42
+ confidence: number;
43
+ git_sha?: string | null;
44
+ git_branch?: string | null;
45
+ related_files?: string[] | null;
46
+ tier: string;
47
+ }): void;
48
+ forget(id: number): void;
49
+ invalidate(id: number, atSha: string | null, replacedById: number | null): void;
50
+ promote(id: number, newTier: string): void;
51
+ demote(id: number, newTier: string): void;
52
+ /**
53
+ * Return the path to the journal file so callers can show it in
54
+ * error messages ("see .sverklo/memories.jsonl for the write log").
55
+ */
56
+ get filePath(): string;
57
+ /**
58
+ * True if the journal file exists on disk. Useful for the doctor
59
+ * command to report journal presence.
60
+ */
61
+ exists(): boolean;
62
+ }
@@ -0,0 +1,136 @@
1
+ // Append-only JSONL journal for memory writes (issue #7).
2
+ //
3
+ // Sverklo's memory layer lives in SQLite for query speed, but users
4
+ // can't easily inspect, diff, or sync a binary database. The Memory
5
+ // MCP reference server showed that append-only JSONL is the right
6
+ // format for user-facing memory: human-readable, git-diffable,
7
+ // trivially syncable, and impossible to corrupt in interesting ways.
8
+ //
9
+ // This module ships the JSONL format as a *mirror* of SQLite, not a
10
+ // replacement. Every memory write (remember / forget / promote /
11
+ // demote / invalidate) lands in both places. If the two ever
12
+ // disagree, SQLite is still the source of truth for 0.2.x — the
13
+ // migration to "JSONL as source of truth" is a later, more invasive
14
+ // step tracked in the same issue.
15
+ //
16
+ // Why mirror, not replace, in this step:
17
+ // - Zero risk to existing users. If the journal is broken, memory
18
+ // still works from SQLite.
19
+ // - Users get the user-visible benefit today (cat / git diff /
20
+ // grep the journal) without waiting for the full migration.
21
+ // - The journal can be replayed into a fresh SQLite if the index
22
+ // is ever deleted — a cheap disaster-recovery path.
23
+ //
24
+ // Format: one JSON object per line, one operation per object. Writes
25
+ // are append-only. Tombstones (forget) are records, not file rewrites.
26
+ // Compaction is a later feature — for now we just grow.
27
+ //
28
+ // File location: `<project>/.sverklo/memories.jsonl`. This lives
29
+ // inside the project root on purpose so users can commit it alongside
30
+ // their code. `.sverklo/index.db` and friends stay in `~/.sverklo/`
31
+ // (the user cache) because those are machine-specific.
32
+ import { appendFileSync, existsSync, mkdirSync } from "node:fs";
33
+ import { dirname, join } from "node:path";
34
+ import { logError } from "../utils/logger.js";
35
+ /**
36
+ * MemoryJournal writes append-only JSONL to <project>/.sverklo/memories.jsonl.
37
+ *
38
+ * Writes are synchronous on purpose: the journal must be committed
39
+ * before the corresponding SQLite write returns to the caller, so we
40
+ * never have a window where SQLite succeeds and the journal is missing
41
+ * the record. Agent workloads are low-frequency on the memory side
42
+ * (dozens of writes per session, not thousands) so sync writes are
43
+ * fine. If memory write volume grows we can batch.
44
+ *
45
+ * Errors are swallowed with a log. The journal is advisory for 0.2.x —
46
+ * a broken journal must not break memory.
47
+ */
48
+ export class MemoryJournal {
49
+ path;
50
+ ready = false;
51
+ constructor(projectRoot) {
52
+ this.path = join(projectRoot, ".sverklo", "memories.jsonl");
53
+ }
54
+ ensureDir() {
55
+ if (this.ready)
56
+ return;
57
+ try {
58
+ mkdirSync(dirname(this.path), { recursive: true });
59
+ this.ready = true;
60
+ }
61
+ catch (err) {
62
+ logError("MemoryJournal: could not create .sverklo directory", err);
63
+ }
64
+ }
65
+ write(entry) {
66
+ this.ensureDir();
67
+ try {
68
+ appendFileSync(this.path, JSON.stringify(entry) + "\n", "utf-8");
69
+ }
70
+ catch (err) {
71
+ logError("MemoryJournal: append failed", err);
72
+ }
73
+ }
74
+ remember(params) {
75
+ this.write({
76
+ op: "remember",
77
+ id: params.id,
78
+ ts: new Date().toISOString(),
79
+ content: params.content,
80
+ category: params.category,
81
+ tags: params.tags ?? null,
82
+ confidence: params.confidence,
83
+ git_sha: params.git_sha ?? null,
84
+ git_branch: params.git_branch ?? null,
85
+ related_files: params.related_files ?? null,
86
+ tier: params.tier,
87
+ });
88
+ }
89
+ forget(id) {
90
+ this.write({
91
+ op: "forget",
92
+ id,
93
+ ts: new Date().toISOString(),
94
+ });
95
+ }
96
+ invalidate(id, atSha, replacedById) {
97
+ this.write({
98
+ op: "invalidate",
99
+ id,
100
+ ts: new Date().toISOString(),
101
+ invalidated_at_sha: atSha ?? null,
102
+ replaced_by_id: replacedById ?? null,
103
+ });
104
+ }
105
+ promote(id, newTier) {
106
+ this.write({
107
+ op: "promote",
108
+ id,
109
+ ts: new Date().toISOString(),
110
+ new_tier: newTier,
111
+ });
112
+ }
113
+ demote(id, newTier) {
114
+ this.write({
115
+ op: "demote",
116
+ id,
117
+ ts: new Date().toISOString(),
118
+ new_tier: newTier,
119
+ });
120
+ }
121
+ /**
122
+ * Return the path to the journal file so callers can show it in
123
+ * error messages ("see .sverklo/memories.jsonl for the write log").
124
+ */
125
+ get filePath() {
126
+ return this.path;
127
+ }
128
+ /**
129
+ * True if the journal file exists on disk. Useful for the doctor
130
+ * command to report journal presence.
131
+ */
132
+ exists() {
133
+ return existsSync(this.path);
134
+ }
135
+ }
136
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../../src/memory/journal.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 { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { MemoryJournal } from "./journal.js";
6
+ describe("MemoryJournal", () => {
7
+ let projectRoot;
8
+ beforeEach(() => {
9
+ projectRoot = mkdtempSync(join(tmpdir(), "sverklo-journal-"));
10
+ });
11
+ afterEach(() => {
12
+ try {
13
+ rmSync(projectRoot, { recursive: true, force: true });
14
+ }
15
+ catch { }
16
+ });
17
+ it("creates the .sverklo directory and writes an append-only file", () => {
18
+ const j = new MemoryJournal(projectRoot);
19
+ j.remember({
20
+ id: 1,
21
+ content: "All timestamps are UTC.",
22
+ category: "procedural",
23
+ tags: ["timezone"],
24
+ confidence: 1.0,
25
+ git_sha: "abc123",
26
+ git_branch: "main",
27
+ related_files: ["src/time.ts"],
28
+ tier: "core",
29
+ });
30
+ const path = join(projectRoot, ".sverklo", "memories.jsonl");
31
+ expect(existsSync(path)).toBe(true);
32
+ const content = readFileSync(path, "utf-8");
33
+ expect(content.split("\n").filter(Boolean).length).toBe(1);
34
+ const entry = JSON.parse(content.trim());
35
+ expect(entry.op).toBe("remember");
36
+ expect(entry.id).toBe(1);
37
+ expect(entry.content).toBe("All timestamps are UTC.");
38
+ expect(entry.tier).toBe("core");
39
+ expect(entry.git_sha).toBe("abc123");
40
+ expect(typeof entry.ts).toBe("string");
41
+ });
42
+ it("appends multiple operations in order", () => {
43
+ const j = new MemoryJournal(projectRoot);
44
+ j.remember({
45
+ id: 1,
46
+ content: "First",
47
+ category: "decision",
48
+ confidence: 1.0,
49
+ tier: "archive",
50
+ });
51
+ j.promote(1, "core");
52
+ j.forget(1);
53
+ const path = join(projectRoot, ".sverklo", "memories.jsonl");
54
+ const lines = readFileSync(path, "utf-8").trim().split("\n");
55
+ expect(lines.length).toBe(3);
56
+ const ops = lines.map((l) => JSON.parse(l).op);
57
+ expect(ops).toEqual(["remember", "promote", "forget"]);
58
+ });
59
+ it("records tombstones for forget so the journal stays replayable", () => {
60
+ const j = new MemoryJournal(projectRoot);
61
+ j.remember({ id: 5, content: "x", category: "context", confidence: 1.0, tier: "archive" });
62
+ j.forget(5);
63
+ const path = join(projectRoot, ".sverklo", "memories.jsonl");
64
+ const content = readFileSync(path, "utf-8");
65
+ // Second line is the tombstone — forget must not rewrite/truncate.
66
+ expect(content).toContain('"op":"remember"');
67
+ expect(content).toContain('"op":"forget"');
68
+ });
69
+ it("records invalidate with atSha and replacedById", () => {
70
+ const j = new MemoryJournal(projectRoot);
71
+ j.invalidate(3, "def456", 7);
72
+ const path = join(projectRoot, ".sverklo", "memories.jsonl");
73
+ const entry = JSON.parse(readFileSync(path, "utf-8").trim());
74
+ expect(entry.op).toBe("invalidate");
75
+ expect(entry.id).toBe(3);
76
+ expect(entry.invalidated_at_sha).toBe("def456");
77
+ expect(entry.replaced_by_id).toBe(7);
78
+ });
79
+ it("is robust to optional fields being undefined", () => {
80
+ const j = new MemoryJournal(projectRoot);
81
+ j.remember({
82
+ id: 2,
83
+ content: "minimal",
84
+ category: "context",
85
+ confidence: 1.0,
86
+ tier: "archive",
87
+ });
88
+ const entry = JSON.parse(readFileSync(join(projectRoot, ".sverklo", "memories.jsonl"), "utf-8").trim());
89
+ expect(entry.tags).toBeNull();
90
+ expect(entry.git_sha).toBeNull();
91
+ expect(entry.related_files).toBeNull();
92
+ });
93
+ it("exposes the file path via filePath getter", () => {
94
+ const j = new MemoryJournal(projectRoot);
95
+ expect(j.filePath).toBe(join(projectRoot, ".sverklo", "memories.jsonl"));
96
+ });
97
+ });
98
+ //# sourceMappingURL=journal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.test.js","sourceRoot":"","sources":["../../../src/memory/journal.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export type SverkloMode = "embedded" | "shared" | "cloud";
2
+ export interface ModeResolution {
3
+ mode: SverkloMode;
4
+ reason: string;
5
+ }
6
+ export declare function resolveMode(argv: string[], env?: NodeJS.ProcessEnv): ModeResolution;
7
+ export declare class SverkloModeError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * Produce a clear, user-facing error for modes that are reserved but
12
+ * not yet implemented. Called from the CLI before attempting to start
13
+ * the server in an unsupported mode. We explicitly link the tracking
14
+ * issue so users have a concrete next step.
15
+ */
16
+ export declare function notYetImplemented(mode: SverkloMode): string;
@@ -0,0 +1,104 @@
1
+ // Runtime mode selection (issue #12).
2
+ //
3
+ // Sverklo is local-first by default and intentionally so. But a shared /
4
+ // team / cloud story is a natural monetization path for later. To avoid
5
+ // painting ourselves into a corner, we introduce the mode concept now
6
+ // as a single seam the rest of the code can branch on.
7
+ //
8
+ // Three modes:
9
+ //
10
+ // embedded (default)
11
+ // Current behavior. Indexes live in ~/.sverklo/<project>/, one
12
+ // process per project, nothing remote. Zero config. Everything
13
+ // works without opting in.
14
+ //
15
+ // shared (reserved)
16
+ // A local daemon process holds indexes for multiple projects and
17
+ // multiple MCP server invocations connect to it. Useful for teams
18
+ // running multiple checkouts on the same laptop, or for the
19
+ // eventual "team cache" feature. NOT YET IMPLEMENTED.
20
+ //
21
+ // cloud (reserved)
22
+ // A hosted sverklo endpoint running the same tool set against
23
+ // centrally-indexed repos. For the eventual Sverklo Team tier.
24
+ // NOT YET IMPLEMENTED.
25
+ //
26
+ // The mode resolver below is the single place where the CLI flag /
27
+ // env var is parsed. If you need mode-specific behavior, call
28
+ // getMode() and branch on the return value — don't re-parse.
29
+ //
30
+ // Hard-failure rule (inspired by Qdrant MCP's QDRANT_LOCAL_PATH vs
31
+ // QDRANT_URL split): ambiguous config is an error, never a silent
32
+ // fallback. If a user sets BOTH --mode cloud AND a local path, crash
33
+ // with a clear message so they know to pick one.
34
+ export function resolveMode(argv, env = process.env) {
35
+ // Precedence: CLI flag > env var > default.
36
+ let fromArgv = null;
37
+ for (const arg of argv) {
38
+ if (arg.startsWith("--mode=")) {
39
+ const val = arg.split("=")[1];
40
+ if (isSverkloMode(val))
41
+ fromArgv = val;
42
+ }
43
+ }
44
+ if (fromArgv) {
45
+ return { mode: fromArgv, reason: `--mode=${fromArgv}` };
46
+ }
47
+ const fromEnv = env.SVERKLO_MODE;
48
+ if (fromEnv && isSverkloMode(fromEnv)) {
49
+ return { mode: fromEnv, reason: `SVERKLO_MODE=${fromEnv}` };
50
+ }
51
+ if (fromEnv && !isSverkloMode(fromEnv)) {
52
+ throw new SverkloModeError(`Invalid SVERKLO_MODE=${fromEnv}. Must be one of: embedded, shared, cloud.`);
53
+ }
54
+ return { mode: "embedded", reason: "default" };
55
+ }
56
+ export class SverkloModeError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "SverkloModeError";
60
+ }
61
+ }
62
+ function isSverkloMode(value) {
63
+ return value === "embedded" || value === "shared" || value === "cloud";
64
+ }
65
+ /**
66
+ * Produce a clear, user-facing error for modes that are reserved but
67
+ * not yet implemented. Called from the CLI before attempting to start
68
+ * the server in an unsupported mode. We explicitly link the tracking
69
+ * issue so users have a concrete next step.
70
+ */
71
+ export function notYetImplemented(mode) {
72
+ if (mode === "shared") {
73
+ return [
74
+ "Sverklo 'shared' mode is reserved for a future release and not yet implemented.",
75
+ "",
76
+ "Shared mode will let a local sverklo daemon hold indexes for multiple",
77
+ "projects and serve multiple MCP server invocations. Tracked in:",
78
+ "",
79
+ " https://github.com/sverklo/sverklo/issues/12",
80
+ "",
81
+ "For now, run each project with the default 'embedded' mode:",
82
+ "",
83
+ " sverklo /path/to/project",
84
+ "",
85
+ ].join("\n");
86
+ }
87
+ if (mode === "cloud") {
88
+ return [
89
+ "Sverklo 'cloud' mode is reserved for the eventual Sverklo Team tier",
90
+ "and not yet implemented.",
91
+ "",
92
+ "Cloud mode will talk to a hosted sverklo endpoint running the same",
93
+ "tool set against centrally-indexed repos. Tracked in:",
94
+ "",
95
+ " https://github.com/sverklo/sverklo/issues/12",
96
+ "",
97
+ "For now, use 'embedded' mode (the default) — everything stays local",
98
+ "and private.",
99
+ "",
100
+ ].join("\n");
101
+ }
102
+ return "";
103
+ }
104
+ //# sourceMappingURL=modes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modes.js","sourceRoot":"","sources":["../../src/modes.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,sEAAsE;AACtE,uDAAuD;AACvD,EAAE;AACF,eAAe;AACf,EAAE;AACF,uBAAuB;AACvB,mEAAmE;AACnE,mEAAmE;AACnE,+BAA+B;AAC/B,EAAE;AACF,sBAAsB;AACtB,qEAAqE;AACrE,sEAAsE;AACtE,gEAAgE;AAChE,0DAA0D;AAC1D,EAAE;AACF,qBAAqB;AACrB,kEAAkE;AAClE,mEAAmE;AACnE,2BAA2B;AAC3B,EAAE;AACF,mEAAmE;AACnE,8DAA8D;AAC9D,6DAA6D;AAC7D,EAAE;AACF,mEAAmE;AACnE,kEAAkE;AAClE,qEAAqE;AACrE,iDAAiD;AASjD,MAAM,UAAU,WAAW,CACzB,IAAc,EACd,MAAyB,OAAO,CAAC,GAAG;IAEpC,4CAA4C;IAC5C,IAAI,QAAQ,GAAuB,IAAI,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,aAAa,CAAC,GAAG,CAAC;gBAAE,QAAQ,GAAG,GAAG,CAAC;QACzC,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,QAAQ,EAAE,EAAE,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC;IACjC,IAAI,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,OAAO,EAAE,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,gBAAgB,CACxB,wBAAwB,OAAO,4CAA4C,CAC5E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED,SAAS,aAAa,CAAC,KAAyB;IAC9C,OAAO,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IACjD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,iFAAiF;YACjF,EAAE;YACF,uEAAuE;YACvE,iEAAiE;YACjE,EAAE;YACF,gDAAgD;YAChD,EAAE;YACF,6DAA6D;YAC7D,EAAE;YACF,4BAA4B;YAC5B,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,OAAO;YACL,qEAAqE;YACrE,0BAA0B;YAC1B,EAAE;YACF,oEAAoE;YACpE,uDAAuD;YACvD,EAAE;YACF,gDAAgD;YAChD,EAAE;YACF,qEAAqE;YACrE,cAAc;YACd,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { resolveMode, notYetImplemented, SverkloModeError } from "./modes.js";
3
+ describe("resolveMode", () => {
4
+ it("defaults to embedded when nothing is set", () => {
5
+ const result = resolveMode([], {});
6
+ expect(result.mode).toBe("embedded");
7
+ expect(result.reason).toBe("default");
8
+ });
9
+ it("reads --mode=embedded from argv", () => {
10
+ const result = resolveMode(["--mode=embedded"], {});
11
+ expect(result.mode).toBe("embedded");
12
+ expect(result.reason).toContain("embedded");
13
+ });
14
+ it("reads SVERKLO_MODE from env", () => {
15
+ const result = resolveMode([], { SVERKLO_MODE: "shared" });
16
+ expect(result.mode).toBe("shared");
17
+ expect(result.reason).toContain("shared");
18
+ });
19
+ it("argv takes precedence over env", () => {
20
+ const result = resolveMode(["--mode=cloud"], { SVERKLO_MODE: "embedded" });
21
+ expect(result.mode).toBe("cloud");
22
+ });
23
+ it("throws on invalid SVERKLO_MODE value", () => {
24
+ expect(() => resolveMode([], { SVERKLO_MODE: "lol" })).toThrow(SverkloModeError);
25
+ });
26
+ it("ignores invalid --mode= value and falls back to default", () => {
27
+ // We don't throw on invalid CLI arg because the user might have a
28
+ // typo and we want to fail loud only via env (which is harder to
29
+ // typo-correct). Behavior: invalid CLI flag is silently ignored,
30
+ // env takes over, default applies.
31
+ const result = resolveMode(["--mode=bogus"], {});
32
+ expect(result.mode).toBe("embedded");
33
+ });
34
+ });
35
+ describe("notYetImplemented", () => {
36
+ it("returns a non-empty message for shared", () => {
37
+ expect(notYetImplemented("shared")).toContain("not yet implemented");
38
+ expect(notYetImplemented("shared")).toContain("issues/12");
39
+ });
40
+ it("returns a non-empty message for cloud", () => {
41
+ expect(notYetImplemented("cloud")).toContain("not yet implemented");
42
+ expect(notYetImplemented("cloud")).toContain("issues/12");
43
+ });
44
+ it("returns empty string for embedded (which is implemented)", () => {
45
+ expect(notYetImplemented("embedded")).toBe("");
46
+ });
47
+ });
48
+ //# sourceMappingURL=modes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modes.test.js","sourceRoot":"","sources":["../../src/modes.test.ts"],"names":[],"mappings":"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"}