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,497 +0,0 @@
1
- import { performance } from "node:perf_hooks";
2
- import { config } from "../config.js";
3
- import { getDb } from "../store/db.js";
4
- import { childLogger } from "../util/logger.js";
5
- import { getActiveScope } from "./active-scope.js";
6
- import { releaseScopeWriteLocks, tryAcquireScopeWriteLocks } from "./scope-lock.js";
7
- import { listScopes } from "./scopes.js";
8
- import { tieringPass } from "./tiering.js";
9
- export { tieringPass };
10
- const log = childLogger("memory.housekeeping");
11
- const GLOBAL_PASS_SCOPE = Symbol("global-housekeeping-pass-scope");
12
- const inFlightScopesByPass = new Map();
13
- const PASS_ORDER = [
14
- "dedup_observations",
15
- "dedup_decisions",
16
- "compact_supersede_chains",
17
- "orphan_cleanup",
18
- "decay",
19
- "compact_inbox",
20
- "tiering",
21
- ];
22
- function passSummary(pass, examined = 0, modified = 0, errors = []) {
23
- return { pass, examined, modified, errors };
24
- }
25
- function tokens(value) {
26
- const words = value
27
- .toLowerCase()
28
- .split(/[^a-z0-9]+/u)
29
- .map((word) => word.replace(/s$/, ""))
30
- .filter((word) => word.length > 1);
31
- return new Set(words);
32
- }
33
- function jaccard(left, right) {
34
- const leftTokens = tokens(left);
35
- const rightTokens = tokens(right);
36
- if (leftTokens.size === 0 || rightTokens.size === 0) {
37
- return 0;
38
- }
39
- let intersection = 0;
40
- for (const token of leftTokens) {
41
- if (rightTokens.has(token)) {
42
- intersection++;
43
- }
44
- }
45
- const union = leftTokens.size + rightTokens.size - intersection;
46
- return union === 0 ? 0 : intersection / union;
47
- }
48
- function isSimilar(left, right) {
49
- if (left.trim().toLowerCase() === right.trim().toLowerCase()) {
50
- return true;
51
- }
52
- return jaccard(left, right) >= config.memoryHousekeepingSimilarityThreshold;
53
- }
54
- function compareObservationKeeper(left, right) {
55
- if (right.confidence !== left.confidence) {
56
- return right.confidence - left.confidence;
57
- }
58
- if (right.created_at !== left.created_at) {
59
- return right.created_at.localeCompare(left.created_at);
60
- }
61
- return left.id - right.id;
62
- }
63
- function compareDecisionKeeper(left, right) {
64
- if (right.decided_at !== left.decided_at) {
65
- return right.decided_at.localeCompare(left.decided_at);
66
- }
67
- if (right.created_at !== left.created_at) {
68
- return right.created_at.localeCompare(left.created_at);
69
- }
70
- return left.id - right.id;
71
- }
72
- function normalizePassName(pass) {
73
- const normalized = pass.trim().toLowerCase().replace(/-/g, "_");
74
- const aliases = {
75
- dedup_observations: "dedup_observations",
76
- dedupobservations: "dedup_observations",
77
- dedupobservationspass: "dedup_observations",
78
- observations: "dedup_observations",
79
- dedup_decisions: "dedup_decisions",
80
- dedupdecisions: "dedup_decisions",
81
- dedupdecisionspass: "dedup_decisions",
82
- decisions: "dedup_decisions",
83
- compact_supersede_chains: "compact_supersede_chains",
84
- compactsupersedechains: "compact_supersede_chains",
85
- compactsupersedechainspass: "compact_supersede_chains",
86
- supersede: "compact_supersede_chains",
87
- supersedechains: "compact_supersede_chains",
88
- orphan_cleanup: "orphan_cleanup",
89
- orphancleanup: "orphan_cleanup",
90
- orphancleanuppass: "orphan_cleanup",
91
- orphans: "orphan_cleanup",
92
- decay: "decay",
93
- decaypass: "decay",
94
- compact_inbox: "compact_inbox",
95
- compactinbox: "compact_inbox",
96
- compactinboxpass: "compact_inbox",
97
- inbox: "compact_inbox",
98
- tiering: "tiering",
99
- tieringpass: "tiering",
100
- tiers: "tiering",
101
- };
102
- const resolved = aliases[normalized];
103
- if (!resolved) {
104
- throw new Error(`Unknown housekeeping pass '${pass}'. Valid passes: ${PASS_ORDER.join(", ")}`);
105
- }
106
- return resolved;
107
- }
108
- export function dedupObservationsPass(scopeId) {
109
- try {
110
- const db = getDb();
111
- const candidates = db.prepare(`
112
- SELECT id, content, confidence, created_at
113
- FROM mem_observations
114
- WHERE scope_id = ?
115
- AND superseded_by IS NULL
116
- AND archived_at IS NULL
117
- ORDER BY id ASC
118
- `).all(scopeId);
119
- let modified = 0;
120
- const visited = new Set();
121
- const update = db.prepare(`UPDATE mem_observations SET superseded_by = ? WHERE id = ? AND superseded_by IS NULL`);
122
- const tx = db.transaction(() => {
123
- for (const candidate of candidates) {
124
- if (visited.has(candidate.id)) {
125
- continue;
126
- }
127
- const cluster = candidates.filter((entry) => !visited.has(entry.id) && isSimilar(candidate.content, entry.content));
128
- for (const entry of cluster) {
129
- visited.add(entry.id);
130
- }
131
- if (cluster.length < 2) {
132
- continue;
133
- }
134
- const [keeper] = cluster.sort(compareObservationKeeper);
135
- if (!keeper) {
136
- continue;
137
- }
138
- for (const entry of cluster) {
139
- if (entry.id === keeper.id) {
140
- continue;
141
- }
142
- modified += update.run(keeper.id, entry.id).changes;
143
- }
144
- }
145
- });
146
- tx();
147
- return passSummary("dedupObservationsPass", candidates.length, modified);
148
- }
149
- catch (error) {
150
- return passSummary("dedupObservationsPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
151
- }
152
- }
153
- export function dedupDecisionsPass(scopeId) {
154
- try {
155
- const db = getDb();
156
- const candidates = db.prepare(`
157
- SELECT id, entity_id, title, decided_at, created_at
158
- FROM mem_decisions
159
- WHERE scope_id = ?
160
- AND superseded_by IS NULL
161
- AND archived_at IS NULL
162
- ORDER BY id ASC
163
- `).all(scopeId);
164
- let modified = 0;
165
- const update = db.prepare(`UPDATE mem_decisions SET superseded_by = ? WHERE id = ? AND superseded_by IS NULL`);
166
- const tx = db.transaction(() => {
167
- const visited = new Set();
168
- for (const candidate of candidates) {
169
- if (visited.has(candidate.id)) {
170
- continue;
171
- }
172
- const cluster = candidates.filter((entry) => !visited.has(entry.id) && isSimilar(candidate.title, entry.title));
173
- for (const entry of cluster) {
174
- visited.add(entry.id);
175
- }
176
- if (cluster.length < 2) {
177
- continue;
178
- }
179
- const [keeper] = cluster.sort(compareDecisionKeeper);
180
- if (!keeper) {
181
- continue;
182
- }
183
- for (const entry of cluster) {
184
- if (entry.id === keeper.id) {
185
- continue;
186
- }
187
- modified += update.run(keeper.id, entry.id).changes;
188
- }
189
- }
190
- });
191
- tx();
192
- return passSummary("dedupDecisionsPass", candidates.length, modified);
193
- }
194
- catch (error) {
195
- return passSummary("dedupDecisionsPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
196
- }
197
- }
198
- function resolveSupersedeTerminal(startId, firstTargetId, supersededBy) {
199
- const seen = new Set([startId]);
200
- let current = firstTargetId;
201
- while (true) {
202
- if (seen.has(current)) {
203
- return null;
204
- }
205
- seen.add(current);
206
- const next = supersededBy.get(current);
207
- if (next === undefined) {
208
- return firstTargetId;
209
- }
210
- if (next === null) {
211
- return current;
212
- }
213
- current = next;
214
- }
215
- }
216
- export function compactSupersedeChainsPass(scopeId) {
217
- try {
218
- const db = getDb();
219
- const observationRows = db.prepare(`
220
- SELECT id, superseded_by
221
- FROM mem_observations
222
- WHERE scope_id = ?
223
- AND superseded_by IS NOT NULL
224
- ORDER BY id ASC
225
- `).all(scopeId);
226
- const decisionRows = db.prepare(`
227
- SELECT id, superseded_by
228
- FROM mem_decisions
229
- WHERE scope_id = ?
230
- AND superseded_by IS NOT NULL
231
- ORDER BY id ASC
232
- `).all(scopeId);
233
- let modified = 0;
234
- const tx = db.transaction(() => {
235
- for (const { table, rows } of [
236
- { table: "mem_observations", rows: observationRows },
237
- { table: "mem_decisions", rows: decisionRows },
238
- ]) {
239
- const targetIds = [...new Set(rows.map((row) => row.superseded_by))];
240
- const activeTargets = targetIds.length === 0
241
- ? undefined
242
- : db.prepare(`
243
- SELECT id, superseded_by
244
- FROM ${table}
245
- WHERE scope_id = ?
246
- AND id IN (${targetIds.map(() => "?").join(",")})
247
- `);
248
- const targets = targetIds.length === 0
249
- ? []
250
- : activeTargets.all(scopeId, ...targetIds);
251
- const supersededBy = new Map();
252
- for (const row of rows) {
253
- supersededBy.set(row.id, row.superseded_by);
254
- }
255
- for (const target of targets) {
256
- supersededBy.set(target.id, target.superseded_by);
257
- }
258
- const update = db.prepare(`UPDATE ${table} SET superseded_by = ? WHERE id = ? AND scope_id = ?`);
259
- for (const row of rows) {
260
- const terminal = resolveSupersedeTerminal(row.id, row.superseded_by, supersededBy);
261
- if (terminal !== null && terminal !== row.superseded_by) {
262
- modified += update.run(terminal, row.id, scopeId).changes;
263
- supersededBy.set(row.id, terminal);
264
- }
265
- }
266
- }
267
- });
268
- tx();
269
- return passSummary("compactSupersedeChainsPass", observationRows.length + decisionRows.length, modified);
270
- }
271
- catch (error) {
272
- return passSummary("compactSupersedeChainsPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
273
- }
274
- }
275
- export function orphanCleanupPass(scopeId) {
276
- try {
277
- const db = getDb();
278
- const orphanIds = db.prepare(`
279
- SELECT o.id
280
- FROM mem_observations o
281
- LEFT JOIN mem_entities e ON e.id = o.entity_id
282
- WHERE o.scope_id = ?
283
- AND o.entity_id IS NOT NULL
284
- AND e.id IS NULL
285
- ORDER BY o.id ASC
286
- `).all(scopeId);
287
- const tx = db.transaction(() => db.prepare(`
288
- UPDATE mem_observations
289
- SET entity_id = NULL
290
- WHERE scope_id = ?
291
- AND entity_id IS NOT NULL
292
- AND NOT EXISTS (SELECT 1 FROM mem_entities e WHERE e.id = mem_observations.entity_id)
293
- `).run(scopeId).changes);
294
- const modified = tx();
295
- if (modified > 0) {
296
- log.info({ scope_id: scopeId, count: modified }, "memory.housekeeping.orphan_cleanup");
297
- }
298
- return passSummary("orphanCleanupPass", orphanIds.length, modified);
299
- }
300
- catch (error) {
301
- return passSummary("orphanCleanupPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
302
- }
303
- }
304
- export function decayPass(scopeId) {
305
- try {
306
- const db = getDb();
307
- const candidateIds = db.prepare(`
308
- SELECT id
309
- FROM mem_observations
310
- WHERE scope_id = ?
311
- AND superseded_by IS NULL
312
- AND archived_at IS NULL
313
- AND confidence < 0.3
314
- AND datetime(created_at) < datetime('now', ?)
315
- ORDER BY id ASC
316
- `).all(scopeId, `-${config.memoryDecayDays} days`);
317
- const tx = db.transaction(() => db.prepare(`
318
- UPDATE mem_observations
319
- SET archived_at = CURRENT_TIMESTAMP
320
- WHERE scope_id = ?
321
- AND superseded_by IS NULL
322
- AND archived_at IS NULL
323
- AND confidence < 0.3
324
- AND datetime(created_at) < datetime('now', ?)
325
- `).run(scopeId, `-${config.memoryDecayDays} days`).changes);
326
- const modified = tx();
327
- return passSummary("decayPass", candidateIds.length, modified);
328
- }
329
- catch (error) {
330
- return passSummary("decayPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
331
- }
332
- }
333
- export function compactInboxPass() {
334
- try {
335
- const db = getDb();
336
- const candidateIds = db.prepare(`
337
- SELECT id
338
- FROM mem_inbox
339
- WHERE status IN ('accepted', 'rejected')
340
- AND resolved_at IS NOT NULL
341
- AND datetime(resolved_at) < datetime('now', ?)
342
- ORDER BY id ASC
343
- `).all(`-${config.memoryInboxRetentionDays} days`);
344
- const tx = db.transaction(() => db.prepare(`
345
- DELETE FROM mem_inbox
346
- WHERE status IN ('accepted', 'rejected')
347
- AND resolved_at IS NOT NULL
348
- AND datetime(resolved_at) < datetime('now', ?)
349
- `).run(`-${config.memoryInboxRetentionDays} days`).changes);
350
- const modified = tx();
351
- return passSummary("compactInboxPass", candidateIds.length, modified);
352
- }
353
- catch (error) {
354
- return passSummary("compactInboxPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
355
- }
356
- }
357
- function resolveScopeIds(input) {
358
- if (input?.scopeIds && input.scopeIds.length > 0) {
359
- return [...new Set(input.scopeIds)];
360
- }
361
- if (input?.allScopes) {
362
- return listScopes().filter((scope) => scope.active).map((scope) => scope.id);
363
- }
364
- const activeScope = getActiveScope();
365
- return activeScope ? [activeScope.id] : [];
366
- }
367
- function getInFlightScopes(pass) {
368
- let scopes = inFlightScopesByPass.get(pass);
369
- if (!scopes) {
370
- scopes = new Set();
371
- inFlightScopesByPass.set(pass, scopes);
372
- }
373
- return scopes;
374
- }
375
- function getReservedPassScopes(scopeIds, passes) {
376
- const reserved = [];
377
- for (const pass of passes) {
378
- if (pass === "compact_inbox") {
379
- reserved.push({ pass, scope: GLOBAL_PASS_SCOPE });
380
- continue;
381
- }
382
- for (const scopeId of scopeIds) {
383
- reserved.push({ pass, scope: scopeId });
384
- }
385
- }
386
- return reserved;
387
- }
388
- function reservePassScopes(reserved) {
389
- if (reserved.some(({ pass, scope }) => getInFlightScopes(pass).has(scope))) {
390
- return false;
391
- }
392
- for (const { pass, scope } of reserved) {
393
- getInFlightScopes(pass).add(scope);
394
- }
395
- return true;
396
- }
397
- function releasePassScopes(reserved) {
398
- for (const { pass, scope } of reserved) {
399
- const scopes = inFlightScopesByPass.get(pass);
400
- scopes?.delete(scope);
401
- if (scopes && scopes.size === 0) {
402
- inFlightScopesByPass.delete(pass);
403
- }
404
- }
405
- }
406
- async function runPass(pass, scopeId) {
407
- switch (pass) {
408
- case "dedup_observations":
409
- return await Promise.resolve(dedupObservationsPass(scopeId));
410
- case "dedup_decisions":
411
- return await Promise.resolve(dedupDecisionsPass(scopeId));
412
- case "compact_supersede_chains":
413
- return await Promise.resolve(compactSupersedeChainsPass(scopeId));
414
- case "orphan_cleanup":
415
- return await Promise.resolve(orphanCleanupPass(scopeId));
416
- case "decay":
417
- return await Promise.resolve(decayPass(scopeId));
418
- case "compact_inbox":
419
- return await Promise.resolve(compactInboxPass());
420
- case "tiering":
421
- return await Promise.resolve(tieringPass(scopeId));
422
- }
423
- }
424
- async function runScopePasses(scopeId, passes) {
425
- const summaries = [];
426
- for (const pass of passes) {
427
- summaries.push(await runPass(pass, scopeId));
428
- }
429
- return summaries;
430
- }
431
- export function isHousekeepingInFlight(scopeIds, passes) {
432
- const normalizedPasses = passes?.map(normalizePassName) ?? PASS_ORDER;
433
- if (!scopeIds || scopeIds.length === 0) {
434
- return normalizedPasses.some((pass) => (inFlightScopesByPass.get(pass)?.size ?? 0) > 0);
435
- }
436
- const uniqueScopeIds = [...new Set(scopeIds)].sort((a, b) => a - b);
437
- return normalizedPasses.some((pass) => {
438
- const scopes = inFlightScopesByPass.get(pass);
439
- if (!scopes) {
440
- return false;
441
- }
442
- if (pass === "compact_inbox") {
443
- return scopes.has(GLOBAL_PASS_SCOPE);
444
- }
445
- return uniqueScopeIds.some((scopeId) => scopes.has(scopeId));
446
- });
447
- }
448
- export async function runHousekeeping(opts = {}) {
449
- const started = performance.now();
450
- const scopeIds = resolveScopeIds(opts).sort((a, b) => a - b);
451
- const passes = opts.passes?.length ? opts.passes.map(normalizePassName) : PASS_ORDER;
452
- const reservedPassScopes = getReservedPassScopes(scopeIds, passes);
453
- if (!reservePassScopes(reservedPassScopes)) {
454
- return {
455
- scopeIds,
456
- summaries: [passSummary("runHousekeeping", 0, 0, ["Housekeeping is already in flight for this scope/pass set."])],
457
- totalExamined: 0,
458
- totalModified: 0,
459
- durationMs: 0,
460
- };
461
- }
462
- const scopedPasses = passes.filter((pass) => pass !== "compact_inbox");
463
- const lockedScopeIds = scopedPasses.length > 0 ? scopeIds : [];
464
- if (lockedScopeIds.length > 0 && !tryAcquireScopeWriteLocks(lockedScopeIds)) {
465
- releasePassScopes(reservedPassScopes);
466
- return {
467
- scopeIds,
468
- summaries: [passSummary("runHousekeeping", 0, 0, ["Memory writes are already in flight for this scope."])],
469
- totalExamined: 0,
470
- totalModified: 0,
471
- durationMs: 0,
472
- };
473
- }
474
- try {
475
- const hasCompactInbox = passes.includes("compact_inbox");
476
- const summaries = (await Promise.all(scopeIds.map((scopeId) => runScopePasses(scopeId, scopedPasses)))).flat();
477
- if (hasCompactInbox) {
478
- summaries.push(await runPass("compact_inbox", undefined));
479
- }
480
- const totalExamined = summaries.reduce((sum, summary) => sum + summary.examined, 0);
481
- const totalModified = summaries.reduce((sum, summary) => sum + summary.modified, 0);
482
- const durationMs = Math.round(performance.now() - started);
483
- log.info({
484
- passes_run: summaries.map((summary) => summary.pass),
485
- total_examined: totalExamined,
486
- total_modified: totalModified,
487
- duration_ms: durationMs,
488
- scope_ids: scopeIds,
489
- }, "memory.housekeeping.run");
490
- return { scopeIds, summaries, totalExamined, totalModified, durationMs };
491
- }
492
- finally {
493
- releaseScopeWriteLocks(lockedScopeIds);
494
- releasePassScopes(reservedPassScopes);
495
- }
496
- }
497
- //# sourceMappingURL=housekeeping.js.map