chapterhouse 0.13.1 → 0.14.1

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 (132) 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 +13 -10
  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 +92 -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/memory-assets/domain-skill.md +38 -0
  60. package/memory-assets/seed/cog-meta/improvements.md +8 -0
  61. package/memory-assets/seed/cog-meta/patterns.md +5 -0
  62. package/memory-assets/seed/cog-meta/reflect-cursor.md +4 -0
  63. package/memory-assets/seed/cog-meta/scenario-calibration.md +14 -0
  64. package/memory-assets/seed/cog-meta/self-observations.md +4 -0
  65. package/memory-assets/seed/domains.yml +19 -0
  66. package/memory-assets/seed/glacier/index.md +6 -0
  67. package/memory-assets/seed/hot-memory.md +5 -0
  68. package/memory-assets/seed/link-index.md +6 -0
  69. package/memory-assets/system-instructions.md +214 -0
  70. package/memory-assets/templates/action-items.md +8 -0
  71. package/memory-assets/templates/entities.md +4 -0
  72. package/memory-assets/templates/generic.md +2 -0
  73. package/memory-assets/templates/hot-memory.md +4 -0
  74. package/memory-assets/templates/observations.md +4 -0
  75. package/package.json +2 -1
  76. package/skills/system/evolve/SKILL.md +131 -0
  77. package/skills/system/foresight/SKILL.md +116 -0
  78. package/skills/system/history/SKILL.md +58 -0
  79. package/skills/system/housekeeping/SKILL.md +185 -0
  80. package/skills/system/reflect/SKILL.md +214 -0
  81. package/skills/system/scenario/SKILL.md +198 -0
  82. package/skills/system/setup/SKILL.md +113 -0
  83. package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
  84. package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
  85. package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
  86. package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
  87. package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
  88. package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
  89. package/web/dist/index.html +1 -1
  90. package/dist/api/routes/memory.js +0 -475
  91. package/dist/api/routes/memory.test.js +0 -108
  92. package/dist/copilot/tools/memory.js +0 -678
  93. package/dist/copilot/tools.memory.test.js +0 -590
  94. package/dist/memory/action-items.js +0 -100
  95. package/dist/memory/action-items.test.js +0 -83
  96. package/dist/memory/active-scope.js +0 -78
  97. package/dist/memory/active-scope.test.js +0 -80
  98. package/dist/memory/checkpoint-prompt.js +0 -71
  99. package/dist/memory/checkpoint.js +0 -274
  100. package/dist/memory/checkpoint.test.js +0 -275
  101. package/dist/memory/decisions.js +0 -54
  102. package/dist/memory/decisions.test.js +0 -92
  103. package/dist/memory/entities.js +0 -70
  104. package/dist/memory/entities.test.js +0 -65
  105. package/dist/memory/eot.js +0 -459
  106. package/dist/memory/eot.test.js +0 -949
  107. package/dist/memory/hooks.js +0 -149
  108. package/dist/memory/hooks.test.js +0 -325
  109. package/dist/memory/hot-tier.js +0 -283
  110. package/dist/memory/hot-tier.test.js +0 -275
  111. package/dist/memory/housekeeping-scheduler.js +0 -187
  112. package/dist/memory/housekeeping-scheduler.test.js +0 -236
  113. package/dist/memory/housekeeping.js +0 -497
  114. package/dist/memory/housekeeping.test.js +0 -410
  115. package/dist/memory/inbox.js +0 -83
  116. package/dist/memory/inbox.test.js +0 -178
  117. package/dist/memory/migration.js +0 -244
  118. package/dist/memory/migration.test.js +0 -108
  119. package/dist/memory/observations.js +0 -46
  120. package/dist/memory/observations.test.js +0 -86
  121. package/dist/memory/recall.js +0 -269
  122. package/dist/memory/recall.test.js +0 -265
  123. package/dist/memory/reflect.js +0 -273
  124. package/dist/memory/reflect.test.js +0 -256
  125. package/dist/memory/scope-lock.js +0 -26
  126. package/dist/memory/scope-lock.test.js +0 -118
  127. package/dist/memory/scopes.js +0 -89
  128. package/dist/memory/scopes.test.js +0 -176
  129. package/dist/memory/tiering.js +0 -223
  130. package/dist/memory/tiering.test.js +0 -323
  131. package/dist/memory/types.js +0 -2
  132. 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