@bubstack/moe-memory 0.1.0

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 (157) hide show
  1. package/.mcp.json +18 -0
  2. package/README.md +47 -0
  3. package/agents/search-conversations.md +171 -0
  4. package/dist/LICENSE +25 -0
  5. package/dist/cli.d.ts +25 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +125 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/codex-hook-trust.d.ts +4 -0
  10. package/dist/codex-hook-trust.d.ts.map +1 -0
  11. package/dist/codex-hook-trust.js +114 -0
  12. package/dist/codex-hook-trust.js.map +1 -0
  13. package/dist/codex-support.d.ts +6 -0
  14. package/dist/codex-support.d.ts.map +1 -0
  15. package/dist/codex-support.js +29 -0
  16. package/dist/codex-support.js.map +1 -0
  17. package/dist/constants.d.ts +14 -0
  18. package/dist/constants.d.ts.map +1 -0
  19. package/dist/constants.js +14 -0
  20. package/dist/constants.js.map +1 -0
  21. package/dist/db.d.ts +103 -0
  22. package/dist/db.d.ts.map +1 -0
  23. package/dist/db.js +430 -0
  24. package/dist/db.js.map +1 -0
  25. package/dist/doctor-cli.d.ts +3 -0
  26. package/dist/doctor-cli.d.ts.map +1 -0
  27. package/dist/doctor-cli.js +43 -0
  28. package/dist/doctor-cli.js.map +1 -0
  29. package/dist/doctor.d.ts +17 -0
  30. package/dist/doctor.d.ts.map +1 -0
  31. package/dist/doctor.js +98 -0
  32. package/dist/doctor.js.map +1 -0
  33. package/dist/embedding-migration.d.ts +73 -0
  34. package/dist/embedding-migration.d.ts.map +1 -0
  35. package/dist/embedding-migration.js +120 -0
  36. package/dist/embedding-migration.js.map +1 -0
  37. package/dist/embeddings.d.ts +78 -0
  38. package/dist/embeddings.d.ts.map +1 -0
  39. package/dist/embeddings.js +180 -0
  40. package/dist/embeddings.js.map +1 -0
  41. package/dist/file-lock.d.ts +39 -0
  42. package/dist/file-lock.d.ts.map +1 -0
  43. package/dist/file-lock.js +91 -0
  44. package/dist/file-lock.js.map +1 -0
  45. package/dist/index-cli.d.ts +10 -0
  46. package/dist/index-cli.d.ts.map +1 -0
  47. package/dist/index-cli.js +195 -0
  48. package/dist/index-cli.js.map +1 -0
  49. package/dist/index.d.ts +12 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +19 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/indexer.d.ts +4 -0
  54. package/dist/indexer.d.ts.map +1 -0
  55. package/dist/indexer.js +318 -0
  56. package/dist/indexer.js.map +1 -0
  57. package/dist/install-check.d.ts +48 -0
  58. package/dist/install-check.d.ts.map +1 -0
  59. package/dist/install-check.js +82 -0
  60. package/dist/install-check.js.map +1 -0
  61. package/dist/journal/index.d.ts +5 -0
  62. package/dist/journal/index.d.ts.map +1 -0
  63. package/dist/journal/index.js +5 -0
  64. package/dist/journal/index.js.map +1 -0
  65. package/dist/journal/legacy-sidecars.d.ts +74 -0
  66. package/dist/journal/legacy-sidecars.d.ts.map +1 -0
  67. package/dist/journal/legacy-sidecars.js +151 -0
  68. package/dist/journal/legacy-sidecars.js.map +1 -0
  69. package/dist/journal/markdown.d.ts +91 -0
  70. package/dist/journal/markdown.d.ts.map +1 -0
  71. package/dist/journal/markdown.js +234 -0
  72. package/dist/journal/markdown.js.map +1 -0
  73. package/dist/journal/search.d.ts +91 -0
  74. package/dist/journal/search.d.ts.map +1 -0
  75. package/dist/journal/search.js +203 -0
  76. package/dist/journal/search.js.map +1 -0
  77. package/dist/journal/store.d.ts +96 -0
  78. package/dist/journal/store.d.ts.map +1 -0
  79. package/dist/journal/store.js +270 -0
  80. package/dist/journal/store.js.map +1 -0
  81. package/dist/journal-cli.d.ts +19 -0
  82. package/dist/journal-cli.d.ts.map +1 -0
  83. package/dist/journal-cli.js +153 -0
  84. package/dist/journal-cli.js.map +1 -0
  85. package/dist/logging.d.ts +6 -0
  86. package/dist/logging.d.ts.map +1 -0
  87. package/dist/logging.js +20 -0
  88. package/dist/logging.js.map +1 -0
  89. package/dist/mcp-server.d.ts +34 -0
  90. package/dist/mcp-server.d.ts.map +1 -0
  91. package/dist/mcp-server.js +607 -0
  92. package/dist/mcp-server.js.map +1 -0
  93. package/dist/parser.d.ts +11 -0
  94. package/dist/parser.d.ts.map +1 -0
  95. package/dist/parser.js +445 -0
  96. package/dist/parser.js.map +1 -0
  97. package/dist/paths.d.ts +103 -0
  98. package/dist/paths.d.ts.map +1 -0
  99. package/dist/paths.js +258 -0
  100. package/dist/paths.js.map +1 -0
  101. package/dist/search-cli.d.ts +3 -0
  102. package/dist/search-cli.d.ts.map +1 -0
  103. package/dist/search-cli.js +138 -0
  104. package/dist/search-cli.js.map +1 -0
  105. package/dist/search.d.ts +36 -0
  106. package/dist/search.d.ts.map +1 -0
  107. package/dist/search.js +371 -0
  108. package/dist/search.js.map +1 -0
  109. package/dist/show-cli.d.ts +3 -0
  110. package/dist/show-cli.d.ts.map +1 -0
  111. package/dist/show-cli.js +60 -0
  112. package/dist/show-cli.js.map +1 -0
  113. package/dist/show.d.ts +3 -0
  114. package/dist/show.d.ts.map +1 -0
  115. package/dist/show.js +984 -0
  116. package/dist/show.js.map +1 -0
  117. package/dist/stats-cli.d.ts +3 -0
  118. package/dist/stats-cli.d.ts.map +1 -0
  119. package/dist/stats-cli.js +51 -0
  120. package/dist/stats-cli.js.map +1 -0
  121. package/dist/stats.d.ts +19 -0
  122. package/dist/stats.d.ts.map +1 -0
  123. package/dist/stats.js +116 -0
  124. package/dist/stats.js.map +1 -0
  125. package/dist/summarizer.d.ts +83 -0
  126. package/dist/summarizer.d.ts.map +1 -0
  127. package/dist/summarizer.js +526 -0
  128. package/dist/summarizer.js.map +1 -0
  129. package/dist/summary-sentinel.d.ts +31 -0
  130. package/dist/summary-sentinel.d.ts.map +1 -0
  131. package/dist/summary-sentinel.js +80 -0
  132. package/dist/summary-sentinel.js.map +1 -0
  133. package/dist/sync-cli.d.ts +15 -0
  134. package/dist/sync-cli.d.ts.map +1 -0
  135. package/dist/sync-cli.js +202 -0
  136. package/dist/sync-cli.js.map +1 -0
  137. package/dist/sync.d.ts +35 -0
  138. package/dist/sync.d.ts.map +1 -0
  139. package/dist/sync.js +204 -0
  140. package/dist/sync.js.map +1 -0
  141. package/dist/types.d.ts +125 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +32 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/verify.d.ts +30 -0
  146. package/dist/verify.d.ts.map +1 -0
  147. package/dist/verify.js +174 -0
  148. package/dist/verify.js.map +1 -0
  149. package/dist/version.d.ts +13 -0
  150. package/dist/version.d.ts.map +1 -0
  151. package/dist/version.js +13 -0
  152. package/dist/version.js.map +1 -0
  153. package/hooks/hooks.json +15 -0
  154. package/package.json +71 -0
  155. package/prompts/search-agent.md +160 -0
  156. package/skills/remembering-conversations/MCP-TOOLS.md +147 -0
  157. package/skills/remembering-conversations/SKILL.md +97 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * One-way importer for private-journal-mcp's `.embedding` sidecars.
3
+ *
4
+ * Upstream wrote, beside every `<entry>.md`, an `<entry>.embedding` JSON file:
5
+ *
6
+ * { embedding: number[], text: string, sections: string[], timestamp: number, path: string }
7
+ *
8
+ * Two reasons that data cannot simply be loaded into the new index:
9
+ *
10
+ * 1. **The vectors are from a different encoder.** Sidecars were produced by
11
+ * `Xenova/all-MiniLM-L6-v2`; this package embeds with `Xenova/bge-small-en-v1.5`
12
+ * at dtype q8 and an asymmetric query prefix. Both are 384-dimensional, so a
13
+ * vec0 column accepts either without complaint and a mixed corpus ranks
14
+ * wrongly with no error. So the sidecar's `embedding` array is DISCARDED and
15
+ * the entry is re-embedded from its markdown.
16
+ * 2. **`path` was baked in absolute.** Upstream's search returned the path stored
17
+ * inside the JSON rather than the path it had just walked, so renaming the
18
+ * journal directory produced results that `read_journal_entry` then refused.
19
+ * So the sidecar's `path` is DISCARDED too, and the walk decides.
20
+ *
21
+ * Which leaves nothing worth importing from the sidecar itself. What this
22
+ * function actually does is find the orphans — `.md` files with a sidecar whose
23
+ * day-directory is not where the new layout expects it — and, mainly, delete the
24
+ * sidecars once the entry is indexed, so the old and new indexes cannot drift.
25
+ *
26
+ * It is opt-in (`moe-memory journal import-legacy`) and never runs on its own:
27
+ * deleting a file the user might still want read by an upstream install is not
28
+ * something to do silently.
29
+ *
30
+ * FIXED: it searched only the CURRENT roots. The command exists because the
31
+ * paths moved on import — `<project>/.private-journal` → `<project>/.moe-journal`
32
+ * and `~/.private-journal` → `<data dir>/journal` — so on any install that had
33
+ * not already hand-copied its journal across, it walked exactly the two
34
+ * directories the sidecars provably are not in, found nothing, and printed
35
+ * "Legacy .embedding sidecars found: 0". Indistinguishable from success. It now
36
+ * also surveys the upstream directories (`findLegacyJournalRoots`) and reports
37
+ * them in `legacy`, so the caller can tell the user their data is over there.
38
+ *
39
+ * It reports rather than migrates, deliberately, matching `findLegacyDataDir`.
40
+ * Journal entries are private reflections; quietly relocating them is worse
41
+ * than quietly relocating a conversation archive, and that was already judged
42
+ * too much to do behind someone's back.
43
+ */
44
+ import fs from "node:fs/promises";
45
+ import path from "node:path";
46
+ import { getJournalIndexState } from "../db.js";
47
+ import { findLegacyJournalRoots } from "../paths.js";
48
+ import { DAY_DIR_PATTERN, journalEntryId } from "./markdown.js";
49
+ /** Count `.md` entries and `.embedding` sidecars under a journal root. */
50
+ async function surveyRoot(root) {
51
+ let entries = 0;
52
+ let sidecars = 0;
53
+ let dayDirs;
54
+ try {
55
+ dayDirs = await fs.readdir(root);
56
+ }
57
+ catch {
58
+ return { entries, sidecars };
59
+ }
60
+ for (const dayDir of dayDirs) {
61
+ if (!DAY_DIR_PATTERN.test(dayDir))
62
+ continue;
63
+ let files;
64
+ try {
65
+ files = await fs.readdir(path.join(root, dayDir));
66
+ }
67
+ catch {
68
+ continue;
69
+ }
70
+ for (const file of files) {
71
+ if (file.endsWith(".md"))
72
+ entries++;
73
+ else if (file.endsWith(".embedding"))
74
+ sidecars++;
75
+ }
76
+ }
77
+ return { entries, sidecars };
78
+ }
79
+ export async function importLegacyJournalSidecars(db, store, options = {}) {
80
+ const result = {
81
+ found: 0,
82
+ indexed: 0,
83
+ removed: 0,
84
+ orphaned: [],
85
+ legacy: [],
86
+ };
87
+ // Look where the data actually is before reporting zero.
88
+ for (const legacyRoot of findLegacyJournalRoots()) {
89
+ const survey = await surveyRoot(legacyRoot);
90
+ if (survey.entries === 0 && survey.sidecars === 0)
91
+ continue;
92
+ result.legacy.push({ root: legacyRoot, ...survey });
93
+ }
94
+ // Re-index first: the markdown files are the source of truth, and every
95
+ // sidecar's entry has to exist in the new index before we consider deleting it.
96
+ await store.indexJournal(db);
97
+ const state = getJournalIndexState(db);
98
+ for (const root of store.roots()) {
99
+ let dayDirs;
100
+ try {
101
+ dayDirs = await fs.readdir(root.path);
102
+ }
103
+ catch {
104
+ continue;
105
+ }
106
+ for (const dayDir of dayDirs) {
107
+ if (!DAY_DIR_PATTERN.test(dayDir))
108
+ continue;
109
+ const dayPath = path.join(root.path, dayDir);
110
+ let files;
111
+ try {
112
+ files = await fs.readdir(dayPath);
113
+ }
114
+ catch {
115
+ continue;
116
+ }
117
+ for (const file of files) {
118
+ if (!file.endsWith(".embedding"))
119
+ continue;
120
+ result.found++;
121
+ const sidecarPath = path.join(dayPath, file);
122
+ const entryPath = sidecarPath.replace(/\.embedding$/, ".md");
123
+ try {
124
+ await fs.access(entryPath);
125
+ }
126
+ catch {
127
+ result.orphaned.push(sidecarPath);
128
+ continue;
129
+ }
130
+ // The scope the walk would assign is not knowable here without reading
131
+ // the entry, so check both possibilities.
132
+ const indexed = state.has(journalEntryId("project", root.path, entryPath)) ||
133
+ state.has(journalEntryId("user", root.path, entryPath));
134
+ if (!indexed)
135
+ continue;
136
+ result.indexed++;
137
+ if (options.remove) {
138
+ try {
139
+ await fs.unlink(sidecarPath);
140
+ result.removed++;
141
+ }
142
+ catch (error) {
143
+ console.error(`moe-memory: could not remove legacy sidecar ${sidecarPath}: ${String(error)}`);
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ return result;
150
+ }
151
+ //# sourceMappingURL=legacy-sidecars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-sidecars.js","sourceRoot":"","sources":["../../src/journal/legacy-sidecars.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The journal entry's on-disk format.
3
+ *
4
+ * Carried over from private-journal-mcp unchanged in substance, because it is a
5
+ * data contract with files already written:
6
+ *
7
+ * <root>/YYYY-MM-DD/HH-MM-SS-µµµµµµ.md
8
+ *
9
+ * ---
10
+ * title: "3:04:05 PM - May 27, 2025"
11
+ * date: 2025-05-27T20:04:05.123Z
12
+ * timestamp: 1748376245123
13
+ * ---
14
+ *
15
+ * ## Reflections
16
+ * …
17
+ *
18
+ * `µµµµµµ` is pseudo-microseconds — `ms * 1000 + floor(random() * 1000)` — so two
19
+ * writes inside the same millisecond collide about one time in a thousand. Kept
20
+ * as-is: changing the filename shape would orphan every existing entry, and the
21
+ * collision is the upstream behaviour its tests pin.
22
+ *
23
+ * What is NOT carried over is the `.embedding` JSON sidecar. That was the only
24
+ * enumeration path in the whole upstream package — search, list_recent_entries
25
+ * and read_recent_entries all read `*.embedding` and never listed `*.md` — so an
26
+ * entry whose embedding failed was written and then permanently invisible. The
27
+ * markdown files are the source of truth here and SQLite is a rebuildable index.
28
+ */
29
+ import { type JournalScope, type JournalThoughts } from "../types.js";
30
+ /** `YYYY-MM-DD` day-directory name. */
31
+ export declare const DAY_DIR_PATTERN: RegExp;
32
+ export declare function formatDayDirectory(date: Date): string;
33
+ export declare function formatEntryBasename(date: Date): string;
34
+ /**
35
+ * Render an entry. Section order is fixed by JOURNAL_SECTION_HEADINGS and empty
36
+ * categories are omitted.
37
+ */
38
+ export declare function formatEntry(thoughts: JournalThoughts, timestamp: Date): string;
39
+ /**
40
+ * Strip frontmatter and section headers, and harvest the headings.
41
+ *
42
+ * The returned `text` is what gets embedded; `sections` is what the
43
+ * `search_journal` section filter matches against.
44
+ */
45
+ export declare function extractSearchableText(markdownContent: string): {
46
+ text: string;
47
+ sections: string[];
48
+ };
49
+ export declare function sectionsMatch(entrySections: string[], requested: string[]): boolean;
50
+ /**
51
+ * Read the epoch-millisecond `timestamp:` out of an entry's frontmatter.
52
+ *
53
+ * Preferred over `timestampFromEntryPath` because the filename only carries
54
+ * second resolution — `HH-MM-SS-µµµµµµ` looks precise but the µ field is
55
+ * `ms * 1000 + random`, so it cannot be read back as a time. Two entries written
56
+ * in the same second are indistinguishable by path and would sort arbitrarily in
57
+ * a chronological listing.
58
+ */
59
+ export declare function timestampFromFrontmatter(markdownContent: string): number | null;
60
+ /**
61
+ * Recover the entry's timestamp from its path when the frontmatter is not to
62
+ * hand. Returns null if either the filename or the day directory does not match.
63
+ */
64
+ export declare function timestampFromEntryPath(filePath: string): Date | null;
65
+ /**
66
+ * Id for an entry, and it is deliberately asymmetric by scope:
67
+ *
68
+ * user md5(scope + ':' + path relative to its root)
69
+ * project md5(scope + ':' + absolute root + ':' + path relative to its root)
70
+ *
71
+ * Neither form is the absolute entry path. Upstream stored that as the record's
72
+ * identity inside the sidecar, so renaming the journal directory made every
73
+ * existing record unreadable — search listed them and read refused them. A
74
+ * root-relative id survives the root moving, and the absolute `path` column is
75
+ * refreshed from the walk on every index run.
76
+ *
77
+ * `project` scope has to give up some of that. Every project on the machine
78
+ * shares one database and a relative entry path is only a date directory plus a
79
+ * timestamped filename, so two repos that journalled in the same second produced
80
+ * the same id and one silently replaced the other. The root is what distinguishes
81
+ * them. There is exactly one user root, so including it there would buy no
82
+ * discrimination and cost the move-stability property for nothing — hence the
83
+ * asymmetry rather than a blanket change.
84
+ */
85
+ export declare function journalEntryId(scope: JournalScope, root: string, entryPath: string): string;
86
+ /**
87
+ * Pick the excerpt to show for a hit: a sliding window scored by how many query
88
+ * words it contains. Carried over from private-journal-mcp unchanged.
89
+ */
90
+ export declare function generateExcerpt(text: string, query: string, maxLength?: number): string;
91
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/journal/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EAA4B,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhG,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAAwB,CAAC;AAErD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAKrD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAQtD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,CA2B9E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,eAAe,EAAE,MAAM,GAAG;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAgBA;AAuBD,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAQnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAwBpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAmB3F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CA0BpF"}
@@ -0,0 +1,234 @@
1
+ /**
2
+ * The journal entry's on-disk format.
3
+ *
4
+ * Carried over from private-journal-mcp unchanged in substance, because it is a
5
+ * data contract with files already written:
6
+ *
7
+ * <root>/YYYY-MM-DD/HH-MM-SS-µµµµµµ.md
8
+ *
9
+ * ---
10
+ * title: "3:04:05 PM - May 27, 2025"
11
+ * date: 2025-05-27T20:04:05.123Z
12
+ * timestamp: 1748376245123
13
+ * ---
14
+ *
15
+ * ## Reflections
16
+ * …
17
+ *
18
+ * `µµµµµµ` is pseudo-microseconds — `ms * 1000 + floor(random() * 1000)` — so two
19
+ * writes inside the same millisecond collide about one time in a thousand. Kept
20
+ * as-is: changing the filename shape would orphan every existing entry, and the
21
+ * collision is the upstream behaviour its tests pin.
22
+ *
23
+ * What is NOT carried over is the `.embedding` JSON sidecar. That was the only
24
+ * enumeration path in the whole upstream package — search, list_recent_entries
25
+ * and read_recent_entries all read `*.embedding` and never listed `*.md` — so an
26
+ * entry whose embedding failed was written and then permanently invisible. The
27
+ * markdown files are the source of truth here and SQLite is a rebuildable index.
28
+ */
29
+ import crypto from "node:crypto";
30
+ import path from "node:path";
31
+ import { JOURNAL_SECTION_HEADINGS } from "../types.js";
32
+ /** `YYYY-MM-DD` day-directory name. */
33
+ export const DAY_DIR_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
34
+ export function formatDayDirectory(date) {
35
+ const year = date.getFullYear();
36
+ const month = String(date.getMonth() + 1).padStart(2, "0");
37
+ const day = String(date.getDate()).padStart(2, "0");
38
+ return `${year}-${month}-${day}`;
39
+ }
40
+ export function formatEntryBasename(date) {
41
+ const hours = String(date.getHours()).padStart(2, "0");
42
+ const minutes = String(date.getMinutes()).padStart(2, "0");
43
+ const seconds = String(date.getSeconds()).padStart(2, "0");
44
+ const microseconds = String(date.getMilliseconds() * 1000 + Math.floor(Math.random() * 1000)).padStart(6, "0");
45
+ return `${hours}-${minutes}-${seconds}-${microseconds}`;
46
+ }
47
+ /**
48
+ * Render an entry. Section order is fixed by JOURNAL_SECTION_HEADINGS and empty
49
+ * categories are omitted.
50
+ */
51
+ export function formatEntry(thoughts, timestamp) {
52
+ const timeDisplay = timestamp.toLocaleTimeString("en-US", {
53
+ hour12: true,
54
+ hour: "numeric",
55
+ minute: "2-digit",
56
+ second: "2-digit",
57
+ });
58
+ const dateDisplay = timestamp.toLocaleDateString("en-US", {
59
+ year: "numeric",
60
+ month: "long",
61
+ day: "numeric",
62
+ });
63
+ const sections = [];
64
+ for (const [key, heading] of JOURNAL_SECTION_HEADINGS) {
65
+ const value = thoughts[key];
66
+ if (value)
67
+ sections.push(`## ${heading}\n\n${value}`);
68
+ }
69
+ return `---
70
+ title: "${timeDisplay} - ${dateDisplay}"
71
+ date: ${timestamp.toISOString()}
72
+ timestamp: ${timestamp.getTime()}
73
+ ---
74
+
75
+ ${sections.join("\n\n")}
76
+ `;
77
+ }
78
+ /**
79
+ * Strip frontmatter and section headers, and harvest the headings.
80
+ *
81
+ * The returned `text` is what gets embedded; `sections` is what the
82
+ * `search_journal` section filter matches against.
83
+ */
84
+ export function extractSearchableText(markdownContent) {
85
+ // Remove YAML frontmatter
86
+ const withoutFrontmatter = markdownContent.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
87
+ const sections = [];
88
+ const sectionMatches = withoutFrontmatter.match(/^## (.+)$/gm);
89
+ if (sectionMatches) {
90
+ sections.push(...sectionMatches.map((match) => match.replace(/^## /, "").trim()));
91
+ }
92
+ const cleanText = withoutFrontmatter
93
+ .replace(/^## .+$/gm, "") // Remove section headers
94
+ .replace(/\n{3,}/g, "\n\n") // Normalize whitespace
95
+ .trim();
96
+ return { text: cleanText, sections };
97
+ }
98
+ /**
99
+ * Compare a caller-supplied section name against the rendered headings stored
100
+ * on the entry.
101
+ *
102
+ * FIXED FROM UPSTREAM. private-journal-mcp compared the caller's value directly
103
+ * against the rendered heading text, so of the six documented values only
104
+ * `reflections` and `observations` ever matched — `project_notes`,
105
+ * `user_context`, `technical_insights` and `world_knowledge` all matched
106
+ * nothing. The broken form was the worked example inside the live tool
107
+ * description, i.e. the model was actively instructed to pass filters that
108
+ * silently returned zero results.
109
+ *
110
+ * Both sides are normalised to lowercase alphanumerics, and the upstream
111
+ * substring leniency is preserved (`reflection` still matches `Reflections`).
112
+ * `feelings` still matches the legacy `## Feelings` heading on pre-2.0.0
113
+ * entries — the one compatibility promise upstream made to data already on disk.
114
+ */
115
+ function normalizeSectionName(value) {
116
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
117
+ }
118
+ export function sectionsMatch(entrySections, requested) {
119
+ if (requested.length === 0)
120
+ return true;
121
+ const normalizedEntry = entrySections.map(normalizeSectionName);
122
+ return requested.some((requestedSection) => {
123
+ const needle = normalizeSectionName(requestedSection);
124
+ if (!needle)
125
+ return false;
126
+ return normalizedEntry.some((entrySection) => entrySection.includes(needle));
127
+ });
128
+ }
129
+ /**
130
+ * Read the epoch-millisecond `timestamp:` out of an entry's frontmatter.
131
+ *
132
+ * Preferred over `timestampFromEntryPath` because the filename only carries
133
+ * second resolution — `HH-MM-SS-µµµµµµ` looks precise but the µ field is
134
+ * `ms * 1000 + random`, so it cannot be read back as a time. Two entries written
135
+ * in the same second are indistinguishable by path and would sort arbitrarily in
136
+ * a chronological listing.
137
+ */
138
+ export function timestampFromFrontmatter(markdownContent) {
139
+ const match = markdownContent.match(/^---\r?\n[\s\S]*?^timestamp:\s*(\d+)\s*$/m);
140
+ const raw = match?.[1];
141
+ if (!raw)
142
+ return null;
143
+ const value = Number.parseInt(raw, 10);
144
+ return Number.isFinite(value) && value > 0 ? value : null;
145
+ }
146
+ /**
147
+ * Recover the entry's timestamp from its path when the frontmatter is not to
148
+ * hand. Returns null if either the filename or the day directory does not match.
149
+ */
150
+ export function timestampFromEntryPath(filePath) {
151
+ const filename = path.basename(filePath, ".md");
152
+ const timeMatch = filename.match(/^(\d{2})-(\d{2})-(\d{2})-\d{6}$/);
153
+ if (!timeMatch)
154
+ return null;
155
+ const dirName = path.basename(path.dirname(filePath));
156
+ const dateMatch = dirName.match(/^(\d{4})-(\d{2})-(\d{2})$/);
157
+ if (!dateMatch)
158
+ return null;
159
+ // Indices are checked by the regexes above; the non-null assertions the
160
+ // upstream version relied on are replaced with explicit reads so
161
+ // noUncheckedIndexedAccess is satisfied without a cast.
162
+ const [, hours, minutes, seconds] = timeMatch;
163
+ const [, year, month, day] = dateMatch;
164
+ if (!hours || !minutes || !seconds || !year || !month || !day)
165
+ return null;
166
+ return new Date(Number.parseInt(year, 10), Number.parseInt(month, 10) - 1, Number.parseInt(day, 10), Number.parseInt(hours, 10), Number.parseInt(minutes, 10), Number.parseInt(seconds, 10));
167
+ }
168
+ /**
169
+ * Id for an entry, and it is deliberately asymmetric by scope:
170
+ *
171
+ * user md5(scope + ':' + path relative to its root)
172
+ * project md5(scope + ':' + absolute root + ':' + path relative to its root)
173
+ *
174
+ * Neither form is the absolute entry path. Upstream stored that as the record's
175
+ * identity inside the sidecar, so renaming the journal directory made every
176
+ * existing record unreadable — search listed them and read refused them. A
177
+ * root-relative id survives the root moving, and the absolute `path` column is
178
+ * refreshed from the walk on every index run.
179
+ *
180
+ * `project` scope has to give up some of that. Every project on the machine
181
+ * shares one database and a relative entry path is only a date directory plus a
182
+ * timestamped filename, so two repos that journalled in the same second produced
183
+ * the same id and one silently replaced the other. The root is what distinguishes
184
+ * them. There is exactly one user root, so including it there would buy no
185
+ * discrimination and cost the move-stability property for nothing — hence the
186
+ * asymmetry rather than a blanket change.
187
+ */
188
+ export function journalEntryId(scope, root, entryPath) {
189
+ const relative = path.relative(root, entryPath).split(path.sep).join("/");
190
+ // The root joins the key for `project` scope only, and the asymmetry is the
191
+ // point.
192
+ //
193
+ // Every project on the machine shares ONE database (src/paths.ts), and a
194
+ // relative entry path is just a date directory and a timestamped filename. So
195
+ // for project entries the root is the only thing distinguishing two repos that
196
+ // journalled in the same second — without it they hash to one primary key and
197
+ // one silently replaces the other.
198
+ //
199
+ // There is exactly one user root, so for `user` scope the root adds no
200
+ // discrimination and including it would cost the property upstream built this
201
+ // function for: an id that survives the journal directory moving. That
202
+ // property is kept, and it is what the "stable across the root moving" test
203
+ // asserts.
204
+ const key = scope === "project" ? `${scope}:${path.resolve(root)}:${relative}` : `${scope}:${relative}`;
205
+ return crypto.createHash("md5").update(key).digest("hex");
206
+ }
207
+ /**
208
+ * Pick the excerpt to show for a hit: a sliding window scored by how many query
209
+ * words it contains. Carried over from private-journal-mcp unchanged.
210
+ */
211
+ export function generateExcerpt(text, query, maxLength = 200) {
212
+ if (!query || query.trim() === "") {
213
+ return text.slice(0, maxLength) + (text.length > maxLength ? "..." : "");
214
+ }
215
+ const queryWords = query.toLowerCase().split(/\s+/);
216
+ const textLower = text.toLowerCase();
217
+ let bestPosition = 0;
218
+ let bestScore = 0;
219
+ for (let i = 0; i <= text.length - maxLength; i += 20) {
220
+ const window = textLower.slice(i, i + maxLength);
221
+ const score = queryWords.reduce((sum, word) => sum + (window.includes(word) ? 1 : 0), 0);
222
+ if (score > bestScore) {
223
+ bestScore = score;
224
+ bestPosition = i;
225
+ }
226
+ }
227
+ let excerpt = text.slice(bestPosition, bestPosition + maxLength);
228
+ if (bestPosition > 0)
229
+ excerpt = `...${excerpt}`;
230
+ if (bestPosition + maxLength < text.length)
231
+ excerpt += "...";
232
+ return excerpt;
233
+ }
234
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/journal/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAA2C,MAAM,aAAa,CAAC;AAEhG,uCAAuC;AACvC,MAAM,CAAC,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAErD,MAAM,UAAU,kBAAkB,CAAC,IAAU;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAU;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,MAAM,CACzB,IAAI,CAAC,eAAe,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CACjE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnB,OAAO,GAAG,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,YAAY,EAAE,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAyB,EAAE,SAAe;IACpE,MAAM,WAAW,GAAG,SAAS,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACxD,MAAM,EAAE,IAAI;QACZ,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,SAAS,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACxD,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;KACf,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,wBAAwB,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,OAAO;UACC,WAAW,MAAM,WAAW;QAC9B,SAAS,CAAC,WAAW,EAAE;aAClB,SAAS,CAAC,OAAO,EAAE;;;EAG9B,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;CACtB,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,eAAuB;IAI3D,0BAA0B;IAC1B,MAAM,kBAAkB,GAAG,eAAe,CAAC,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;IAEzF,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,cAAc,GAAG,kBAAkB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC/D,IAAI,cAAc,EAAE,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,SAAS,GAAG,kBAAkB;SACjC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,yBAAyB;SAClD,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,uBAAuB;SAClD,IAAI,EAAE,CAAC;IAEV,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,oBAAoB,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,aAAuB,EAAE,SAAmB;IACxE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAChE,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,gBAAgB,EAAE,EAAE;QACzC,MAAM,MAAM,GAAG,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,eAAuB;IAC9D,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;IACjF,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACpE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC7D,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,wEAAwE;IACxE,iEAAiE;IACjE,wDAAwD;IACxD,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9C,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;IACvC,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAE3E,OAAO,IAAI,IAAI,CACb,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,EACzB,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,EAC9B,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,EACxB,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,EAC1B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,EAC5B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAC7B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAAC,KAAmB,EAAE,IAAY,EAAE,SAAiB;IACjF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1E,4EAA4E;IAC5E,SAAS;IACT,EAAE;IACF,yEAAyE;IACzE,8EAA8E;IAC9E,+EAA+E;IAC/E,8EAA8E;IAC9E,mCAAmC;IACnC,EAAE;IACF,uEAAuE;IACvE,8EAA8E;IAC9E,uEAAuE;IACvE,4EAA4E;IAC5E,WAAW;IACX,MAAM,GAAG,GACP,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC;IAC9F,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAa,EAAE,SAAS,GAAG,GAAG;IAC1E,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAErC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAEzF,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;YACtB,SAAS,GAAG,KAAK,CAAC;YAClB,YAAY,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC;IACjE,IAAI,YAAY,GAAG,CAAC;QAAE,OAAO,GAAG,MAAM,OAAO,EAAE,CAAC;IAChD,IAAI,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,KAAK,CAAC;IAE7D,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Journal retrieval.
3
+ *
4
+ * Reconciled from private-journal-mcp's `SearchService`. The storage half lost:
5
+ * its `search()` loaded every `.embedding` sidecar under both roots into memory
6
+ * on every query and cosine-scored them in JS — O(n) I/O and O(n) memory per
7
+ * call, with no cache. That is replaced by the same sqlite-vec KNN the
8
+ * conversation half uses, over `vec_journal_entries`.
9
+ *
10
+ * Two things were carried FORWARD unchanged in substance, because they are the
11
+ * best-engineered code in that source and the conversation half has no
12
+ * equivalent:
13
+ *
14
+ * - `readEntry`'s two-stage containment guard: resolve, require `.md`, require
15
+ * containment in the journal roots, realpath, then require containment in
16
+ * the REALPATH'd roots. The second stage blocks a symlink escape, and the
17
+ * two stages exist precisely because macOS `/tmp` and `/var` are symlinks —
18
+ * do not "simplify" it to one check.
19
+ * - `generateExcerpt`, in ./markdown.ts.
20
+ *
21
+ * `SearchResult` and `SearchOptions` were exported by BOTH upstream `search.ts`
22
+ * files with different shapes. The conversation names keep their meaning; these
23
+ * are `JournalSearchResult` and `JournalSearchOptions`, so the barrel can export
24
+ * all four.
25
+ */
26
+ import type Database from "better-sqlite3";
27
+ import { type EmbedFn } from "../embeddings.js";
28
+ import type { JournalScope, JournalSearchResult } from "../types.js";
29
+ /** `both` means "do not filter by scope", matching upstream's `type` parameter. */
30
+ export type JournalScopeFilter = JournalScope | "both";
31
+ export interface JournalSearchOptions {
32
+ limit?: number | undefined;
33
+ minScore?: number | undefined;
34
+ sections?: string[] | undefined;
35
+ dateRange?: {
36
+ start?: Date | undefined;
37
+ end?: Date | undefined;
38
+ } | undefined;
39
+ /** Which journal to search. Named `type` on the MCP wire; kept as `scope` here. */
40
+ scope?: JournalScopeFilter | undefined;
41
+ }
42
+ export interface JournalRecentOptions {
43
+ limit?: number | undefined;
44
+ scope?: JournalScopeFilter | undefined;
45
+ dateRange?: {
46
+ start?: Date | undefined;
47
+ end?: Date | undefined;
48
+ } | undefined;
49
+ }
50
+ export interface JournalEntryContent {
51
+ path: string;
52
+ content: string;
53
+ sections: string[];
54
+ timestamp: number;
55
+ scope: JournalScope;
56
+ }
57
+ export interface JournalSearchServiceOptions {
58
+ /**
59
+ * Injected query encoder. Defaults to `generateQueryEmbedding`, which prepends
60
+ * the BGE retrieval prefix — bge is asymmetric, and private-journal-mcp
61
+ * embedded its queries with the same call it used for documents, which costs
62
+ * recall silently. The seam exists so the CI-safe test project can exercise
63
+ * retrieval without a model download; production should never pass it.
64
+ */
65
+ embedQuery?: EmbedFn | undefined;
66
+ }
67
+ export declare class JournalSearchService {
68
+ private readonly db;
69
+ private readonly roots;
70
+ private readonly embedQuery;
71
+ /**
72
+ * @param db an open database from `initDatabase()`
73
+ * @param roots the journal roots, already de-duplicated (see `JournalStore.roots`)
74
+ */
75
+ constructor(db: Database.Database, roots: string[], options?: JournalSearchServiceOptions);
76
+ search(query: string, options?: JournalSearchOptions): Promise<JournalSearchResult[]>;
77
+ listRecent(options?: JournalRecentOptions): JournalSearchResult[];
78
+ readRecentEntries(options?: {
79
+ limit?: number | undefined;
80
+ scope?: JournalScopeFilter | undefined;
81
+ }): Promise<JournalEntryContent[]>;
82
+ /**
83
+ * Read one entry off disk.
84
+ *
85
+ * Carried over from private-journal-mcp with its guard intact. The error text
86
+ * is treated as API by three assertions, so it is preserved verbatim.
87
+ */
88
+ readEntry(filePath: string): Promise<string | null>;
89
+ private realRoots;
90
+ }
91
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/journal/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,KAAK,OAAO,EAA0B,MAAM,kBAAkB,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGrE,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EACN;QACE,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;QACzB,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;KACxB,GACD,SAAS,CAAC;IACd,mFAAmF;IACnF,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACvC,SAAS,CAAC,EACN;QACE,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;QACzB,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;KACxB,GACD,SAAS,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;CACrB;AA0CD,MAAM,WAAW,2BAA2B;IAC1C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED,qBAAa,oBAAoB;IAS7B,OAAO,CAAC,QAAQ,CAAC,EAAE;IARrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IAErC;;;OAGG;gBAEgB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACtC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,2BAAgC;IAMrC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA2C/F,UAAU,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,EAAE;IAsB/D,iBAAiB,CACrB,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;KAAO,GACnF,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAmBjC;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAsC3C,SAAS;CAWxB"}