openhermes 4.11.2 → 4.13.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 (74) hide show
  1. package/CONTEXT.md +1 -1
  2. package/ETHOS.md +1 -1
  3. package/README.md +12 -18
  4. package/bootstrap.ts +73 -148
  5. package/docs/HOW-IT-WORKS.md +162 -0
  6. package/docs/adr/ADR-0001-rebuild-vs-increment.md +30 -0
  7. package/docs/adr/ADR-0002-routing-graph-vs-linear-chain.md +36 -0
  8. package/docs/adr/ADR-0003-per-directory-plan-storage.md +34 -0
  9. package/docs/adr/ADR-0004-composer-fragment-architecture.md +42 -0
  10. package/docs/adr/ADR-0005-hook-system-design.md +42 -0
  11. package/docs/adr/README.md +9 -0
  12. package/harness/codex/AUTOPILOT.md +30 -23
  13. package/harness/codex/CHARTER.md +3 -3
  14. package/harness/lib/composer/compose.test.ts +11 -0
  15. package/harness/lib/composer/fragments/02-delegation.md +2 -1
  16. package/harness/lib/composer/fragments/04-task-flow.md +42 -2
  17. package/harness/lib/composer/fragments/08-routing.md +1 -1
  18. package/harness/lib/composer/fragments/09-guardrails.md +17 -4
  19. package/harness/lib/composer/index.ts +1 -1
  20. package/harness/lib/guards/guard-config.ts +72 -0
  21. package/harness/lib/hooks/builtins/confidence-gate-hook.ts +2 -4
  22. package/harness/lib/hooks/builtins/delegation-depth-hook.ts +23 -4
  23. package/harness/lib/hooks/builtins/dynamic-route-hook.ts +99 -0
  24. package/harness/lib/hooks/builtins/next-route-hook.ts +24 -0
  25. package/harness/lib/hooks/builtins/plan-check-hook.ts +2 -2
  26. package/harness/lib/hooks/builtins/route-tracking-hook.ts +79 -25
  27. package/harness/lib/hooks/hooks.test.ts +117 -205
  28. package/harness/lib/hooks/index.ts +38 -30
  29. package/harness/lib/hooks/registry.ts +309 -416
  30. package/harness/lib/hooks/types.ts +116 -71
  31. package/harness/lib/plans/plan-location.ts +134 -0
  32. package/harness/lib/routing/index.ts +21 -0
  33. package/harness/lib/routing/route-guidance.ts +147 -0
  34. package/harness/lib/routing/route-resolver.ts +58 -0
  35. package/harness/lib/routing/routing.test.ts +195 -0
  36. package/harness/lib/routing/skill-frontmatter.ts +125 -0
  37. package/harness/lib/routing/types.ts +52 -0
  38. package/harness/skills/oh-ascii/SKILL.md +1 -1
  39. package/harness/skills/oh-fusion/DEEP.md +56 -33
  40. package/harness/skills/oh-fusion/SKILL.md +30 -16
  41. package/harness/skills/oh-init/DEEP.md +2 -2
  42. package/harness/skills/oh-manifest/SKILL.md +1 -0
  43. package/harness/skills/oh-plan-review/DEEP.md +1 -1
  44. package/harness/skills/oh-planner/DEEP.md +3 -3
  45. package/harness/skills/oh-review/DEEP.md +2 -0
  46. package/harness/skills/oh-review/SKILL.md +1 -0
  47. package/package.json +56 -55
  48. package/harness/lib/background/background.test.ts +0 -197
  49. package/harness/lib/background/index.ts +0 -7
  50. package/harness/lib/background/interfaces.ts +0 -31
  51. package/harness/lib/background/manager.ts +0 -320
  52. package/harness/lib/hooks/builtins/error-recovery-hook.ts +0 -107
  53. package/harness/lib/hooks/builtins/memory-sync-hook.ts +0 -73
  54. package/harness/lib/hooks/builtins/sanity-check-hook.ts +0 -52
  55. package/harness/lib/memory/index.ts +0 -18
  56. package/harness/lib/memory/interfaces.ts +0 -53
  57. package/harness/lib/memory/memory-manager.ts +0 -205
  58. package/harness/lib/memory/memory.test.ts +0 -491
  59. package/harness/lib/memory/plan-store.ts +0 -366
  60. package/harness/lib/recovery/handler.ts +0 -243
  61. package/harness/lib/recovery/index.ts +0 -14
  62. package/harness/lib/recovery/interfaces.ts +0 -48
  63. package/harness/lib/recovery/patterns.ts +0 -149
  64. package/harness/lib/recovery/recovery.test.ts +0 -312
  65. package/harness/lib/sanity/anomaly-tracker.ts +0 -127
  66. package/harness/lib/sanity/checker.ts +0 -178
  67. package/harness/lib/sanity/index.ts +0 -13
  68. package/harness/lib/sanity/interfaces.ts +0 -24
  69. package/harness/lib/sanity/sanity.test.ts +0 -472
  70. package/harness/lib/sync/file-watcher.ts +0 -174
  71. package/harness/lib/sync/index.ts +0 -11
  72. package/harness/lib/sync/interfaces.ts +0 -27
  73. package/harness/lib/sync/plan-sync.ts +0 -536
  74. package/harness/lib/sync/sync.test.ts +0 -832
@@ -1,491 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import assert from "node:assert/strict";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import os from "node:os";
6
- import { randomUUID } from "node:crypto";
7
-
8
- import { MemoryManager } from "./memory-manager.ts";
9
- import { PlanStore } from "./plan-store.ts";
10
- import { MemoryLevel, DEFAULT_BUDGETS } from "./interfaces.ts";
11
- import type { MemoryEntry, Finding, Decision } from "./interfaces.ts";
12
-
13
- // ---------------------------------------------------------------------------
14
- // Helpers
15
- // ---------------------------------------------------------------------------
16
-
17
- function tmpdir(): string {
18
- return fs.mkdtempSync(path.join(os.tmpdir(), "memory-test-"));
19
- }
20
-
21
- // ---------------------------------------------------------------------------
22
- // MemoryManager tests
23
- // ---------------------------------------------------------------------------
24
-
25
- describe("MemoryManager", () => {
26
- beforeEach(() => {
27
- MemoryManager.resetInstance();
28
- });
29
-
30
- afterEach(() => {
31
- MemoryManager.resetInstance();
32
- });
33
-
34
- // ---- 1: add() creates entry with correct level ---------------------------
35
-
36
- it("add() creates an entry at the given level", () => {
37
- const mm = MemoryManager.getInstance();
38
- const entry = mm.add(MemoryLevel.MISSION, "Test mission entry", 0.8);
39
-
40
- assert.ok(entry.id, "entry must have an id");
41
- assert.equal(entry.level, MemoryLevel.MISSION);
42
- assert.equal(entry.content, "Test mission entry");
43
- assert.equal(entry.importance, 0.8);
44
- assert.ok(entry.timestamp > 0, "timestamp must be set");
45
- });
46
-
47
- // ---- 2: getEntries() returns entries at all levels ----------------------
48
-
49
- it("getEntries() returns all entries when no level filter", () => {
50
- const mm = MemoryManager.getInstance();
51
- mm.add(MemoryLevel.SYSTEM, "sys", 1.0);
52
- mm.add(MemoryLevel.PROJECT, "proj", 0.5);
53
- mm.add(MemoryLevel.TASK, "task", 0.3);
54
-
55
- const all = mm.getEntries();
56
- assert.equal(all.length, 3);
57
- });
58
-
59
- // ---- 3: getEntries() filters by level -----------------------------------
60
-
61
- it("getEntries() filters by level", () => {
62
- const mm = MemoryManager.getInstance();
63
- mm.add(MemoryLevel.SYSTEM, "sys", 1.0);
64
- mm.add(MemoryLevel.PROJECT, "proj", 0.5);
65
-
66
- const sys = mm.getEntries(MemoryLevel.SYSTEM);
67
- assert.equal(sys.length, 1);
68
- assert.equal(sys[0].level, MemoryLevel.SYSTEM);
69
- });
70
-
71
- // ---- 4: getEntryCount() returns correct count ---------------------------
72
-
73
- it("getEntryCount() returns correct count", () => {
74
- const mm = MemoryManager.getInstance();
75
- assert.equal(mm.getEntryCount(MemoryLevel.TASK), 0);
76
-
77
- mm.add(MemoryLevel.TASK, "t1", 0.5);
78
- mm.add(MemoryLevel.TASK, "t2", 0.3);
79
- assert.equal(mm.getEntryCount(MemoryLevel.TASK), 2);
80
- });
81
-
82
- // ---- 5: entries sorted by importance DESC -------------------------------
83
-
84
- it("entries are sorted by importance descending", () => {
85
- const mm = MemoryManager.getInstance();
86
- mm.add(MemoryLevel.TASK, "low", 0.1);
87
- mm.add(MemoryLevel.TASK, "high", 0.9);
88
- mm.add(MemoryLevel.TASK, "mid", 0.5);
89
-
90
- const entries = mm.getEntries(MemoryLevel.TASK);
91
- assert.equal(entries[0].content, "high");
92
- assert.equal(entries[1].content, "mid");
93
- assert.equal(entries[2].content, "low");
94
- });
95
-
96
- // ---- 6: budget enforcement ----------------------------------------------
97
-
98
- it("prunes when budget is exceeded", () => {
99
- const mm = MemoryManager.getInstance({
100
- budgets: { [MemoryLevel.TASK]: 3 },
101
- });
102
-
103
- mm.add(MemoryLevel.TASK, "keep-1", 0.9);
104
- mm.add(MemoryLevel.TASK, "keep-2", 0.8);
105
- mm.add(MemoryLevel.TASK, "keep-3", 0.7);
106
- mm.add(MemoryLevel.TASK, "drop-1", 0.1);
107
- mm.add(MemoryLevel.TASK, "drop-2", 0.2);
108
-
109
- assert.equal(mm.getEntryCount(MemoryLevel.TASK), 3);
110
- const entries = mm.getEntries(MemoryLevel.TASK);
111
- const contents = entries.map((e) => e.content);
112
- assert.ok(contents.includes("keep-1"));
113
- assert.ok(contents.includes("keep-2"));
114
- assert.ok(contents.includes("keep-3"));
115
- assert.ok(!contents.includes("drop-1"));
116
- assert.ok(!contents.includes("drop-2"));
117
- });
118
-
119
- // ---- 7: importance is clamped to [0, 1] ---------------------------------
120
-
121
- it("clamps importance to [0, 1]", () => {
122
- const mm = MemoryManager.getInstance();
123
- const e1 = mm.add(MemoryLevel.TASK, "too-high", 5.0);
124
- const e2 = mm.add(MemoryLevel.TASK, "too-low", -1.0);
125
-
126
- assert.equal(e1.importance, 1.0);
127
- assert.equal(e2.importance, 0.0);
128
- });
129
-
130
- // ---- 8: clearLevel clears only the specified level ----------------------
131
-
132
- it("clearLevel clears only the specified level", () => {
133
- const mm = MemoryManager.getInstance();
134
- mm.add(MemoryLevel.SYSTEM, "sys", 1.0);
135
- mm.add(MemoryLevel.MISSION, "mission", 0.5);
136
- mm.add(MemoryLevel.TASK, "task", 0.3);
137
-
138
- mm.clearLevel(MemoryLevel.TASK);
139
-
140
- assert.equal(mm.getEntryCount(MemoryLevel.TASK), 0);
141
- assert.equal(mm.getEntryCount(MemoryLevel.SYSTEM), 1);
142
- assert.equal(mm.getEntryCount(MemoryLevel.MISSION), 1);
143
- });
144
-
145
- // ---- 9: export / import round-trip --------------------------------------
146
-
147
- it("export/import round-trip preserves all entries", () => {
148
- const mm = MemoryManager.getInstance();
149
- mm.add(MemoryLevel.SYSTEM, "sys-1", 1.0);
150
- mm.add(MemoryLevel.SYSTEM, "sys-2", 0.9);
151
- mm.add(MemoryLevel.PROJECT, "proj-1", 0.5);
152
- mm.add(MemoryLevel.MISSION, "mission-1", 0.8);
153
- mm.add(MemoryLevel.TASK, "task-1", 0.3);
154
-
155
- const snapshot = mm.export();
156
- assert.equal(snapshot.system.length, 2);
157
- assert.equal(snapshot.project.length, 1);
158
- assert.equal(snapshot.mission.length, 1);
159
- assert.equal(snapshot.task.length, 1);
160
-
161
- // Import into a fresh instance
162
- MemoryManager.resetInstance();
163
- const mm2 = MemoryManager.getInstance();
164
- mm2.import(snapshot);
165
-
166
- assert.equal(mm2.getEntryCount(MemoryLevel.SYSTEM), 2);
167
- assert.equal(mm2.getEntryCount(MemoryLevel.PROJECT), 1);
168
- assert.equal(mm2.getEntryCount(MemoryLevel.MISSION), 1);
169
- assert.equal(mm2.getEntryCount(MemoryLevel.TASK), 1);
170
-
171
- // Verify content preserved
172
- const sysEntries = mm2.getEntries(MemoryLevel.SYSTEM);
173
- assert.equal(sysEntries[0].content, "sys-1");
174
- assert.equal(sysEntries[1].content, "sys-2");
175
- });
176
-
177
- // ---- 10: getContext produces formatted output ---------------------------
178
-
179
- it("getContext returns formatted output", () => {
180
- const mm = MemoryManager.getInstance();
181
- mm.add(MemoryLevel.SYSTEM, "OpenHermes identity", 1.0);
182
- mm.add(MemoryLevel.MISSION, "Implement feature X", 0.8);
183
-
184
- const ctx = mm.getContext();
185
- assert.ok(ctx.includes("== SYSTEM =="));
186
- assert.ok(ctx.includes("== MISSION =="));
187
- assert.ok(ctx.includes("OpenHermes identity"));
188
- assert.ok(ctx.includes("Implement feature X"));
189
-
190
- // Verify each line has importance
191
- const lines = ctx.split("\n");
192
- const sysLine = lines.find((l) => l.includes("OpenHermes identity"));
193
- assert.ok(sysLine, "must find system line");
194
- assert.match(sysLine!, /\[1\.00\]/);
195
- });
196
-
197
- // ---- 11: getContext with query filters by relevance ---------------------
198
-
199
- it("getContext filters by query string", () => {
200
- const mm = MemoryManager.getInstance();
201
- mm.add(MemoryLevel.SYSTEM, "OpenHermes identity", 1.0);
202
- mm.add(MemoryLevel.MISSION, "Implement feature X", 0.8);
203
- mm.add(MemoryLevel.PROJECT, "Database schema design", 0.6);
204
-
205
- const ctx = mm.getContext("feature");
206
- assert.ok(ctx.includes("Implement feature X"));
207
- assert.ok(!ctx.includes("OpenHermes identity"));
208
- assert.ok(!ctx.includes("Database schema"));
209
- });
210
-
211
- // ---- 12: getContext with empty query returns all ------------------------
212
-
213
- it("getContext with empty query returns all", () => {
214
- const mm = MemoryManager.getInstance();
215
- mm.add(MemoryLevel.SYSTEM, "entry-1", 1.0);
216
- mm.add(MemoryLevel.PROJECT, "entry-2", 0.5);
217
-
218
- const ctx = mm.getContext("");
219
- assert.ok(ctx.includes("entry-1"));
220
- assert.ok(ctx.includes("entry-2"));
221
- });
222
-
223
- // ---- 13: Default budgets match spec -------------------------------------
224
-
225
- it("has correct default budgets", () => {
226
- assert.equal(DEFAULT_BUDGETS[MemoryLevel.SYSTEM], 50);
227
- assert.equal(DEFAULT_BUDGETS[MemoryLevel.PROJECT], 100);
228
- assert.equal(DEFAULT_BUDGETS[MemoryLevel.MISSION], 30);
229
- assert.equal(DEFAULT_BUDGETS[MemoryLevel.TASK], 20);
230
- });
231
-
232
- // ---- 14: setBudgets re-prunes -------------------------------------------
233
-
234
- it("setBudgets re-prunes after changing budgets", () => {
235
- const mm = MemoryManager.getInstance({
236
- budgets: { [MemoryLevel.TASK]: 10 },
237
- });
238
-
239
- mm.add(MemoryLevel.TASK, "e1", 0.9);
240
- mm.add(MemoryLevel.TASK, "e2", 0.8);
241
- mm.add(MemoryLevel.TASK, "e3", 0.7);
242
-
243
- // Reduce budget to 2 — should prune 1
244
- mm.setBudgets({ [MemoryLevel.TASK]: 2 });
245
- assert.equal(mm.getEntryCount(MemoryLevel.TASK), 2);
246
- });
247
-
248
- // ---- 15: metadata stored and formatted ----------------------------------
249
-
250
- it("metadata is stored on entries", () => {
251
- const mm = MemoryManager.getInstance();
252
- const entry = mm.add(MemoryLevel.TASK, "with meta", 0.5, {
253
- source: "test",
254
- phase: "red",
255
- });
256
-
257
- assert.deepEqual(entry.metadata, { source: "test", phase: "red" });
258
- });
259
- });
260
-
261
- // ---------------------------------------------------------------------------
262
- // PlanStore tests
263
- // ---------------------------------------------------------------------------
264
-
265
- describe("PlanStore", () => {
266
- let testDir: string;
267
-
268
- beforeEach(() => {
269
- testDir = tmpdir();
270
- });
271
-
272
- afterEach(() => {
273
- fs.rmSync(testDir, { recursive: true, force: true });
274
- });
275
-
276
- // ---- 16: readPlan returns defaults for missing file ----------------------
277
-
278
- it("readPlan returns defaults for non-existent file", async () => {
279
- const result = await PlanStore.readPlan(
280
- path.join(testDir, "nonexistent.md"),
281
- );
282
- assert.deepEqual(result.tasks, []);
283
- assert.deepEqual(result.memory, []);
284
- assert.deepEqual(result.findings, []);
285
- assert.deepEqual(result.decisions, []);
286
- });
287
-
288
- // ---- 17: writePlan / readPlan round-trip --------------------------------
289
-
290
- it("writePlan and readPlan round-trip preserves data", async () => {
291
- const planPath = path.join(testDir, "plan-001.md");
292
-
293
- const data = {
294
- tasks: [
295
- { id: randomUUID(), description: "Task A", status: "pending" as const, dependsOn: [] },
296
- { id: randomUUID(), description: "Task B", status: "completed" as const, dependsOn: ["task-a"] },
297
- ],
298
- memory: [
299
- { id: randomUUID(), level: MemoryLevel.PROJECT, content: "Use TypeScript", importance: 0.9, timestamp: Date.now() },
300
- ],
301
- findings: [
302
- { id: randomUUID(), sessionId: "sess-1", description: "Found bug", severity: "blocker" as const, timestamp: Date.now() },
303
- ],
304
- decisions: [
305
- { id: randomUUID(), sessionId: "sess-1", description: "Use Bun", rationale: "Fastest runtime", timestamp: Date.now() },
306
- ],
307
- };
308
-
309
- await PlanStore.writePlan(planPath, data);
310
- assert.ok(fs.existsSync(planPath));
311
-
312
- const loaded = await PlanStore.readPlan(planPath);
313
-
314
- // Tasks
315
- assert.equal(loaded.tasks.length, 2);
316
- assert.equal(loaded.tasks[0].description, "Task A");
317
- assert.equal(loaded.tasks[0].status, "pending");
318
- assert.equal(loaded.tasks[1].description, "Task B");
319
- assert.equal(loaded.tasks[1].status, "completed");
320
-
321
- // Memory
322
- assert.equal(loaded.memory.length, 1);
323
- assert.equal(loaded.memory[0].content, "Use TypeScript");
324
-
325
- // Findings
326
- assert.equal(loaded.findings.length, 1);
327
- assert.equal(loaded.findings[0].description, "Found bug");
328
-
329
- // Decisions
330
- assert.equal(loaded.decisions.length, 1);
331
- assert.equal(loaded.decisions[0].description, "Use Bun");
332
- });
333
-
334
- // ---- 18: addFinding appends to file ------------------------------------
335
-
336
- it("addFinding appends a finding to the plan file", async () => {
337
- const planPath = path.join(testDir, "plan-002.md");
338
- const sessionId = "sess-test";
339
-
340
- // Write initial empty plan
341
- await PlanStore.writePlan(planPath, {
342
- tasks: [],
343
- memory: [],
344
- findings: [],
345
- decisions: [],
346
- });
347
-
348
- await PlanStore.addFinding(planPath, sessionId, {
349
- description: "Critical bug discovered",
350
- severity: "blocker",
351
- });
352
-
353
- const data = await PlanStore.readPlan(planPath);
354
- assert.equal(data.findings.length, 1);
355
- assert.equal(data.findings[0].description, "Critical bug discovered");
356
- assert.equal(data.findings[0].severity, "blocker");
357
- assert.equal(data.findings[0].sessionId, sessionId);
358
- });
359
-
360
- // ---- 19: addDecision appends to file ------------------------------------
361
-
362
- it("addDecision appends a decision to the plan file", async () => {
363
- const planPath = path.join(testDir, "plan-003.md");
364
- const sessionId = "sess-dec";
365
-
366
- await PlanStore.writePlan(planPath, {
367
- tasks: [],
368
- memory: [],
369
- findings: [],
370
- decisions: [],
371
- });
372
-
373
- await PlanStore.addDecision(planPath, sessionId, {
374
- description: "Use singleton pattern",
375
- rationale: "Ensures single instance across sessions",
376
- });
377
-
378
- const data = await PlanStore.readPlan(planPath);
379
- assert.equal(data.decisions.length, 1);
380
- assert.equal(data.decisions[0].description, "Use singleton pattern");
381
- assert.equal(data.decisions[0].rationale, "Ensures single instance across sessions");
382
- assert.equal(data.decisions[0].sessionId, sessionId);
383
- });
384
-
385
- // ---- 20: writePlan preserves existing header ----------------------------
386
-
387
- it("writePlan preserves existing header", async () => {
388
- const planPath = path.join(testDir, "plan-004.md");
389
-
390
- // Write initial plan with header
391
- const header = [
392
- "# PLAN: test-project",
393
- "Plan ID: test-project/plan-004.md",
394
- "Status: active",
395
- "",
396
- ].join("\n");
397
- fs.writeFileSync(planPath, header, "utf8");
398
-
399
- await PlanStore.writePlan(planPath, {
400
- tasks: [{ id: randomUUID(), description: "Do something", status: "pending", dependsOn: [] }],
401
- memory: [],
402
- findings: [],
403
- decisions: [],
404
- });
405
-
406
- const content = fs.readFileSync(planPath, "utf8");
407
- assert.ok(content.includes("# PLAN: test-project"));
408
- assert.ok(content.includes("Status: active"));
409
- assert.ok(content.includes("## Tasks"));
410
- assert.ok(content.includes("Do something"));
411
- });
412
-
413
- // ---- 21: getMerged returns empty for now --------------------------------
414
-
415
- it("getMerged returns empty for now", async () => {
416
- const result = await PlanStore.getMerged("session-1");
417
- assert.deepEqual(result, []);
418
- });
419
- });
420
-
421
- // ---------------------------------------------------------------------------
422
- // Integration: MemoryManager + PlanStore
423
- // ---------------------------------------------------------------------------
424
-
425
- describe("Memory integration", () => {
426
- let testDir: string;
427
-
428
- beforeEach(() => {
429
- testDir = tmpdir();
430
- MemoryManager.resetInstance();
431
- });
432
-
433
- afterEach(() => {
434
- fs.rmSync(testDir, { recursive: true, force: true });
435
- MemoryManager.resetInstance();
436
- });
437
-
438
- it("can persist memory entries through PlanStore", async () => {
439
- const planPath = path.join(testDir, "plan-integration.md");
440
- const mm = MemoryManager.getInstance();
441
-
442
- mm.add(MemoryLevel.PROJECT, "Project convention: use Bun", 0.9);
443
- mm.add(MemoryLevel.MISSION, "Implement memory system", 0.8);
444
-
445
- // Export memory and write to plan
446
- const snapshot = mm.export();
447
- const allMemory = [
448
- ...snapshot.system,
449
- ...snapshot.project,
450
- ...snapshot.mission,
451
- ...snapshot.task,
452
- ];
453
-
454
- await PlanStore.writePlan(planPath, {
455
- tasks: [],
456
- memory: allMemory,
457
- findings: [],
458
- decisions: [],
459
- });
460
-
461
- // Read back
462
- const loaded = await PlanStore.readPlan(planPath);
463
- assert.equal(loaded.memory.length, 2);
464
-
465
- const contents = loaded.memory.map((m) => m.content);
466
- assert.ok(contents.includes("Project convention: use Bun"));
467
- assert.ok(contents.includes("Implement memory system"));
468
-
469
- // Import back into fresh MemoryManager
470
- MemoryManager.resetInstance();
471
- const mm2 = MemoryManager.getInstance();
472
- const restored: MemoryEntry[] = [];
473
-
474
- // Split back into levels
475
- for (const entry of loaded.memory) {
476
- restored.push(entry);
477
- }
478
-
479
- // Group by level and import
480
- const importSnapshot = {
481
- system: restored.filter((e) => e.level === MemoryLevel.SYSTEM),
482
- project: restored.filter((e) => e.level === MemoryLevel.PROJECT),
483
- mission: restored.filter((e) => e.level === MemoryLevel.MISSION),
484
- task: restored.filter((e) => e.level === MemoryLevel.TASK),
485
- };
486
- mm2.import(importSnapshot);
487
-
488
- assert.equal(mm2.getEntryCount(MemoryLevel.PROJECT), 1);
489
- assert.equal(mm2.getEntryCount(MemoryLevel.MISSION), 1);
490
- });
491
- });