recallnest 2.3.0 → 2.6.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 (258) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/README.md +49 -38
  3. package/README_CN.md +46 -31
  4. package/assets/ui/app.js +1031 -0
  5. package/assets/ui/index.html +224 -0
  6. package/assets/ui/styles.css +781 -0
  7. package/config.json.example +4 -1
  8. package/docs/api-reference.md +1291 -0
  9. package/docs/architecture.md +168 -0
  10. package/docs/connector-spec.md +126 -0
  11. package/eval/cases-canary.json +58 -0
  12. package/eval/cases-scope-robustness.json +278 -0
  13. package/eval/cases.json +117 -20
  14. package/package.json +25 -4
  15. package/src/__tests__/access-tracker-novelty.test.ts +304 -0
  16. package/src/__tests__/activity-counter.test.ts +125 -0
  17. package/src/__tests__/adaptive-fidelity.test.ts +102 -0
  18. package/src/__tests__/admission-control.test.ts +206 -0
  19. package/src/__tests__/anchor-generator.test.ts +67 -0
  20. package/src/__tests__/api-server-security.test.ts +102 -0
  21. package/src/__tests__/auto-consolidation.test.ts +168 -0
  22. package/src/__tests__/auto-recall.test.ts +131 -0
  23. package/src/__tests__/babel-integration.test.ts +144 -0
  24. package/src/__tests__/batch-dedup.test.ts +65 -0
  25. package/src/__tests__/belief-history.test.ts +220 -0
  26. package/src/__tests__/canary-eval.test.ts +114 -0
  27. package/src/__tests__/capture-engine.test.ts +1328 -0
  28. package/src/__tests__/capture-heuristic.test.ts +171 -0
  29. package/src/__tests__/cascade-forget.test.ts +210 -0
  30. package/src/__tests__/category-min-scores.test.ts +116 -0
  31. package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
  32. package/src/__tests__/circuit-breaker.test.ts +99 -0
  33. package/src/__tests__/cluster-consolidation.test.ts +945 -0
  34. package/src/__tests__/confidence-metadata.test.ts +125 -0
  35. package/src/__tests__/confidence-tracker.test.ts +261 -0
  36. package/src/__tests__/conflict-advisor.test.ts +206 -0
  37. package/src/__tests__/conflict-escalation.test.ts +149 -0
  38. package/src/__tests__/conflict-lifecycle.test.ts +83 -0
  39. package/src/__tests__/conflict-output.test.ts +85 -0
  40. package/src/__tests__/conflict-store.test.ts +418 -0
  41. package/src/__tests__/connector-v1.test.ts +181 -0
  42. package/src/__tests__/consolidation-engine.test.ts +430 -0
  43. package/src/__tests__/constructive-retrieval.test.ts +509 -0
  44. package/src/__tests__/context-collapse-renderer.test.ts +214 -0
  45. package/src/__tests__/context-composer-pins.test.ts +64 -0
  46. package/src/__tests__/context-composer-task-selection.test.ts +24 -0
  47. package/src/__tests__/context-reconstructor.test.ts +65 -0
  48. package/src/__tests__/context-renderer.test.ts +91 -0
  49. package/src/__tests__/continuity-eval.test.ts +275 -0
  50. package/src/__tests__/continuity-schema.test.ts +97 -0
  51. package/src/__tests__/contracts.test.ts +262 -0
  52. package/src/__tests__/conversation-importer.test.ts +692 -0
  53. package/src/__tests__/core-recall-eval.test.ts +235 -0
  54. package/src/__tests__/core-summary.test.ts +109 -0
  55. package/src/__tests__/data-checkup.test.ts +276 -0
  56. package/src/__tests__/decay-exemption.test.ts +129 -0
  57. package/src/__tests__/dedup-actions.test.ts +58 -0
  58. package/src/__tests__/dedup-guard.test.ts +39 -0
  59. package/src/__tests__/deterministic-id.test.ts +39 -0
  60. package/src/__tests__/distill-lock.test.ts +305 -0
  61. package/src/__tests__/distill-session-argshape.test.ts +79 -0
  62. package/src/__tests__/doctor.test.ts +319 -0
  63. package/src/__tests__/dream-pipeline.test.ts +780 -0
  64. package/src/__tests__/drill-down.test.ts +108 -0
  65. package/src/__tests__/embedder.test.ts +122 -0
  66. package/src/__tests__/emotion-decay.test.ts +46 -0
  67. package/src/__tests__/emotion-detector.test.ts +56 -0
  68. package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
  69. package/src/__tests__/emotion-retrieval.test.ts +56 -0
  70. package/src/__tests__/entity-resolver.test.ts +58 -0
  71. package/src/__tests__/env-config.test.ts +151 -0
  72. package/src/__tests__/error-signature-boost.test.ts +87 -0
  73. package/src/__tests__/error-signature.test.ts +95 -0
  74. package/src/__tests__/error-taxonomy.test.ts +129 -0
  75. package/src/__tests__/ethics-layer.test.ts +476 -0
  76. package/src/__tests__/event-segmenter.test.ts +153 -0
  77. package/src/__tests__/feature-flags-integration.test.ts +54 -0
  78. package/src/__tests__/frequency-tracker.test.ts +113 -0
  79. package/src/__tests__/freshness.test.ts +186 -0
  80. package/src/__tests__/graph-export.test.ts +489 -0
  81. package/src/__tests__/hotness-blend.test.ts +283 -0
  82. package/src/__tests__/ingest-chunking.test.ts +44 -0
  83. package/src/__tests__/ingest.test.ts +360 -0
  84. package/src/__tests__/interference-detector.test.ts +203 -0
  85. package/src/__tests__/kg-extractor.test.ts +192 -0
  86. package/src/__tests__/kg-store.test.ts +300 -0
  87. package/src/__tests__/llm-client-pivot.test.ts +628 -0
  88. package/src/__tests__/llm-consolidation.test.ts +226 -0
  89. package/src/__tests__/maintenance-locks.test.ts +189 -0
  90. package/src/__tests__/mcp-efficiency.test.ts +288 -0
  91. package/src/__tests__/mcp-related-scopes.test.ts +151 -0
  92. package/src/__tests__/memory-boundaries.test.ts +282 -0
  93. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
  94. package/src/__tests__/memory-evolution.test.ts +271 -0
  95. package/src/__tests__/memory-health-rebalance.test.ts +144 -0
  96. package/src/__tests__/memory-lint.test.ts +421 -0
  97. package/src/__tests__/memory-output.test.ts +215 -0
  98. package/src/__tests__/memory-promotion.test.ts +315 -0
  99. package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
  100. package/src/__tests__/multi-vector.test.ts +242 -0
  101. package/src/__tests__/narrative-architecture.test.ts +456 -0
  102. package/src/__tests__/noise-filter.test.ts +205 -0
  103. package/src/__tests__/noise-prototype-bank.test.ts +280 -0
  104. package/src/__tests__/obsidian-connector.test.ts +363 -0
  105. package/src/__tests__/omitted-reporting.test.ts +98 -0
  106. package/src/__tests__/pending-review.test.ts +77 -0
  107. package/src/__tests__/pii-redact.test.ts +333 -0
  108. package/src/__tests__/pivot-apply.test.ts +373 -0
  109. package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
  110. package/src/__tests__/pivot-distill.test.ts +1178 -0
  111. package/src/__tests__/post-retrieval-filter.test.ts +178 -0
  112. package/src/__tests__/ppr-traversal.test.ts +256 -0
  113. package/src/__tests__/prediction-engine.test.ts +349 -0
  114. package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
  115. package/src/__tests__/preference-guard-smoke.test.ts +211 -0
  116. package/src/__tests__/preference-matcher.test.ts +282 -0
  117. package/src/__tests__/preference-slots.test.ts +205 -0
  118. package/src/__tests__/prospective-memory.test.ts +283 -0
  119. package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
  120. package/src/__tests__/query-entity-detector.test.ts +171 -0
  121. package/src/__tests__/query-expander-alias.test.ts +146 -0
  122. package/src/__tests__/query-expander.test.ts +34 -0
  123. package/src/__tests__/recall-gate.test.ts +266 -0
  124. package/src/__tests__/recall-governor.test.ts +227 -0
  125. package/src/__tests__/recall-mode.test.ts +64 -0
  126. package/src/__tests__/resolve-db-path.test.ts +130 -0
  127. package/src/__tests__/resolve-tier.test.ts +64 -0
  128. package/src/__tests__/result-synthesizer.test.ts +198 -0
  129. package/src/__tests__/resume-context.test.ts +4898 -0
  130. package/src/__tests__/retrieval-eval.test.ts +163 -0
  131. package/src/__tests__/retrieval-trace.test.ts +213 -0
  132. package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
  133. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
  134. package/src/__tests__/retroactive-boost.test.ts +223 -0
  135. package/src/__tests__/salience-scoring.test.ts +106 -0
  136. package/src/__tests__/scope-inventory.test.ts +200 -0
  137. package/src/__tests__/scope-policy.test.ts +69 -0
  138. package/src/__tests__/scope-suggester.test.ts +80 -0
  139. package/src/__tests__/session-checkpoint.test.ts +291 -0
  140. package/src/__tests__/session-distiller.test.ts +640 -0
  141. package/src/__tests__/session-gc.test.ts +215 -0
  142. package/src/__tests__/skill-engine.test.ts +552 -0
  143. package/src/__tests__/skill-promotion.test.ts +654 -0
  144. package/src/__tests__/skill-verifier.test.ts +122 -0
  145. package/src/__tests__/source-heartbeat.test.ts +250 -0
  146. package/src/__tests__/stable-context-length-contract.test.ts +62 -0
  147. package/src/__tests__/store-patch-metadata.test.ts +205 -0
  148. package/src/__tests__/store-read-consistency.test.ts +122 -0
  149. package/src/__tests__/store-schema-migration.test.ts +87 -0
  150. package/src/__tests__/store-scope-audit.test.ts +81 -0
  151. package/src/__tests__/store-upsert.test.ts +201 -0
  152. package/src/__tests__/supersede-chain.test.ts +154 -0
  153. package/src/__tests__/temporal-parser.test.ts +241 -0
  154. package/src/__tests__/temporal-validity.test.ts +160 -0
  155. package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
  156. package/src/__tests__/term-registry.test.ts +121 -0
  157. package/src/__tests__/tool-error-surfacing.test.ts +98 -0
  158. package/src/__tests__/tool-output-compressor.test.ts +160 -0
  159. package/src/__tests__/topic-tag.test.ts +113 -0
  160. package/src/__tests__/trusted-memory.test.ts +839 -0
  161. package/src/__tests__/ui-server-security.test.ts +93 -0
  162. package/src/__tests__/usage-tracker.test.ts +287 -0
  163. package/src/__tests__/version-manager.test.ts +213 -0
  164. package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
  165. package/src/__tests__/workflow-observation.test.ts +433 -0
  166. package/src/__tests__/write-verifier.test.ts +122 -0
  167. package/src/access-tracker.ts +90 -34
  168. package/src/activity-counter.ts +46 -18
  169. package/src/adaptive-retrieval.ts +11 -0
  170. package/src/aliases.ts +133 -0
  171. package/src/api-server.ts +36 -9
  172. package/src/audit-log.ts +4 -2
  173. package/src/auto-consolidation.ts +8 -3
  174. package/src/auto-gc.ts +85 -21
  175. package/src/auto-recall.ts +61 -1
  176. package/src/belief-history.ts +164 -0
  177. package/src/capture-engine.ts +142 -32
  178. package/src/cli.ts +268 -41
  179. package/src/confidence-tracker.ts +36 -18
  180. package/src/conflict-engine.ts +24 -3
  181. package/src/consolidation-engine.ts +255 -23
  182. package/src/context-composer-stable-selection.ts +7 -1
  183. package/src/context-composer.ts +8 -1
  184. package/src/data-checkup.ts +96 -7
  185. package/src/decay-engine.ts +59 -7
  186. package/src/distill-lock.ts +194 -15
  187. package/src/doctor.ts +13 -4
  188. package/src/dream-pipeline.ts +473 -41
  189. package/src/env-config.ts +118 -0
  190. package/src/error-signature.ts +153 -0
  191. package/src/error-taxonomy.ts +115 -0
  192. package/src/eval.ts +402 -14
  193. package/src/freshness.ts +192 -0
  194. package/src/http-request-policy.ts +148 -0
  195. package/src/idempotency.ts +8 -0
  196. package/src/ingest.ts +308 -12
  197. package/src/kg-extractor.ts +13 -3
  198. package/src/kg-store.ts +236 -44
  199. package/src/llm-client.ts +106 -11
  200. package/src/llm-consolidation.ts +2 -1
  201. package/src/mcp-server.ts +87 -1800
  202. package/src/mcp-tool-deps.ts +48 -0
  203. package/src/mcp-tools-advanced.ts +954 -0
  204. package/src/mcp-tools-core.ts +637 -0
  205. package/src/mcp-tools-governance.ts +475 -0
  206. package/src/memory-boundaries.ts +26 -3
  207. package/src/memory-evolution.ts +73 -1
  208. package/src/memory-health-rebalance.ts +4 -0
  209. package/src/memory-lint.ts +183 -10
  210. package/src/memory-output.ts +129 -4
  211. package/src/memory-promotion.ts +246 -0
  212. package/src/memory-schema.ts +25 -2
  213. package/src/memory-store-port.ts +46 -0
  214. package/src/multi-vector.ts +2 -1
  215. package/src/narrative-schema.ts +2 -1
  216. package/src/noise-filter.ts +57 -6
  217. package/src/noise-prototype-bank.ts +408 -0
  218. package/src/pii-detector.ts +207 -5
  219. package/src/pivot-apply.ts +399 -0
  220. package/src/preference-matcher.ts +37 -3
  221. package/src/query-expander.ts +90 -9
  222. package/src/recall-gate.ts +305 -0
  223. package/src/result-synthesizer.ts +2 -1
  224. package/src/retention-policy.ts +2 -1
  225. package/src/retrieval-profiles.ts +46 -4
  226. package/src/retriever.ts +354 -157
  227. package/src/retroactive-boost.ts +6 -2
  228. package/src/runtime-config.ts +78 -6
  229. package/src/scope-suggester.ts +113 -0
  230. package/src/session-distiller.ts +23 -2
  231. package/src/session-engine.ts +2 -1
  232. package/src/session-schema.ts +1 -0
  233. package/src/session-store.ts +5 -0
  234. package/src/skill-engine.ts +78 -0
  235. package/src/skill-promotion.ts +304 -48
  236. package/src/skill-schema.ts +16 -3
  237. package/src/skill-verifier.ts +152 -0
  238. package/src/store.ts +321 -57
  239. package/src/term-registry.ts +35 -0
  240. package/src/tool-output-compressor.ts +3 -1
  241. package/src/ui-server.ts +9 -1
  242. package/src/usage-tracker.ts +242 -0
  243. package/src/version-manager.ts +3 -2
  244. package/src/version.ts +19 -0
  245. package/src/workflow-observation-engine.ts +2 -1
  246. package/src/workflow-observation-schema.ts +2 -1
  247. package/src/workflow-observation-store.ts +6 -2
  248. package/.env +0 -4
  249. package/CLAUDE.md +0 -46
  250. package/config.json +0 -55
  251. package/daily-recall.sh +0 -110
  252. package/logs/daily-recall.error.log +0 -0
  253. package/logs/daily-recall.log +0 -60
  254. package/logs/ingest-2026-04-11.log +0 -110
  255. package/logs/ingest-2026-04-12.log +0 -90
  256. package/logs/ingest-2026-04-13.log +0 -87
  257. package/logs/launchagent-stderr.log +0 -0
  258. package/logs/launchagent-stdout.log +0 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Tests for Tier 3.4: Prospective Memory (Conditional Trigger Reminders)
3
+ *
4
+ * Validates:
5
+ * 1. setReminder creates a properly structured memory entry
6
+ * 2. checkTriggers finds matching pending reminders
7
+ * 3. checkTriggers ignores fired/expired reminders
8
+ * 4. fireReminder marks the reminder as fired
9
+ * 5. formatReminders produces readable output
10
+ * 6. Trigger matching uses keyword logic
11
+ * 7. Expiration is handled correctly
12
+ */
13
+ import { describe, expect, it } from "bun:test";
14
+ import {
15
+ setReminder,
16
+ checkTriggers,
17
+ fireReminder,
18
+ formatReminders,
19
+ } from "../prospective-memory.js";
20
+ import type { MemoryEntry, MemorySearchResult } from "../store.js";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ function createMockStore() {
27
+ const data = new Map<string, MemoryEntry>();
28
+ let idCounter = 0;
29
+
30
+ return {
31
+ data,
32
+ async store(entry: Omit<MemoryEntry, "id" | "timestamp">) {
33
+ const id = `reminder-${++idCounter}`;
34
+ const full: MemoryEntry = {
35
+ ...entry,
36
+ id,
37
+ timestamp: Date.now(),
38
+ } as MemoryEntry;
39
+ data.set(id, full);
40
+ return full;
41
+ },
42
+ async vectorSearch(
43
+ _vector: number[],
44
+ _limit: number,
45
+ _minScore: number,
46
+ _scopeFilter?: string[],
47
+ ): Promise<MemorySearchResult[]> {
48
+ return [...data.values()].map(e => ({ entry: e, score: 0.8 }));
49
+ },
50
+ async getById(id: string) {
51
+ return data.get(id) ?? null;
52
+ },
53
+ async update(id: string, upd: { metadata?: string; text?: string }, _scope?: string[]) {
54
+ const entry = data.get(id);
55
+ if (!entry) return null;
56
+ if (upd.metadata) entry.metadata = upd.metadata;
57
+ if (upd.text) entry.text = upd.text;
58
+ return entry;
59
+ },
60
+ };
61
+ }
62
+
63
+ function createMockEmbedder() {
64
+ return {
65
+ async embedPassage(_text: string) {
66
+ return [0.1, 0.2, 0.3];
67
+ },
68
+ async embedBatchPassage(texts: string[]) {
69
+ return texts.map(() => [0.1, 0.2, 0.3]);
70
+ },
71
+ };
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // setReminder
76
+ // ---------------------------------------------------------------------------
77
+
78
+ describe("setReminder", () => {
79
+ it("creates a reminder with prospective metadata", async () => {
80
+ const store = createMockStore();
81
+ const embedder = createMockEmbedder();
82
+
83
+ const entry = await setReminder(store as any, embedder as any, {
84
+ trigger: "deployment",
85
+ action: "Check the monitoring dashboard after deploy",
86
+ scope: "project:test",
87
+ });
88
+
89
+ expect(entry.id).toBeTruthy();
90
+ expect(entry.category).toBe("patterns");
91
+ expect(entry.text).toContain("Reminder");
92
+ expect(entry.text).toContain("deployment");
93
+
94
+ const meta = JSON.parse(entry.metadata);
95
+ expect(meta.prospective.trigger).toBe("deployment");
96
+ expect(meta.prospective.action).toBe("Check the monitoring dashboard after deploy");
97
+ expect(meta.prospective.status).toBe("pending");
98
+ });
99
+
100
+ it("creates a reminder with expiration", async () => {
101
+ const store = createMockStore();
102
+ const embedder = createMockEmbedder();
103
+
104
+ const entry = await setReminder(store as any, embedder as any, {
105
+ trigger: "release",
106
+ action: "Update changelog",
107
+ scope: "project:test",
108
+ expiresInDays: 7,
109
+ });
110
+
111
+ const meta = JSON.parse(entry.metadata);
112
+ expect(meta.prospective.expiresAt).toBeTruthy();
113
+ // Should expire roughly 7 days from now
114
+ const expiresAt = new Date(meta.prospective.expiresAt).getTime();
115
+ const now = Date.now();
116
+ const sevenDays = 7 * 86_400_000;
117
+ expect(expiresAt - now).toBeGreaterThan(sevenDays - 60_000);
118
+ expect(expiresAt - now).toBeLessThan(sevenDays + 60_000);
119
+ });
120
+ });
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // checkTriggers
124
+ // ---------------------------------------------------------------------------
125
+
126
+ describe("checkTriggers", () => {
127
+ it("finds matching pending reminders", async () => {
128
+ const store = createMockStore();
129
+ const embedder = createMockEmbedder();
130
+
131
+ await setReminder(store as any, embedder as any, {
132
+ trigger: "deployment",
133
+ action: "Check monitoring",
134
+ scope: "project:test",
135
+ });
136
+
137
+ const reminders = await checkTriggers(
138
+ store as any,
139
+ embedder as any,
140
+ "We need to do a deployment today",
141
+ );
142
+
143
+ expect(reminders.length).toBe(1);
144
+ expect(reminders[0].action).toBe("Check monitoring");
145
+ });
146
+
147
+ it("ignores non-matching queries", async () => {
148
+ const store = createMockStore();
149
+ const embedder = createMockEmbedder();
150
+
151
+ await setReminder(store as any, embedder as any, {
152
+ trigger: "deployment",
153
+ action: "Check monitoring",
154
+ scope: "project:test",
155
+ });
156
+
157
+ const reminders = await checkTriggers(
158
+ store as any,
159
+ embedder as any,
160
+ "Let me review the code changes",
161
+ );
162
+
163
+ expect(reminders.length).toBe(0);
164
+ });
165
+
166
+ it("ignores fired reminders", async () => {
167
+ const store = createMockStore();
168
+ const embedder = createMockEmbedder();
169
+
170
+ const entry = await setReminder(store as any, embedder as any, {
171
+ trigger: "deployment",
172
+ action: "Check monitoring",
173
+ scope: "project:test",
174
+ });
175
+
176
+ // Fire the reminder
177
+ await fireReminder(store as any, entry.id);
178
+
179
+ const reminders = await checkTriggers(
180
+ store as any,
181
+ embedder as any,
182
+ "deployment is happening",
183
+ );
184
+
185
+ expect(reminders.length).toBe(0);
186
+ });
187
+
188
+ it("handles expired reminders", async () => {
189
+ const store = createMockStore();
190
+ const embedder = createMockEmbedder();
191
+
192
+ // Create a reminder that's already expired
193
+ const entry = await setReminder(store as any, embedder as any, {
194
+ trigger: "something",
195
+ action: "do something",
196
+ scope: "project:test",
197
+ });
198
+
199
+ // Manually set expiresAt to past
200
+ const meta = JSON.parse(entry.metadata);
201
+ meta.prospective.expiresAt = "2020-01-01T00:00:00Z";
202
+ entry.metadata = JSON.stringify(meta);
203
+
204
+ const reminders = await checkTriggers(
205
+ store as any,
206
+ embedder as any,
207
+ "something came up",
208
+ );
209
+
210
+ expect(reminders.length).toBe(0);
211
+ // Should be marked as expired
212
+ const updated = JSON.parse(entry.metadata);
213
+ expect(updated.prospective.status).toBe("expired");
214
+ });
215
+ });
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // fireReminder
219
+ // ---------------------------------------------------------------------------
220
+
221
+ describe("fireReminder", () => {
222
+ it("marks reminder as fired and returns action", async () => {
223
+ const store = createMockStore();
224
+ const embedder = createMockEmbedder();
225
+
226
+ const entry = await setReminder(store as any, embedder as any, {
227
+ trigger: "test",
228
+ action: "do the thing",
229
+ scope: "project:test",
230
+ });
231
+
232
+ const action = await fireReminder(store as any, entry.id);
233
+
234
+ expect(action).toBe("do the thing");
235
+ const meta = JSON.parse(store.data.get(entry.id)!.metadata);
236
+ expect(meta.prospective.status).toBe("fired");
237
+ expect(meta.prospective.firedAt).toBeTruthy();
238
+ });
239
+
240
+ it("returns null for non-existent entry", async () => {
241
+ const store = createMockStore();
242
+ const action = await fireReminder(store as any, "nonexistent");
243
+ expect(action).toBeNull();
244
+ });
245
+
246
+ it("returns null for already-fired reminder", async () => {
247
+ const store = createMockStore();
248
+ const embedder = createMockEmbedder();
249
+
250
+ const entry = await setReminder(store as any, embedder as any, {
251
+ trigger: "test",
252
+ action: "do it",
253
+ scope: "project:test",
254
+ });
255
+
256
+ await fireReminder(store as any, entry.id);
257
+ const secondFire = await fireReminder(store as any, entry.id);
258
+
259
+ expect(secondFire).toBeNull();
260
+ });
261
+ });
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // formatReminders
265
+ // ---------------------------------------------------------------------------
266
+
267
+ describe("formatReminders", () => {
268
+ it("formats reminders for context injection", () => {
269
+ const formatted = formatReminders([
270
+ { entryId: "r1", trigger: "deploy", action: "Check monitoring after deploy" },
271
+ { entryId: "r2", trigger: "meeting", action: "Bring up the budget issue" },
272
+ ]);
273
+
274
+ expect(formatted.length).toBe(2);
275
+ expect(formatted[0]).toContain("deploy");
276
+ expect(formatted[0]).toContain("Check monitoring after deploy");
277
+ expect(formatted[1]).toContain("meeting");
278
+ });
279
+
280
+ it("handles empty array", () => {
281
+ expect(formatReminders([])).toEqual([]);
282
+ });
283
+ });