@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,203 @@
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 fs from "node:fs/promises";
27
+ import path from "node:path";
28
+ import { JOURNAL_SELECT_COLUMNS, journalEntryFromRow } from "../db.js";
29
+ import { generateQueryEmbedding } from "../embeddings.js";
30
+ import { l2DistanceToCosineSimilarity } from "../search.js";
31
+ import { generateExcerpt, sectionsMatch } from "./markdown.js";
32
+ function buildFilters(options, roots) {
33
+ const parts = [];
34
+ const params = [];
35
+ // Confine retrieval to the roots this service was constructed with.
36
+ //
37
+ // The service has always been handed them and never used them for filtering,
38
+ // so a `scope: "project"` entry written in one repo was returned in every
39
+ // other repo on the machine — they all share one database. The roots are the
40
+ // current project's journal directory plus the shared user directory, so user
41
+ // entries, which deliberately follow the person between projects, still match.
42
+ if (roots.length > 0) {
43
+ parts.push(`j.root IN (${roots.map(() => "?").join(", ")})`);
44
+ params.push(...roots);
45
+ }
46
+ if (options.scope && options.scope !== "both") {
47
+ parts.push("j.scope = ?");
48
+ params.push(options.scope);
49
+ }
50
+ if (options.dateRange?.start) {
51
+ parts.push("j.timestamp >= ?");
52
+ params.push(options.dateRange.start.getTime());
53
+ }
54
+ if (options.dateRange?.end) {
55
+ parts.push("j.timestamp <= ?");
56
+ params.push(options.dateRange.end.getTime());
57
+ }
58
+ return { sql: parts.length ? `AND ${parts.join(" AND ")}` : "", params };
59
+ }
60
+ export class JournalSearchService {
61
+ db;
62
+ roots;
63
+ embedQuery;
64
+ /**
65
+ * @param db an open database from `initDatabase()`
66
+ * @param roots the journal roots, already de-duplicated (see `JournalStore.roots`)
67
+ */
68
+ constructor(db, roots, options = {}) {
69
+ this.db = db;
70
+ this.roots = roots.map((root) => path.resolve(root));
71
+ this.embedQuery = options.embedQuery ?? generateQueryEmbedding;
72
+ }
73
+ async search(query, options = {}) {
74
+ const limit = options.limit ?? 10;
75
+ const minScore = options.minScore ?? 0.1;
76
+ const sections = options.sections ?? [];
77
+ const queryEmbedding = await this.embedQuery(query);
78
+ const { sql: filterClause, params: filterParams } = buildFilters(options, this.roots);
79
+ // vec0 applies KNN before WHERE, so ask for more candidates than `limit`
80
+ // whenever a filter is active and trim afterwards — the same correction the
81
+ // conversation search makes for its metadata filters.
82
+ const overfetch = filterClause || sections.length > 0 ? 5 : 1;
83
+ const rows = this.db
84
+ .prepare(`
85
+ SELECT
86
+ ${JOURNAL_SELECT_COLUMNS},
87
+ vec.distance
88
+ FROM vec_journal_entries AS vec
89
+ JOIN journal_entries AS j ON vec.id = j.id
90
+ WHERE vec.embedding MATCH ?
91
+ AND k = ?
92
+ ${filterClause}
93
+ ORDER BY vec.distance ASC
94
+ `)
95
+ .all(Buffer.from(new Float32Array(queryEmbedding).buffer), limit * overfetch, ...filterParams);
96
+ const results = [];
97
+ for (const row of rows) {
98
+ const entry = journalEntryFromRow(row);
99
+ if (!sectionsMatch(entry.sections, sections))
100
+ continue;
101
+ const score = l2DistanceToCosineSimilarity(row.distance);
102
+ if (score < minScore)
103
+ continue;
104
+ results.push({ entry, score, excerpt: generateExcerpt(entry.text, query) });
105
+ if (results.length >= limit)
106
+ break;
107
+ }
108
+ return results;
109
+ }
110
+ listRecent(options = {}) {
111
+ const limit = options.limit ?? 10;
112
+ const { sql: filterClause, params: filterParams } = buildFilters(options, this.roots);
113
+ const rows = this.db
114
+ .prepare(`
115
+ SELECT ${JOURNAL_SELECT_COLUMNS}
116
+ FROM journal_entries AS j
117
+ WHERE 1 = 1
118
+ ${filterClause}
119
+ ORDER BY j.timestamp DESC
120
+ LIMIT ?
121
+ `)
122
+ .all(...filterParams, limit);
123
+ return rows.map((row) => {
124
+ const entry = journalEntryFromRow(row);
125
+ // No similarity score for a chronological listing — upstream reported 1.
126
+ return { entry, score: 1, excerpt: generateExcerpt(entry.text, "", 150) };
127
+ });
128
+ }
129
+ async readRecentEntries(options = {}) {
130
+ const recent = this.listRecent({ limit: options.limit ?? 5, scope: options.scope });
131
+ const results = [];
132
+ for (const hit of recent) {
133
+ const content = await this.readEntry(hit.entry.path);
134
+ if (content !== null) {
135
+ results.push({
136
+ path: hit.entry.path,
137
+ content,
138
+ sections: hit.entry.sections,
139
+ timestamp: hit.entry.timestamp,
140
+ scope: hit.entry.scope,
141
+ });
142
+ }
143
+ }
144
+ return results;
145
+ }
146
+ /**
147
+ * Read one entry off disk.
148
+ *
149
+ * Carried over from private-journal-mcp with its guard intact. The error text
150
+ * is treated as API by three assertions, so it is preserved verbatim.
151
+ */
152
+ async readEntry(filePath) {
153
+ const resolvedPath = path.resolve(filePath);
154
+ // Only Markdown entries under the journal directories are readable; reject
155
+ // anything else with a clear error.
156
+ if (path.extname(resolvedPath).toLowerCase() !== ".md") {
157
+ throw new Error(`${resolvedPath} is not a readable journal entry`);
158
+ }
159
+ if (!isUnderRoot(resolvedPath, this.roots)) {
160
+ throw new Error(`${resolvedPath} is not a readable journal entry`);
161
+ }
162
+ let realPath;
163
+ try {
164
+ realPath = await fs.realpath(resolvedPath);
165
+ }
166
+ catch (error) {
167
+ if (error?.code === "ENOENT") {
168
+ return null;
169
+ }
170
+ throw error;
171
+ }
172
+ // Resolve symlinks and confirm the real file is still under a journal
173
+ // directory before reading.
174
+ if (!isUnderRoot(realPath, await this.realRoots())) {
175
+ throw new Error(`${resolvedPath} is not a readable journal entry`);
176
+ }
177
+ try {
178
+ return await fs.readFile(realPath, "utf8");
179
+ }
180
+ catch (error) {
181
+ if (error?.code === "ENOENT") {
182
+ return null;
183
+ }
184
+ throw error;
185
+ }
186
+ }
187
+ async realRoots() {
188
+ const roots = [];
189
+ for (const root of this.roots) {
190
+ try {
191
+ roots.push(await fs.realpath(root));
192
+ }
193
+ catch {
194
+ // A journal root that doesn't exist on disk can't contain anything.
195
+ }
196
+ }
197
+ return roots;
198
+ }
199
+ }
200
+ function isUnderRoot(candidate, roots) {
201
+ return roots.some((root) => candidate === root || candidate.startsWith(root + path.sep));
202
+ }
203
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/journal/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The journal write path and its index.
3
+ *
4
+ * Reconciled from private-journal-mcp's `JournalManager`. The markdown-writing
5
+ * half is carried over essentially unchanged — it is a data contract with files
6
+ * already on disk. The persistence half changed completely: entries are indexed
7
+ * into the shared SQLite store instead of a `.embedding` JSON sidecar per file.
8
+ *
9
+ * That swap fixes a real defect. Upstream, `generateEmbeddingForEntry` caught and
10
+ * logged every embedding error so "embedding failure shouldn't prevent journal
11
+ * writing" — but the sidecar index was the ONLY enumeration path in the package,
12
+ * so a failed encode wrote an entry that no read path could ever see again, with
13
+ * no doctor or verify command to notice. Here the markdown files are the source
14
+ * of truth: `indexJournal()` walks them, and anything missing from the index or
15
+ * behind the current EMBEDDING_VERSION is picked up on the next run.
16
+ */
17
+ import type Database from "better-sqlite3";
18
+ import { type EmbedFn } from "../embeddings.js";
19
+ import type { JournalScope, JournalThoughts } from "../types.js";
20
+ export interface JournalStoreOptions {
21
+ projectPath?: string | undefined;
22
+ userPath?: string | undefined;
23
+ /**
24
+ * Injected encoder. Defaults to the real bge pipeline; the offline test
25
+ * project passes a deterministic stub so the write path and the index can be
26
+ * exercised without a 35 MB model download.
27
+ */
28
+ embed?: EmbedFn | undefined;
29
+ }
30
+ export interface JournalIndexResult {
31
+ indexed: number;
32
+ pruned: number;
33
+ failed: number;
34
+ total: number;
35
+ }
36
+ interface JournalRoot {
37
+ scope: JournalScope;
38
+ path: string;
39
+ }
40
+ export declare class JournalStore {
41
+ private readonly projectPath;
42
+ private readonly userPath;
43
+ private readonly embed;
44
+ constructor(options?: JournalStoreOptions);
45
+ /**
46
+ * The roots to walk, de-duplicated.
47
+ *
48
+ * When `MOE_MEMORY_JOURNAL_PATH` is set both resolve to the same directory.
49
+ * Upstream loaded such a directory twice — once labelled `project`, once
50
+ * `user` — so every entry appeared twice with contradictory labels and
51
+ * `limit: 10` yielded 5 unique entries. That is the documented containerised
52
+ * configuration, i.e. the one most likely to be used in infra.
53
+ */
54
+ roots(): JournalRoot[];
55
+ private get collapsed();
56
+ /**
57
+ * Which journal an entry belongs to.
58
+ *
59
+ * Normally the directory decides. When the roots are collapsed there is no
60
+ * directory to decide, so the entry's own sections do: `writeThoughts` routes
61
+ * `project_notes` to the project journal and nothing else, so the presence of
62
+ * a `## Project Notes` heading is a faithful discriminator either way.
63
+ */
64
+ private scopeFor;
65
+ /**
66
+ * Write one set of thoughts. `project_notes` lands in the project journal, the
67
+ * other five in the user journal, and each side is skipped when empty — so a
68
+ * `process_thoughts` call carrying only reflections creates no project
69
+ * directory at all.
70
+ *
71
+ * `db` is optional: pass it to index the new entries immediately (what the MCP
72
+ * server does), omit it to write markdown only.
73
+ */
74
+ writeThoughts(thoughts: JournalThoughts, db?: Database.Database): Promise<string[]>;
75
+ private writeToLocation;
76
+ private ensureDirectoryExists;
77
+ /**
78
+ * Index one markdown file by absolute path. Used straight after a write.
79
+ */
80
+ private indexOne;
81
+ private indexContent;
82
+ /**
83
+ * Walk both journal roots and bring the index up to date.
84
+ *
85
+ * Replaces private-journal-mcp's `generateMissingEmbeddings()`, which keyed
86
+ * purely on the absence of a `.embedding` sidecar. This one re-indexes when
87
+ * the file's mtime moved or its row is behind EMBEDDING_VERSION, and prunes
88
+ * rows whose file is gone — so an edited entry, a bumped encoder and a deleted
89
+ * file are all handled.
90
+ */
91
+ indexJournal(db: Database.Database): Promise<JournalIndexResult>;
92
+ /** Count indexed entries, optionally for one scope. */
93
+ count(db: Database.Database, scope?: JournalScope): number;
94
+ }
95
+ export {};
96
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/journal/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAQ3C,OAAO,EAAE,KAAK,OAAO,EAA0B,MAAM,kBAAkB,CAAC;AAExE,OAAO,KAAK,EAAgB,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY/E,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,WAAW;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AA4BD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;gBAEpB,OAAO,GAAE,mBAAwB;IAM7C;;;;;;;;OAQG;IACH,KAAK,IAAI,WAAW,EAAE;IAUtB,OAAO,KAAK,SAAS,GAEpB;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ;IAQhB;;;;;;;;OAQG;IACG,aAAa,CAAC,QAAQ,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YA+B3E,eAAe;YAaf,qBAAqB;IAgBnC;;OAEG;YACW,QAAQ;YAYR,YAAY;IA8B1B;;;;;;;;OAQG;IACG,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuFtE,uDAAuD;IACvD,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM;CAG3D"}
@@ -0,0 +1,270 @@
1
+ /**
2
+ * The journal write path and its index.
3
+ *
4
+ * Reconciled from private-journal-mcp's `JournalManager`. The markdown-writing
5
+ * half is carried over essentially unchanged — it is a data contract with files
6
+ * already on disk. The persistence half changed completely: entries are indexed
7
+ * into the shared SQLite store instead of a `.embedding` JSON sidecar per file.
8
+ *
9
+ * That swap fixes a real defect. Upstream, `generateEmbeddingForEntry` caught and
10
+ * logged every embedding error so "embedding failure shouldn't prevent journal
11
+ * writing" — but the sidecar index was the ONLY enumeration path in the package,
12
+ * so a failed encode wrote an entry that no read path could ever see again, with
13
+ * no doctor or verify command to notice. Here the markdown files are the source
14
+ * of truth: `indexJournal()` walks them, and anything missing from the index or
15
+ * behind the current EMBEDDING_VERSION is picked up on the next run.
16
+ */
17
+ import fs from "node:fs/promises";
18
+ import path from "node:path";
19
+ import { countJournalEntries, deleteJournalEntry, getJournalIndexState, upsertJournalEntry, } from "../db.js";
20
+ import { EMBEDDING_VERSION } from "../embedding-migration.js";
21
+ import { generateEntryEmbedding } from "../embeddings.js";
22
+ import { resolveProjectJournalPath, resolveUserJournalPath } from "../paths.js";
23
+ import { DAY_DIR_PATTERN, extractSearchableText, formatDayDirectory, formatEntry, formatEntryBasename, journalEntryId, timestampFromEntryPath, timestampFromFrontmatter, } from "./markdown.js";
24
+ /**
25
+ * `project_notes` is the only category that belongs to the codebase. The other
26
+ * five belong to the person, and follow them between projects.
27
+ */
28
+ const PROJECT_KEYS = ["project_notes"];
29
+ const USER_KEYS = [
30
+ "reflections",
31
+ "observations",
32
+ "user_context",
33
+ "technical_insights",
34
+ "world_knowledge",
35
+ ];
36
+ function pick(thoughts, keys) {
37
+ const out = {};
38
+ for (const key of keys) {
39
+ const value = thoughts[key];
40
+ if (value !== undefined)
41
+ out[key] = value;
42
+ }
43
+ return out;
44
+ }
45
+ function hasContent(thoughts) {
46
+ return Object.values(thoughts).some((value) => value !== undefined && value !== "");
47
+ }
48
+ export class JournalStore {
49
+ projectPath;
50
+ userPath;
51
+ embed;
52
+ constructor(options = {}) {
53
+ this.projectPath = path.resolve(options.projectPath ?? resolveProjectJournalPath());
54
+ this.userPath = path.resolve(options.userPath ?? resolveUserJournalPath());
55
+ this.embed = options.embed ?? generateEntryEmbedding;
56
+ }
57
+ /**
58
+ * The roots to walk, de-duplicated.
59
+ *
60
+ * When `MOE_MEMORY_JOURNAL_PATH` is set both resolve to the same directory.
61
+ * Upstream loaded such a directory twice — once labelled `project`, once
62
+ * `user` — so every entry appeared twice with contradictory labels and
63
+ * `limit: 10` yielded 5 unique entries. That is the documented containerised
64
+ * configuration, i.e. the one most likely to be used in infra.
65
+ */
66
+ roots() {
67
+ if (this.projectPath === this.userPath) {
68
+ return [{ scope: "user", path: this.userPath }];
69
+ }
70
+ return [
71
+ { scope: "project", path: this.projectPath },
72
+ { scope: "user", path: this.userPath },
73
+ ];
74
+ }
75
+ get collapsed() {
76
+ return this.projectPath === this.userPath;
77
+ }
78
+ /**
79
+ * Which journal an entry belongs to.
80
+ *
81
+ * Normally the directory decides. When the roots are collapsed there is no
82
+ * directory to decide, so the entry's own sections do: `writeThoughts` routes
83
+ * `project_notes` to the project journal and nothing else, so the presence of
84
+ * a `## Project Notes` heading is a faithful discriminator either way.
85
+ */
86
+ scopeFor(root, sections) {
87
+ if (!this.collapsed)
88
+ return root.scope;
89
+ const isProject = sections.some((section) => section.toLowerCase().replace(/[^a-z0-9]/g, "") === "projectnotes");
90
+ return isProject ? "project" : "user";
91
+ }
92
+ /**
93
+ * Write one set of thoughts. `project_notes` lands in the project journal, the
94
+ * other five in the user journal, and each side is skipped when empty — so a
95
+ * `process_thoughts` call carrying only reflections creates no project
96
+ * directory at all.
97
+ *
98
+ * `db` is optional: pass it to index the new entries immediately (what the MCP
99
+ * server does), omit it to write markdown only.
100
+ */
101
+ async writeThoughts(thoughts, db) {
102
+ const timestamp = new Date();
103
+ const written = [];
104
+ const projectThoughts = pick(thoughts, PROJECT_KEYS);
105
+ if (hasContent(projectThoughts)) {
106
+ written.push(await this.writeToLocation(projectThoughts, timestamp, this.projectPath));
107
+ }
108
+ const userThoughts = pick(thoughts, USER_KEYS);
109
+ if (hasContent(userThoughts)) {
110
+ written.push(await this.writeToLocation(userThoughts, timestamp, this.userPath));
111
+ }
112
+ if (db) {
113
+ for (const entryPath of written) {
114
+ await this.indexOne(db, entryPath).catch((error) => {
115
+ // Not fatal, and no longer invisible: the markdown file is the source
116
+ // of truth and indexJournal() will retry this entry on the next run.
117
+ console.error(`moe-memory: journal entry written but not indexed (${entryPath}): ${error instanceof Error ? error.message : String(error)}`);
118
+ });
119
+ }
120
+ }
121
+ return written;
122
+ }
123
+ async writeToLocation(thoughts, timestamp, basePath) {
124
+ const dayDirectory = path.join(basePath, formatDayDirectory(timestamp));
125
+ const filePath = path.join(dayDirectory, `${formatEntryBasename(timestamp)}.md`);
126
+ await this.ensureDirectoryExists(dayDirectory);
127
+ await fs.writeFile(filePath, formatEntry(thoughts, timestamp), "utf8");
128
+ return filePath;
129
+ }
130
+ async ensureDirectoryExists(dirPath) {
131
+ try {
132
+ await fs.access(dirPath);
133
+ }
134
+ catch {
135
+ try {
136
+ await fs.mkdir(dirPath, { recursive: true });
137
+ }
138
+ catch (mkdirError) {
139
+ throw new Error(`Failed to create journal directory at ${dirPath}: ${mkdirError instanceof Error ? mkdirError.message : String(mkdirError)}`);
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * Index one markdown file by absolute path. Used straight after a write.
145
+ */
146
+ async indexOne(db, entryPath) {
147
+ const root = this.roots().find((candidate) => entryPath === candidate.path || entryPath.startsWith(candidate.path + path.sep));
148
+ if (!root)
149
+ throw new Error(`${entryPath} is not under a journal root`);
150
+ const content = await fs.readFile(entryPath, "utf8");
151
+ const stat = await fs.stat(entryPath);
152
+ await this.indexContent(db, root, entryPath, content, stat.mtimeMs);
153
+ }
154
+ async indexContent(db, root, entryPath, content, mtimeMs) {
155
+ const { text, sections } = extractSearchableText(content);
156
+ if (text.trim().length === 0)
157
+ return; // Nothing to embed
158
+ const scope = this.scopeFor(root, sections);
159
+ // Frontmatter first (epoch ms), then the filename (second resolution), then
160
+ // the file's mtime. See timestampFromFrontmatter for why the order matters.
161
+ const timestamp = timestampFromFrontmatter(content) ?? timestampFromEntryPath(entryPath)?.getTime() ?? mtimeMs;
162
+ const entry = {
163
+ id: journalEntryId(scope, root.path, entryPath),
164
+ path: entryPath,
165
+ root: path.resolve(root.path),
166
+ scope,
167
+ timestamp,
168
+ text,
169
+ sections,
170
+ };
171
+ const embedding = await this.embed(text);
172
+ upsertJournalEntry(db, entry, mtimeMs, embedding);
173
+ }
174
+ /**
175
+ * Walk both journal roots and bring the index up to date.
176
+ *
177
+ * Replaces private-journal-mcp's `generateMissingEmbeddings()`, which keyed
178
+ * purely on the absence of a `.embedding` sidecar. This one re-indexes when
179
+ * the file's mtime moved or its row is behind EMBEDDING_VERSION, and prunes
180
+ * rows whose file is gone — so an edited entry, a bumped encoder and a deleted
181
+ * file are all handled.
182
+ */
183
+ async indexJournal(db) {
184
+ const result = { indexed: 0, pruned: 0, failed: 0, total: 0 };
185
+ const state = getJournalIndexState(db);
186
+ const seen = new Set();
187
+ for (const root of this.roots()) {
188
+ let dayDirs;
189
+ try {
190
+ dayDirs = await fs.readdir(root.path);
191
+ }
192
+ catch (error) {
193
+ if (error?.code !== "ENOENT") {
194
+ console.error(`moe-memory: failed to scan journal root ${root.path}: ${String(error)}`);
195
+ }
196
+ continue;
197
+ }
198
+ for (const dayDir of dayDirs) {
199
+ if (!DAY_DIR_PATTERN.test(dayDir))
200
+ continue;
201
+ const dayPath = path.join(root.path, dayDir);
202
+ let files;
203
+ try {
204
+ const stat = await fs.stat(dayPath);
205
+ if (!stat.isDirectory())
206
+ continue;
207
+ files = await fs.readdir(dayPath);
208
+ }
209
+ catch {
210
+ continue;
211
+ }
212
+ for (const file of files) {
213
+ if (!file.endsWith(".md"))
214
+ continue;
215
+ const entryPath = path.join(dayPath, file);
216
+ result.total++;
217
+ try {
218
+ const content = await fs.readFile(entryPath, "utf8");
219
+ const stat = await fs.stat(entryPath);
220
+ const { sections } = extractSearchableText(content);
221
+ const scope = this.scopeFor(root, sections);
222
+ const id = journalEntryId(scope, root.path, entryPath);
223
+ seen.add(id);
224
+ const existing = state.get(id);
225
+ const fresh = existing !== undefined &&
226
+ existing.path === entryPath &&
227
+ existing.sourceMtimeMs === stat.mtimeMs &&
228
+ existing.embeddingVersion === EMBEDDING_VERSION;
229
+ if (fresh)
230
+ continue;
231
+ await this.indexContent(db, root, entryPath, content, stat.mtimeMs);
232
+ result.indexed++;
233
+ }
234
+ catch (error) {
235
+ result.failed++;
236
+ console.error(`moe-memory: failed to index journal entry ${entryPath}: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ }
239
+ }
240
+ }
241
+ // Prune only what this walk was actually responsible for.
242
+ //
243
+ // One database holds every project's entries, and `seen` can only ever
244
+ // contain ids from the roots above. Deleting everything absent from it
245
+ // therefore deleted other repos' project notes — permanently, on every
246
+ // server start. A row is a candidate for pruning only if its root is one of
247
+ // the roots just walked; anything else belongs to a project that is not this
248
+ // one, and its own next index is what will prune it.
249
+ //
250
+ // Rows written before `journal_entries.root` existed carry `''` and are
251
+ // never pruned here. migrateJournalRoot clears them once, so the case is
252
+ // transitional; treating an unknown root as "not mine" is the safe default
253
+ // regardless.
254
+ const walked = new Set(this.roots().map((root) => path.resolve(root.path)));
255
+ for (const [id, row] of state) {
256
+ if (seen.has(id))
257
+ continue;
258
+ if (!row.root || !walked.has(row.root))
259
+ continue;
260
+ deleteJournalEntry(db, id);
261
+ result.pruned++;
262
+ }
263
+ return result;
264
+ }
265
+ /** Count indexed entries, optionally for one scope. */
266
+ count(db, scope) {
267
+ return countJournalEntries(db, scope);
268
+ }
269
+ }
270
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/journal/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `moe-memory journal <subcommand>` — the journal half from the terminal.
3
+ *
4
+ * private-journal-mcp had no CLI beyond `--journal-path`: its single bin was the
5
+ * MCP server and nothing else. These subcommands are new surface, and they exist
6
+ * because the journal is no longer self-indexing through sidecars — if the index
7
+ * can be rebuilt, something has to be able to rebuild it.
8
+ */
9
+ import type { JournalScopeFilter } from "./journal/search.js";
10
+ export interface JournalArgs {
11
+ /** Positional arguments joined — the search query. */
12
+ query: string;
13
+ limit: number;
14
+ scope: JournalScopeFilter;
15
+ journalPath: string | undefined;
16
+ }
17
+ export declare function parseJournalArgs(args: string[]): JournalArgs;
18
+ export declare function runJournal(args: string[]): Promise<number>;
19
+ //# sourceMappingURL=journal-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal-cli.d.ts","sourceRoot":"","sources":["../src/journal-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA6C9D,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,kBAAkB,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,CAiC5D;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwFhE"}