chapterhouse 0.13.0 → 0.14.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 (118) hide show
  1. package/dist/api/route-coverage.test.js +1 -3
  2. package/dist/api/server.js +0 -2
  3. package/dist/api/server.test.js +0 -281
  4. package/dist/config.js +3 -85
  5. package/dist/config.test.js +5 -123
  6. package/dist/copilot/agents.js +25 -13
  7. package/dist/copilot/agents.test.js +10 -11
  8. package/dist/copilot/memory-coordinator.js +12 -227
  9. package/dist/copilot/memory-coordinator.test.js +31 -250
  10. package/dist/copilot/orchestrator.js +8 -66
  11. package/dist/copilot/orchestrator.test.js +9 -467
  12. package/dist/copilot/skills.js +15 -1
  13. package/dist/copilot/system-message.js +9 -15
  14. package/dist/copilot/system-message.test.js +9 -22
  15. package/dist/copilot/tools/index.js +3 -3
  16. package/dist/copilot/tools-deps.js +1 -1
  17. package/dist/copilot/tools.agent.test.js +6 -0
  18. package/dist/copilot/tools.inventory.test.js +1 -14
  19. package/dist/daemon.js +7 -9
  20. package/dist/memory/assets.js +33 -0
  21. package/dist/memory/domains.js +58 -0
  22. package/dist/memory/domains.test.js +47 -0
  23. package/dist/memory/git.js +66 -0
  24. package/dist/memory/git.test.js +32 -0
  25. package/dist/memory/history.js +19 -0
  26. package/dist/memory/hottier.js +32 -0
  27. package/dist/memory/hottier.test.js +33 -0
  28. package/dist/memory/index.js +5 -13
  29. package/dist/memory/instructions.js +17 -0
  30. package/dist/memory/manager.js +84 -0
  31. package/dist/memory/markdown.js +78 -0
  32. package/dist/memory/markdown.test.js +42 -0
  33. package/dist/memory/mutex.js +18 -0
  34. package/dist/memory/path-guard.js +26 -0
  35. package/dist/memory/path-guard.test.js +27 -0
  36. package/dist/memory/paths.js +12 -0
  37. package/dist/memory/reconcile.js +75 -0
  38. package/dist/memory/reconcile.test.js +50 -0
  39. package/dist/memory/scaffold.js +37 -0
  40. package/dist/memory/scaffold.test.js +52 -0
  41. package/dist/memory/tools/commit-wrapper.js +32 -0
  42. package/dist/memory/tools/domains.js +73 -0
  43. package/dist/memory/tools/domains.test.js +66 -0
  44. package/dist/memory/tools/git.js +52 -0
  45. package/dist/memory/tools/index.js +25 -0
  46. package/dist/memory/tools/read.js +101 -0
  47. package/dist/memory/tools/read.test.js +69 -0
  48. package/dist/memory/tools/search.js +103 -0
  49. package/dist/memory/tools/search.test.js +63 -0
  50. package/dist/memory/tools/sessions.js +45 -0
  51. package/dist/memory/tools/sessions.test.js +74 -0
  52. package/dist/memory/tools/shared.js +7 -0
  53. package/dist/memory/tools/write.js +116 -0
  54. package/dist/memory/tools/write.test.js +107 -0
  55. package/dist/memory/walk.js +39 -0
  56. package/dist/store/repositories/sessions.js +40 -0
  57. package/dist/wiki/consolidation.js +3 -31
  58. package/dist/wiki/consolidation.test.js +0 -19
  59. package/dist/wiki/frontmatter.js +18 -6
  60. package/dist/wiki/frontmatter.test.js +40 -0
  61. package/package.json +1 -1
  62. package/skills/system/evolve/SKILL.md +131 -0
  63. package/skills/system/foresight/SKILL.md +116 -0
  64. package/skills/system/history/SKILL.md +58 -0
  65. package/skills/system/housekeeping/SKILL.md +185 -0
  66. package/skills/system/reflect/SKILL.md +214 -0
  67. package/skills/system/scenario/SKILL.md +198 -0
  68. package/skills/system/setup/SKILL.md +113 -0
  69. package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
  70. package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
  71. package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
  72. package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
  73. package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
  74. package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
  75. package/web/dist/index.html +1 -1
  76. package/dist/api/routes/memory.js +0 -475
  77. package/dist/api/routes/memory.test.js +0 -108
  78. package/dist/copilot/tools/memory.js +0 -678
  79. package/dist/copilot/tools.memory.test.js +0 -590
  80. package/dist/memory/action-items.js +0 -100
  81. package/dist/memory/action-items.test.js +0 -83
  82. package/dist/memory/active-scope.js +0 -78
  83. package/dist/memory/active-scope.test.js +0 -80
  84. package/dist/memory/checkpoint-prompt.js +0 -71
  85. package/dist/memory/checkpoint.js +0 -274
  86. package/dist/memory/checkpoint.test.js +0 -275
  87. package/dist/memory/decisions.js +0 -54
  88. package/dist/memory/decisions.test.js +0 -92
  89. package/dist/memory/entities.js +0 -70
  90. package/dist/memory/entities.test.js +0 -65
  91. package/dist/memory/eot.js +0 -459
  92. package/dist/memory/eot.test.js +0 -949
  93. package/dist/memory/hooks.js +0 -149
  94. package/dist/memory/hooks.test.js +0 -325
  95. package/dist/memory/hot-tier.js +0 -283
  96. package/dist/memory/hot-tier.test.js +0 -275
  97. package/dist/memory/housekeeping-scheduler.js +0 -187
  98. package/dist/memory/housekeeping-scheduler.test.js +0 -236
  99. package/dist/memory/housekeeping.js +0 -497
  100. package/dist/memory/housekeeping.test.js +0 -410
  101. package/dist/memory/inbox.js +0 -83
  102. package/dist/memory/inbox.test.js +0 -178
  103. package/dist/memory/migration.js +0 -244
  104. package/dist/memory/migration.test.js +0 -108
  105. package/dist/memory/observations.js +0 -46
  106. package/dist/memory/observations.test.js +0 -86
  107. package/dist/memory/recall.js +0 -269
  108. package/dist/memory/recall.test.js +0 -265
  109. package/dist/memory/reflect.js +0 -273
  110. package/dist/memory/reflect.test.js +0 -256
  111. package/dist/memory/scope-lock.js +0 -26
  112. package/dist/memory/scope-lock.test.js +0 -118
  113. package/dist/memory/scopes.js +0 -89
  114. package/dist/memory/scopes.test.js +0 -176
  115. package/dist/memory/tiering.js +0 -223
  116. package/dist/memory/tiering.test.js +0 -323
  117. package/dist/memory/types.js +0 -2
  118. package/web/dist/assets/index-gAvLNEvJ.js.map +0 -1
@@ -1,187 +0,0 @@
1
- import { getDb } from "../store/db.js";
2
- import { childLogger } from "../util/logger.js";
3
- import { isHousekeepingInFlight, runHousekeeping } from "./housekeeping.js";
4
- import { reflectAllScopes } from "./reflect.js";
5
- export const DEFAULT_MEMORY_HOUSEKEEP_INTERVAL_MS = 21_600_000;
6
- export const DEFAULT_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS = 300_000;
7
- export const DEFAULT_MEMORY_REFLECT_INTERVAL_DAYS = 7;
8
- function parseNonNegativeIntegerEnv(name, rawValue, defaultValue) {
9
- const normalized = rawValue?.trim();
10
- if (!normalized) {
11
- return defaultValue;
12
- }
13
- const parsed = Number(normalized);
14
- if (!Number.isInteger(parsed) || parsed < 0) {
15
- throw new Error(`${name} must be a non-negative integer, got: "${rawValue}"`);
16
- }
17
- return parsed;
18
- }
19
- function toPassCounts(result) {
20
- return Object.fromEntries(result.summaries.map((summary) => [
21
- summary.pass,
22
- {
23
- examined: summary.examined,
24
- modified: summary.modified,
25
- errors: summary.errors,
26
- },
27
- ]));
28
- }
29
- export class MemoryHousekeepingScheduler {
30
- env;
31
- runHousekeepingImpl;
32
- log;
33
- runReflectAllScopesImpl;
34
- nowImpl;
35
- setTimeoutImpl;
36
- clearTimeoutImpl;
37
- setIntervalImpl;
38
- clearIntervalImpl;
39
- timeoutHandle;
40
- intervalHandle;
41
- activeRun;
42
- running = false;
43
- started = false;
44
- reflectIntervalDays;
45
- lastReflectAtMs;
46
- constructor(options = {}) {
47
- this.env = options.env ?? {};
48
- this.runHousekeepingImpl = options.runHousekeeping ?? runHousekeeping;
49
- this.runReflectAllScopesImpl = options.runReflectAllScopes ?? reflectAllScopes;
50
- this.nowImpl = options.nowImpl ?? Date.now;
51
- this.log = options.log ?? childLogger("memory.housekeeping.scheduler");
52
- this.reflectIntervalDays = parseNonNegativeIntegerEnv("CHAPTERHOUSE_MEMORY_REFLECT_INTERVAL_DAYS", this.env.CHAPTERHOUSE_MEMORY_REFLECT_INTERVAL_DAYS, DEFAULT_MEMORY_REFLECT_INTERVAL_DAYS);
53
- this.lastReflectAtMs = this.reflectIntervalDays > 0 ? this.nowImpl() : undefined;
54
- this.setTimeoutImpl = options.setTimeoutImpl ?? setTimeout;
55
- this.clearTimeoutImpl = options.clearTimeoutImpl ?? ((handle) => clearTimeout(handle));
56
- this.setIntervalImpl = options.setIntervalImpl ?? setInterval;
57
- this.clearIntervalImpl = options.clearIntervalImpl ?? ((handle) => clearInterval(handle));
58
- }
59
- start() {
60
- if (this.started) {
61
- return;
62
- }
63
- const intervalMs = parseNonNegativeIntegerEnv("CHAPTERHOUSE_MEMORY_HOUSEKEEP_INTERVAL_MS", this.env.CHAPTERHOUSE_MEMORY_HOUSEKEEP_INTERVAL_MS, DEFAULT_MEMORY_HOUSEKEEP_INTERVAL_MS);
64
- if (intervalMs === 0) {
65
- this.log.info({ interval_ms: intervalMs }, "Memory housekeeping scheduler disabled");
66
- return;
67
- }
68
- const initialDelayMs = parseNonNegativeIntegerEnv("CHAPTERHOUSE_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS", this.env.CHAPTERHOUSE_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS, DEFAULT_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS);
69
- this.started = true;
70
- this.timeoutHandle = this.setTimeoutImpl(() => {
71
- this.timeoutHandle = undefined;
72
- this.startScheduledRun("initial_delay");
73
- this.intervalHandle = this.setIntervalImpl(() => {
74
- this.startScheduledRun("interval");
75
- }, intervalMs);
76
- this.intervalHandle?.unref?.();
77
- }, initialDelayMs);
78
- this.timeoutHandle?.unref?.();
79
- this.log.info({ interval_ms: intervalMs, initial_delay_ms: initialDelayMs }, "Memory housekeeping scheduler started");
80
- }
81
- async stop() {
82
- if (this.timeoutHandle) {
83
- this.clearTimeoutImpl(this.timeoutHandle);
84
- this.timeoutHandle = undefined;
85
- }
86
- if (this.intervalHandle) {
87
- this.clearIntervalImpl(this.intervalHandle);
88
- this.intervalHandle = undefined;
89
- }
90
- this.started = false;
91
- await this.activeRun;
92
- }
93
- startScheduledRun(trigger) {
94
- const run = this.runScheduledHousekeeping(trigger);
95
- const tracked = run.finally(() => {
96
- if (this.activeRun === tracked) {
97
- this.activeRun = undefined;
98
- }
99
- });
100
- this.activeRun = tracked;
101
- void this.activeRun;
102
- }
103
- async runScheduledHousekeeping(trigger) {
104
- if (this.running || isHousekeepingInFlight()) {
105
- this.log.warn({ trigger }, "Memory housekeeping run skipped because a previous run is still active");
106
- return;
107
- }
108
- this.running = true;
109
- try {
110
- const result = await this.runHousekeepingImpl({ allScopes: true });
111
- if (isHousekeepingRunResult(result)) {
112
- if (isContentionResult(result)) {
113
- this.log.warn({
114
- trigger,
115
- scope_ids: result.scopeIds,
116
- summaries: result.summaries,
117
- }, "Memory housekeeping run skipped because a previous run is still active");
118
- return;
119
- }
120
- this.log.info({
121
- trigger,
122
- scope_ids: result.scopeIds,
123
- summaries: result.summaries,
124
- pass_counts: toPassCounts(result),
125
- total_examined: result.totalExamined,
126
- total_modified: result.totalModified,
127
- duration_ms: result.durationMs,
128
- }, "Memory housekeeping scheduled run complete");
129
- await this.maybeRunReflect(trigger);
130
- }
131
- else {
132
- this.log.info({ trigger }, "Memory housekeeping scheduled run complete");
133
- await this.maybeRunReflect(trigger);
134
- }
135
- }
136
- catch (error) {
137
- const message = error instanceof Error ? error.message : String(error);
138
- if (this.log.error) {
139
- this.log.error({ trigger, err: message }, "Memory housekeeping scheduled run failed");
140
- }
141
- else {
142
- this.log.warn({ trigger, err: message }, "Memory housekeeping scheduled run failed");
143
- }
144
- }
145
- finally {
146
- this.running = false;
147
- }
148
- }
149
- async maybeRunReflect(trigger) {
150
- if (this.reflectIntervalDays === 0) {
151
- return;
152
- }
153
- const now = this.nowImpl();
154
- const intervalMs = this.reflectIntervalDays * 24 * 60 * 60 * 1000;
155
- if (this.lastReflectAtMs !== undefined && (now - this.lastReflectAtMs) < intervalMs) {
156
- return;
157
- }
158
- const result = await this.runReflectAllScopesImpl(getDb());
159
- this.lastReflectAtMs = now;
160
- const totals = Object.values(result).reduce((acc, scopeResult) => ({
161
- patterns_created: acc.patterns_created + scopeResult.patternsCreated,
162
- patterns_updated: acc.patterns_updated + scopeResult.patternsUpdated,
163
- contradictions_found: acc.contradictions_found + scopeResult.contradictionsFound,
164
- }), {
165
- patterns_created: 0,
166
- patterns_updated: 0,
167
- contradictions_found: 0,
168
- });
169
- this.log.info({ trigger, scopes: Object.keys(result), ...totals }, "Memory reflect scheduled run complete");
170
- }
171
- }
172
- function isContentionResult(result) {
173
- return result.summaries.some((summary) => (summary.pass === "runHousekeeping"
174
- && summary.errors.some((error) => /already in flight/i.test(error))));
175
- }
176
- function isHousekeepingRunResult(value) {
177
- if (!value || typeof value !== "object") {
178
- return false;
179
- }
180
- const candidate = value;
181
- return Array.isArray(candidate.scopeIds)
182
- && Array.isArray(candidate.summaries)
183
- && typeof candidate.totalExamined === "number"
184
- && typeof candidate.totalModified === "number"
185
- && typeof candidate.durationMs === "number";
186
- }
187
- //# sourceMappingURL=housekeeping-scheduler.js.map
@@ -1,236 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- async function loadSchedulerModule() {
4
- return await import(new URL(`./housekeeping-scheduler.js?cachebust=${Date.now()}-${Math.random()}`, import.meta.url).href);
5
- }
6
- function createTimers() {
7
- let nextId = 1;
8
- const timeouts = [];
9
- const intervals = [];
10
- return {
11
- timeouts,
12
- intervals,
13
- setTimeoutImpl(callback, delayMs) {
14
- const handle = { kind: "timeout", id: nextId++, unref() { } };
15
- timeouts.push({ handle, callback, delayMs, cleared: false });
16
- return handle;
17
- },
18
- clearTimeoutImpl(handle) {
19
- const entry = timeouts.find((item) => item.handle === handle);
20
- if (entry)
21
- entry.cleared = true;
22
- },
23
- setIntervalImpl(callback, delayMs) {
24
- const handle = { kind: "interval", id: nextId++, unref() { } };
25
- intervals.push({ handle, callback, delayMs, cleared: false });
26
- return handle;
27
- },
28
- clearIntervalImpl(handle) {
29
- const entry = intervals.find((item) => item.handle === handle);
30
- if (entry)
31
- entry.cleared = true;
32
- },
33
- };
34
- }
35
- test("MemoryHousekeepingScheduler registers with the default 6h interval when env is unset", async () => {
36
- const schedulerModule = await loadSchedulerModule();
37
- const timers = createTimers();
38
- const scheduler = new schedulerModule.MemoryHousekeepingScheduler({
39
- env: {},
40
- runHousekeeping: () => ({ scopeIds: [], summaries: [], totalExamined: 0, totalModified: 0, durationMs: 0 }),
41
- setTimeoutImpl: timers.setTimeoutImpl,
42
- clearTimeoutImpl: timers.clearTimeoutImpl,
43
- setIntervalImpl: timers.setIntervalImpl,
44
- clearIntervalImpl: timers.clearIntervalImpl,
45
- });
46
- scheduler.start();
47
- timers.timeouts[0]?.callback();
48
- assert.equal(schedulerModule.DEFAULT_MEMORY_HOUSEKEEP_INTERVAL_MS, 21_600_000);
49
- assert.equal(timers.intervals.length, 1);
50
- assert.equal(timers.intervals[0]?.delayMs, 21_600_000);
51
- });
52
- test("MemoryHousekeepingScheduler is disabled when CHAPTERHOUSE_MEMORY_HOUSEKEEP_INTERVAL_MS is 0", async () => {
53
- const schedulerModule = await loadSchedulerModule();
54
- const timers = createTimers();
55
- let runs = 0;
56
- const scheduler = new schedulerModule.MemoryHousekeepingScheduler({
57
- env: { CHAPTERHOUSE_MEMORY_HOUSEKEEP_INTERVAL_MS: "0" },
58
- runHousekeeping: () => {
59
- runs += 1;
60
- },
61
- setTimeoutImpl: timers.setTimeoutImpl,
62
- clearTimeoutImpl: timers.clearTimeoutImpl,
63
- setIntervalImpl: timers.setIntervalImpl,
64
- clearIntervalImpl: timers.clearIntervalImpl,
65
- });
66
- scheduler.start();
67
- assert.equal(timers.timeouts.length, 0);
68
- assert.equal(timers.intervals.length, 0);
69
- assert.equal(runs, 0);
70
- });
71
- test("MemoryHousekeepingScheduler waits for the initial delay before the first run", async () => {
72
- const schedulerModule = await loadSchedulerModule();
73
- const timers = createTimers();
74
- let runs = 0;
75
- const scheduler = new schedulerModule.MemoryHousekeepingScheduler({
76
- env: {},
77
- runHousekeeping: () => {
78
- runs += 1;
79
- },
80
- setTimeoutImpl: timers.setTimeoutImpl,
81
- clearTimeoutImpl: timers.clearTimeoutImpl,
82
- setIntervalImpl: timers.setIntervalImpl,
83
- clearIntervalImpl: timers.clearIntervalImpl,
84
- });
85
- scheduler.start();
86
- assert.equal(schedulerModule.DEFAULT_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS, 300_000);
87
- assert.equal(timers.timeouts.length, 1);
88
- assert.equal(timers.timeouts[0]?.delayMs, 300_000);
89
- assert.equal(runs, 0);
90
- timers.timeouts[0]?.callback();
91
- await Promise.resolve();
92
- assert.equal(runs, 1);
93
- });
94
- test("MemoryHousekeepingScheduler does not overlap runs when an interval fires during an active run", async () => {
95
- const schedulerModule = await loadSchedulerModule();
96
- const timers = createTimers();
97
- const warnings = [];
98
- let runs = 0;
99
- let releaseRun;
100
- const scheduler = new schedulerModule.MemoryHousekeepingScheduler({
101
- env: {
102
- CHAPTERHOUSE_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS: "1",
103
- CHAPTERHOUSE_MEMORY_HOUSEKEEP_INTERVAL_MS: "2",
104
- },
105
- runHousekeeping: () => {
106
- runs += 1;
107
- return new Promise((resolve) => {
108
- releaseRun = resolve;
109
- });
110
- },
111
- log: {
112
- info: () => { },
113
- warn: (_obj, msg) => warnings.push(msg),
114
- },
115
- setTimeoutImpl: timers.setTimeoutImpl,
116
- clearTimeoutImpl: timers.clearTimeoutImpl,
117
- setIntervalImpl: timers.setIntervalImpl,
118
- clearIntervalImpl: timers.clearIntervalImpl,
119
- });
120
- scheduler.start();
121
- timers.timeouts[0]?.callback();
122
- await Promise.resolve();
123
- timers.intervals[0]?.callback();
124
- await Promise.resolve();
125
- assert.equal(runs, 1);
126
- assert.ok(warnings.some((entry) => entry.includes("Memory housekeeping run skipped")));
127
- releaseRun?.();
128
- await Promise.resolve();
129
- await Promise.resolve();
130
- timers.intervals[0]?.callback();
131
- await Promise.resolve();
132
- assert.equal(runs, 2);
133
- });
134
- test("MemoryHousekeepingScheduler stop clears pending initial-delay and active interval timers", async () => {
135
- const schedulerModule = await loadSchedulerModule();
136
- const timers = createTimers();
137
- const beforeInitialRun = new schedulerModule.MemoryHousekeepingScheduler({
138
- env: {},
139
- runHousekeeping: () => { },
140
- setTimeoutImpl: timers.setTimeoutImpl,
141
- clearTimeoutImpl: timers.clearTimeoutImpl,
142
- setIntervalImpl: timers.setIntervalImpl,
143
- clearIntervalImpl: timers.clearIntervalImpl,
144
- });
145
- beforeInitialRun.start();
146
- await beforeInitialRun.stop();
147
- assert.equal(timers.timeouts[0]?.cleared, true);
148
- const afterInitialRun = new schedulerModule.MemoryHousekeepingScheduler({
149
- env: {},
150
- runHousekeeping: () => { },
151
- setTimeoutImpl: timers.setTimeoutImpl,
152
- clearTimeoutImpl: timers.clearTimeoutImpl,
153
- setIntervalImpl: timers.setIntervalImpl,
154
- clearIntervalImpl: timers.clearIntervalImpl,
155
- });
156
- afterInitialRun.start();
157
- timers.timeouts[1]?.callback();
158
- await afterInitialRun.stop();
159
- assert.equal(timers.intervals[0]?.cleared, true);
160
- });
161
- test("MemoryHousekeepingScheduler stop waits for an in-flight run to finish without aborting it", async () => {
162
- const schedulerModule = await loadSchedulerModule();
163
- const timers = createTimers();
164
- let releaseRun;
165
- let stopResolved = false;
166
- const scheduler = new schedulerModule.MemoryHousekeepingScheduler({
167
- env: {},
168
- runHousekeeping: () => new Promise((resolve) => {
169
- releaseRun = resolve;
170
- }),
171
- setTimeoutImpl: timers.setTimeoutImpl,
172
- clearTimeoutImpl: timers.clearTimeoutImpl,
173
- setIntervalImpl: timers.setIntervalImpl,
174
- clearIntervalImpl: timers.clearIntervalImpl,
175
- });
176
- scheduler.start();
177
- timers.timeouts[0]?.callback();
178
- await Promise.resolve();
179
- const stopped = scheduler.stop().then(() => {
180
- stopResolved = true;
181
- });
182
- await Promise.resolve();
183
- assert.equal(stopResolved, false);
184
- releaseRun?.();
185
- await stopped;
186
- assert.equal(stopResolved, true);
187
- });
188
- test("MemoryHousekeepingScheduler runs weekly reflection after housekeeping and logs pattern deltas", async () => {
189
- const schedulerModule = await loadSchedulerModule();
190
- const timers = createTimers();
191
- const infos = [];
192
- const housekeepingRuns = [];
193
- const reflectRuns = [];
194
- const DAY_MS = 24 * 60 * 60 * 1000;
195
- let now = 0;
196
- const scheduler = new schedulerModule.MemoryHousekeepingScheduler({
197
- env: {
198
- CHAPTERHOUSE_MEMORY_HOUSEKEEP_INITIAL_DELAY_MS: "1",
199
- CHAPTERHOUSE_MEMORY_HOUSEKEEP_INTERVAL_MS: "2",
200
- CHAPTERHOUSE_MEMORY_REFLECT_INTERVAL_DAYS: "7",
201
- },
202
- runHousekeeping: async () => {
203
- housekeepingRuns.push(`housekeeping:${now}`);
204
- return { scopeIds: [1], summaries: [], totalExamined: 0, totalModified: 0, durationMs: 0 };
205
- },
206
- runReflectAllScopes: async () => {
207
- reflectRuns.push(`reflect:${now}`);
208
- return {
209
- chapterhouse: { patternsCreated: 2, patternsUpdated: 1, contradictionsFound: 1 },
210
- };
211
- },
212
- nowImpl: () => now,
213
- log: {
214
- info: (obj, msg) => infos.push({ obj, msg }),
215
- warn: () => { },
216
- },
217
- setTimeoutImpl: timers.setTimeoutImpl,
218
- clearTimeoutImpl: timers.clearTimeoutImpl,
219
- setIntervalImpl: timers.setIntervalImpl,
220
- clearIntervalImpl: timers.clearIntervalImpl,
221
- });
222
- scheduler.start();
223
- timers.timeouts[0]?.callback();
224
- await Promise.resolve();
225
- await Promise.resolve();
226
- assert.equal(housekeepingRuns.length, 1);
227
- assert.equal(reflectRuns.length, 0);
228
- now = 7 * DAY_MS;
229
- timers.intervals[0]?.callback();
230
- await scheduler.stop();
231
- assert.equal(housekeepingRuns.length, 2);
232
- assert.equal(reflectRuns.length, 1);
233
- assert.equal(infos.some((entry) => entry.msg.includes("Memory reflect scheduled run complete")), true);
234
- assert.equal(infos.some((entry) => entry.obj.patterns_created === 2 && entry.obj.patterns_updated === 1 && entry.obj.contradictions_found === 1), true);
235
- });
236
- //# sourceMappingURL=housekeeping-scheduler.test.js.map