chapterhouse 0.13.1 → 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.
- package/dist/api/route-coverage.test.js +1 -3
- package/dist/api/server.js +0 -2
- package/dist/api/server.test.js +0 -281
- package/dist/config.js +3 -85
- package/dist/config.test.js +5 -123
- package/dist/copilot/agents.js +13 -10
- package/dist/copilot/agents.test.js +10 -11
- package/dist/copilot/memory-coordinator.js +12 -227
- package/dist/copilot/memory-coordinator.test.js +31 -250
- package/dist/copilot/orchestrator.js +8 -66
- package/dist/copilot/orchestrator.test.js +9 -467
- package/dist/copilot/skills.js +15 -1
- package/dist/copilot/system-message.js +9 -15
- package/dist/copilot/system-message.test.js +9 -22
- package/dist/copilot/tools/index.js +3 -3
- package/dist/copilot/tools-deps.js +1 -1
- package/dist/copilot/tools.agent.test.js +6 -0
- package/dist/copilot/tools.inventory.test.js +1 -14
- package/dist/daemon.js +7 -9
- package/dist/memory/assets.js +33 -0
- package/dist/memory/domains.js +58 -0
- package/dist/memory/domains.test.js +47 -0
- package/dist/memory/git.js +66 -0
- package/dist/memory/git.test.js +32 -0
- package/dist/memory/history.js +19 -0
- package/dist/memory/hottier.js +32 -0
- package/dist/memory/hottier.test.js +33 -0
- package/dist/memory/index.js +5 -13
- package/dist/memory/instructions.js +17 -0
- package/dist/memory/manager.js +84 -0
- package/dist/memory/markdown.js +78 -0
- package/dist/memory/markdown.test.js +42 -0
- package/dist/memory/mutex.js +18 -0
- package/dist/memory/path-guard.js +26 -0
- package/dist/memory/path-guard.test.js +27 -0
- package/dist/memory/paths.js +12 -0
- package/dist/memory/reconcile.js +75 -0
- package/dist/memory/reconcile.test.js +50 -0
- package/dist/memory/scaffold.js +37 -0
- package/dist/memory/scaffold.test.js +52 -0
- package/dist/memory/tools/commit-wrapper.js +32 -0
- package/dist/memory/tools/domains.js +73 -0
- package/dist/memory/tools/domains.test.js +66 -0
- package/dist/memory/tools/git.js +52 -0
- package/dist/memory/tools/index.js +25 -0
- package/dist/memory/tools/read.js +101 -0
- package/dist/memory/tools/read.test.js +69 -0
- package/dist/memory/tools/search.js +103 -0
- package/dist/memory/tools/search.test.js +63 -0
- package/dist/memory/tools/sessions.js +45 -0
- package/dist/memory/tools/sessions.test.js +74 -0
- package/dist/memory/tools/shared.js +7 -0
- package/dist/memory/tools/write.js +116 -0
- package/dist/memory/tools/write.test.js +107 -0
- package/dist/memory/walk.js +39 -0
- package/dist/store/repositories/sessions.js +40 -0
- package/dist/wiki/consolidation.js +3 -31
- package/dist/wiki/consolidation.test.js +0 -19
- package/package.json +1 -1
- package/skills/system/evolve/SKILL.md +131 -0
- package/skills/system/foresight/SKILL.md +116 -0
- package/skills/system/history/SKILL.md +58 -0
- package/skills/system/housekeeping/SKILL.md +185 -0
- package/skills/system/reflect/SKILL.md +214 -0
- package/skills/system/scenario/SKILL.md +198 -0
- package/skills/system/setup/SKILL.md +113 -0
- package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
- package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
- package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
- package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
- package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
- package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
- package/web/dist/index.html +1 -1
- package/dist/api/routes/memory.js +0 -475
- package/dist/api/routes/memory.test.js +0 -108
- package/dist/copilot/tools/memory.js +0 -678
- package/dist/copilot/tools.memory.test.js +0 -590
- package/dist/memory/action-items.js +0 -100
- package/dist/memory/action-items.test.js +0 -83
- package/dist/memory/active-scope.js +0 -78
- package/dist/memory/active-scope.test.js +0 -80
- package/dist/memory/checkpoint-prompt.js +0 -71
- package/dist/memory/checkpoint.js +0 -274
- package/dist/memory/checkpoint.test.js +0 -275
- package/dist/memory/decisions.js +0 -54
- package/dist/memory/decisions.test.js +0 -92
- package/dist/memory/entities.js +0 -70
- package/dist/memory/entities.test.js +0 -65
- package/dist/memory/eot.js +0 -459
- package/dist/memory/eot.test.js +0 -949
- package/dist/memory/hooks.js +0 -149
- package/dist/memory/hooks.test.js +0 -325
- package/dist/memory/hot-tier.js +0 -283
- package/dist/memory/hot-tier.test.js +0 -275
- package/dist/memory/housekeeping-scheduler.js +0 -187
- package/dist/memory/housekeeping-scheduler.test.js +0 -236
- package/dist/memory/housekeeping.js +0 -497
- package/dist/memory/housekeeping.test.js +0 -410
- package/dist/memory/inbox.js +0 -83
- package/dist/memory/inbox.test.js +0 -178
- package/dist/memory/migration.js +0 -244
- package/dist/memory/migration.test.js +0 -108
- package/dist/memory/observations.js +0 -46
- package/dist/memory/observations.test.js +0 -86
- package/dist/memory/recall.js +0 -269
- package/dist/memory/recall.test.js +0 -265
- package/dist/memory/reflect.js +0 -273
- package/dist/memory/reflect.test.js +0 -256
- package/dist/memory/scope-lock.js +0 -26
- package/dist/memory/scope-lock.test.js +0 -118
- package/dist/memory/scopes.js +0 -89
- package/dist/memory/scopes.test.js +0 -176
- package/dist/memory/tiering.js +0 -223
- package/dist/memory/tiering.test.js +0 -323
- package/dist/memory/types.js +0 -2
- package/web/dist/assets/index-gAvLNEvJ.js.map +0 -1
package/dist/memory/scopes.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { getDb } from "../store/db.js";
|
|
2
|
-
function parseKeywords(raw, scopeSlug) {
|
|
3
|
-
const parsed = JSON.parse(raw);
|
|
4
|
-
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
|
|
5
|
-
throw new Error(`Invalid mem_scopes.keywords payload for scope '${scopeSlug}'.`);
|
|
6
|
-
}
|
|
7
|
-
return parsed;
|
|
8
|
-
}
|
|
9
|
-
function serializeKeywords(keywords) {
|
|
10
|
-
if (!Array.isArray(keywords) || keywords.some((keyword) => typeof keyword !== "string")) {
|
|
11
|
-
throw new Error("Scope keywords must be an array of strings.");
|
|
12
|
-
}
|
|
13
|
-
return JSON.stringify(keywords);
|
|
14
|
-
}
|
|
15
|
-
function toScope(row) {
|
|
16
|
-
return {
|
|
17
|
-
id: row.id,
|
|
18
|
-
slug: row.slug,
|
|
19
|
-
title: row.title,
|
|
20
|
-
description: row.description,
|
|
21
|
-
keywords: parseKeywords(row.keywords, row.slug),
|
|
22
|
-
active: row.active === 1,
|
|
23
|
-
createdAt: row.created_at,
|
|
24
|
-
updatedAt: row.updated_at,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function getScopeRow(idOrSlug) {
|
|
28
|
-
const db = getDb();
|
|
29
|
-
if (typeof idOrSlug === "number") {
|
|
30
|
-
return db.prepare(`
|
|
31
|
-
SELECT id, slug, title, description, keywords, active, created_at, updated_at
|
|
32
|
-
FROM mem_scopes
|
|
33
|
-
WHERE id = ?
|
|
34
|
-
`).get(idOrSlug);
|
|
35
|
-
}
|
|
36
|
-
return db.prepare(`
|
|
37
|
-
SELECT id, slug, title, description, keywords, active, created_at, updated_at
|
|
38
|
-
FROM mem_scopes
|
|
39
|
-
WHERE slug = ?
|
|
40
|
-
`).get(idOrSlug);
|
|
41
|
-
}
|
|
42
|
-
export function listScopes() {
|
|
43
|
-
const db = getDb();
|
|
44
|
-
const rows = db.prepare(`
|
|
45
|
-
SELECT id, slug, title, description, keywords, active, created_at, updated_at
|
|
46
|
-
FROM mem_scopes
|
|
47
|
-
ORDER BY slug
|
|
48
|
-
`).all();
|
|
49
|
-
return rows.map(toScope);
|
|
50
|
-
}
|
|
51
|
-
export function getScope(idOrSlug) {
|
|
52
|
-
const row = getScopeRow(idOrSlug);
|
|
53
|
-
return row ? toScope(row) : undefined;
|
|
54
|
-
}
|
|
55
|
-
export function createScope(input) {
|
|
56
|
-
const db = getDb();
|
|
57
|
-
const result = db.prepare(`
|
|
58
|
-
INSERT INTO mem_scopes (slug, title, description, keywords, active, created_at, updated_at)
|
|
59
|
-
VALUES (?, ?, ?, ?, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
60
|
-
`).run(input.slug, input.title, input.description, serializeKeywords(input.keywords));
|
|
61
|
-
return getScope(Number(result.lastInsertRowid));
|
|
62
|
-
}
|
|
63
|
-
export function updateScope(id, patch) {
|
|
64
|
-
const existing = getScope(id);
|
|
65
|
-
if (!existing) {
|
|
66
|
-
throw new Error(`Unknown scope id '${id}'.`);
|
|
67
|
-
}
|
|
68
|
-
const nextTitle = patch.title ?? existing.title;
|
|
69
|
-
const nextDescription = patch.description ?? existing.description;
|
|
70
|
-
const nextKeywords = patch.keywords ?? existing.keywords;
|
|
71
|
-
getDb().prepare(`
|
|
72
|
-
UPDATE mem_scopes
|
|
73
|
-
SET title = ?, description = ?, keywords = ?, updated_at = CURRENT_TIMESTAMP
|
|
74
|
-
WHERE id = ?
|
|
75
|
-
`).run(nextTitle, nextDescription, serializeKeywords(nextKeywords), id);
|
|
76
|
-
return getScope(id);
|
|
77
|
-
}
|
|
78
|
-
export function deactivateScope(id) {
|
|
79
|
-
const result = getDb().prepare(`
|
|
80
|
-
UPDATE mem_scopes
|
|
81
|
-
SET active = 0, updated_at = CURRENT_TIMESTAMP
|
|
82
|
-
WHERE id = ?
|
|
83
|
-
`).run(id);
|
|
84
|
-
if (result.changes === 0) {
|
|
85
|
-
throw new Error(`Unknown scope id '${id}'.`);
|
|
86
|
-
}
|
|
87
|
-
return getScope(id);
|
|
88
|
-
}
|
|
89
|
-
//# sourceMappingURL=scopes.js.map
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import test from "node:test";
|
|
5
|
-
const repoRoot = process.cwd();
|
|
6
|
-
const sandboxRoot = join(repoRoot, ".test-work", `memory-scopes-${process.pid}`);
|
|
7
|
-
const chapterhouseHome = join(sandboxRoot, ".chapterhouse");
|
|
8
|
-
process.env.CHAPTERHOUSE_HOME = sandboxRoot;
|
|
9
|
-
async function loadModules() {
|
|
10
|
-
const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
|
|
11
|
-
const memoryModule = await import(new URL("./scopes.js", import.meta.url).href);
|
|
12
|
-
return { dbModule, memoryModule };
|
|
13
|
-
}
|
|
14
|
-
function resetSandbox() {
|
|
15
|
-
mkdirSync(join(repoRoot, ".test-work"), { recursive: true });
|
|
16
|
-
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
17
|
-
mkdirSync(chapterhouseHome, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
test.beforeEach(async () => {
|
|
20
|
-
const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
|
|
21
|
-
dbModule.closeDb();
|
|
22
|
-
resetSandbox();
|
|
23
|
-
});
|
|
24
|
-
test.after(async () => {
|
|
25
|
-
const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
|
|
26
|
-
dbModule.closeDb();
|
|
27
|
-
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
28
|
-
});
|
|
29
|
-
test("getDb creates memory tables and indexes", async () => {
|
|
30
|
-
const { dbModule } = await loadModules();
|
|
31
|
-
try {
|
|
32
|
-
const db = dbModule.getDb();
|
|
33
|
-
const tables = new Set(db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'mem_%'`).all()
|
|
34
|
-
.map((row) => row.name));
|
|
35
|
-
const indexes = new Set(db.prepare(`SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE 'mem_%'`).all()
|
|
36
|
-
.map((row) => row.name));
|
|
37
|
-
for (const name of [
|
|
38
|
-
"mem_scopes",
|
|
39
|
-
"mem_entities",
|
|
40
|
-
"mem_observations",
|
|
41
|
-
"mem_decisions",
|
|
42
|
-
"mem_inbox",
|
|
43
|
-
]) {
|
|
44
|
-
assert.equal(tables.has(name), true, `expected memory table ${name}`);
|
|
45
|
-
}
|
|
46
|
-
for (const name of [
|
|
47
|
-
"mem_scopes_slug_idx",
|
|
48
|
-
"mem_entities_scope_kind_idx",
|
|
49
|
-
"mem_observations_scope_idx",
|
|
50
|
-
"mem_decisions_scope_idx",
|
|
51
|
-
"mem_inbox_status_idx",
|
|
52
|
-
]) {
|
|
53
|
-
assert.equal(indexes.has(name), true, `expected memory index ${name}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
dbModule.closeDb();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
test("getDb seeds canonical memory scopes on first run", async () => {
|
|
61
|
-
const { dbModule } = await loadModules();
|
|
62
|
-
try {
|
|
63
|
-
const db = dbModule.getDb();
|
|
64
|
-
const rows = db.prepare(`
|
|
65
|
-
SELECT slug, title, description, keywords, active
|
|
66
|
-
FROM mem_scopes
|
|
67
|
-
ORDER BY slug
|
|
68
|
-
`).all();
|
|
69
|
-
assert.deepEqual(rows, [
|
|
70
|
-
{
|
|
71
|
-
slug: "chapterhouse",
|
|
72
|
-
title: "Chapterhouse",
|
|
73
|
-
description: "Chapterhouse codebase, conventions, decisions, gotchas",
|
|
74
|
-
keywords: JSON.stringify(["chapterhouse", "this repo", "this project", "the daemon"]),
|
|
75
|
-
active: 1,
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
slug: "global",
|
|
79
|
-
title: "Global",
|
|
80
|
-
description: "Cross-cutting facts that apply everywhere",
|
|
81
|
-
keywords: JSON.stringify(["everywhere", "general"]),
|
|
82
|
-
active: 1,
|
|
83
|
-
},
|
|
84
|
-
]);
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
dbModule.closeDb();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
test("memory schema initialization is idempotent", async () => {
|
|
91
|
-
const { dbModule } = await loadModules();
|
|
92
|
-
try {
|
|
93
|
-
dbModule.getDb();
|
|
94
|
-
dbModule.closeDb();
|
|
95
|
-
const reopened = await loadModules();
|
|
96
|
-
const db = reopened.dbModule.getDb();
|
|
97
|
-
const counts = db.prepare(`
|
|
98
|
-
SELECT slug, COUNT(*) AS count
|
|
99
|
-
FROM mem_scopes
|
|
100
|
-
GROUP BY slug
|
|
101
|
-
ORDER BY slug
|
|
102
|
-
`).all();
|
|
103
|
-
assert.deepEqual(counts, [
|
|
104
|
-
{ slug: "chapterhouse", count: 1 },
|
|
105
|
-
{ slug: "global", count: 1 },
|
|
106
|
-
]);
|
|
107
|
-
reopened.dbModule.closeDb();
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
dbModule.closeDb();
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
test("scope CRUD creates, reads, lists, updates, deactivates, and preserves keywords", async () => {
|
|
114
|
-
const { dbModule, memoryModule } = await loadModules();
|
|
115
|
-
try {
|
|
116
|
-
const created = memoryModule.createScope({
|
|
117
|
-
slug: "docs-site",
|
|
118
|
-
title: "Docs Site",
|
|
119
|
-
description: "Documentation publishing and content workflows",
|
|
120
|
-
keywords: ["docs", "content", "publish"],
|
|
121
|
-
});
|
|
122
|
-
assert.equal(created.slug, "docs-site");
|
|
123
|
-
assert.equal(created.title, "Docs Site");
|
|
124
|
-
assert.equal(created.description, "Documentation publishing and content workflows");
|
|
125
|
-
assert.deepEqual(created.keywords, ["docs", "content", "publish"]);
|
|
126
|
-
assert.equal(created.active, true);
|
|
127
|
-
assert.deepEqual(memoryModule.getScope("docs-site"), created);
|
|
128
|
-
assert.deepEqual(memoryModule.getScope(created.id), created);
|
|
129
|
-
const listed = memoryModule.listScopes();
|
|
130
|
-
assert.equal(listed.some((scope) => scope.slug === "docs-site"), true);
|
|
131
|
-
await new Promise((resolve) => setTimeout(resolve, 1_100));
|
|
132
|
-
const updated = memoryModule.updateScope(created.id, {
|
|
133
|
-
title: "Docs Platform",
|
|
134
|
-
description: "Docs platform and release content",
|
|
135
|
-
keywords: ["docs", "release", "guides"],
|
|
136
|
-
});
|
|
137
|
-
assert.equal(updated.id, created.id);
|
|
138
|
-
assert.equal(updated.slug, "docs-site");
|
|
139
|
-
assert.equal(updated.title, "Docs Platform");
|
|
140
|
-
assert.equal(updated.description, "Docs platform and release content");
|
|
141
|
-
assert.deepEqual(updated.keywords, ["docs", "release", "guides"]);
|
|
142
|
-
assert.notEqual(updated.updatedAt, created.updatedAt);
|
|
143
|
-
const inactive = memoryModule.deactivateScope(created.id);
|
|
144
|
-
assert.equal(inactive.active, false);
|
|
145
|
-
assert.deepEqual(inactive.keywords, ["docs", "release", "guides"]);
|
|
146
|
-
assert.deepEqual(memoryModule.getScope(created.id), inactive);
|
|
147
|
-
const storedKeywords = dbModule.getDb()
|
|
148
|
-
.prepare(`SELECT keywords FROM mem_scopes WHERE id = ?`)
|
|
149
|
-
.get(created.id).keywords;
|
|
150
|
-
assert.deepEqual(JSON.parse(storedKeywords), ["docs", "release", "guides"]);
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
dbModule.closeDb();
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
test("createScope enforces unique slugs", async () => {
|
|
157
|
-
const { dbModule, memoryModule } = await loadModules();
|
|
158
|
-
try {
|
|
159
|
-
memoryModule.createScope({
|
|
160
|
-
slug: "shared",
|
|
161
|
-
title: "Shared",
|
|
162
|
-
description: "Shared scope",
|
|
163
|
-
keywords: ["shared"],
|
|
164
|
-
});
|
|
165
|
-
assert.throws(() => memoryModule.createScope({
|
|
166
|
-
slug: "shared",
|
|
167
|
-
title: "Shared Again",
|
|
168
|
-
description: "Duplicate scope",
|
|
169
|
-
keywords: ["duplicate"],
|
|
170
|
-
}), /UNIQUE|constraint/i);
|
|
171
|
-
}
|
|
172
|
-
finally {
|
|
173
|
-
dbModule.closeDb();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
//# sourceMappingURL=scopes.test.js.map
|
package/dist/memory/tiering.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { config } from "../config.js";
|
|
2
|
-
import { getDb } from "../store/db.js";
|
|
3
|
-
import { childLogger } from "../util/logger.js";
|
|
4
|
-
const log = childLogger("memory.tiering");
|
|
5
|
-
const TABLES = {
|
|
6
|
-
observation: "mem_observations",
|
|
7
|
-
decision: "mem_decisions",
|
|
8
|
-
entity: "mem_entities",
|
|
9
|
-
action_item: "mem_action_items",
|
|
10
|
-
};
|
|
11
|
-
function dbTable(table) {
|
|
12
|
-
return TABLES[table];
|
|
13
|
-
}
|
|
14
|
-
function passSummary(pass, examined = 0, modified = 0, errors = []) {
|
|
15
|
-
return { pass, examined, modified, errors };
|
|
16
|
-
}
|
|
17
|
-
function isRecent(value, days) {
|
|
18
|
-
if (!value) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
const parsed = new Date(value.includes("T") ? value : value.replace(" ", "T"));
|
|
22
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
return parsed.getTime() >= Date.now() - days * 24 * 60 * 60 * 1000;
|
|
26
|
-
}
|
|
27
|
-
export function inferTierFromSignals(row) {
|
|
28
|
-
if (row.archived_at || row.superseded_by) {
|
|
29
|
-
return "cold";
|
|
30
|
-
}
|
|
31
|
-
if (row.tier === "glacier") {
|
|
32
|
-
return "cold";
|
|
33
|
-
}
|
|
34
|
-
if (row.confidence !== undefined && row.confidence !== null && row.confidence > 0.7) {
|
|
35
|
-
const timestamp = row.created_at ?? row.decided_at ?? row.updated_at;
|
|
36
|
-
if (isRecent(timestamp, 30)) {
|
|
37
|
-
return "hot";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return row.tier === "hot" || row.tier === "cold" ? row.tier : "warm";
|
|
41
|
-
}
|
|
42
|
-
function setTier(table, id, tier, reason, manual) {
|
|
43
|
-
const result = getDb().prepare(`
|
|
44
|
-
UPDATE ${dbTable(table)}
|
|
45
|
-
SET tier = ?, tier_reason = ?, tier_pinned_at = ${manual ? "CURRENT_TIMESTAMP" : "tier_pinned_at"}
|
|
46
|
-
WHERE id = ?
|
|
47
|
-
`).run(tier, reason, id);
|
|
48
|
-
if (result.changes === 0) {
|
|
49
|
-
throw new Error(`Unknown memory ${table} id '${id}'.`);
|
|
50
|
-
}
|
|
51
|
-
log.info({ table, id, tier, reason, manual }, "memory.tier.set");
|
|
52
|
-
}
|
|
53
|
-
export function promoteToHot(table, id, reason) {
|
|
54
|
-
setTier(table, id, "hot", reason, true);
|
|
55
|
-
}
|
|
56
|
-
export function demoteToWarm(table, id, reason) {
|
|
57
|
-
setTier(table, id, "warm", reason, true);
|
|
58
|
-
}
|
|
59
|
-
export function demoteToCold(table, id, reason) {
|
|
60
|
-
setTier(table, id, "cold", reason, true);
|
|
61
|
-
}
|
|
62
|
-
function updateTier(sql, params) {
|
|
63
|
-
return getDb().prepare(sql).run(...params).changes;
|
|
64
|
-
}
|
|
65
|
-
export function tieringPass(scopeId) {
|
|
66
|
-
if (!config.memoryTieringEnabled) {
|
|
67
|
-
return passSummary("tieringPass");
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
const db = getDb();
|
|
71
|
-
const counts = db.prepare(`
|
|
72
|
-
SELECT
|
|
73
|
-
(SELECT COUNT(*) FROM mem_observations WHERE scope_id = ?) AS observations,
|
|
74
|
-
(SELECT COUNT(*) FROM mem_decisions WHERE scope_id = ?) AS decisions,
|
|
75
|
-
(SELECT COUNT(*) FROM mem_entities WHERE scope_id = ?) AS entities,
|
|
76
|
-
(SELECT COUNT(*) FROM mem_action_items WHERE scope_id = ?) AS action_items
|
|
77
|
-
`).get(scopeId, scopeId, scopeId, scopeId);
|
|
78
|
-
let modified = 0;
|
|
79
|
-
const tx = db.transaction(() => {
|
|
80
|
-
modified += updateTier(`
|
|
81
|
-
UPDATE mem_observations
|
|
82
|
-
SET tier = 'cold', tier_reason = 'archived or superseded'
|
|
83
|
-
WHERE scope_id = ?
|
|
84
|
-
AND tier != 'cold'
|
|
85
|
-
AND tier_pinned_at IS NULL
|
|
86
|
-
AND (archived_at IS NOT NULL OR superseded_by IS NOT NULL)
|
|
87
|
-
`, [scopeId]);
|
|
88
|
-
modified += updateTier(`
|
|
89
|
-
UPDATE mem_decisions
|
|
90
|
-
SET tier = 'cold', tier_reason = 'archived or superseded'
|
|
91
|
-
WHERE scope_id = ?
|
|
92
|
-
AND tier != 'cold'
|
|
93
|
-
AND tier_pinned_at IS NULL
|
|
94
|
-
AND (archived_at IS NOT NULL OR superseded_by IS NOT NULL)
|
|
95
|
-
`, [scopeId]);
|
|
96
|
-
modified += updateTier(`
|
|
97
|
-
UPDATE mem_observations
|
|
98
|
-
SET tier = 'hot', tier_reason = 'referenced by recent entity decision'
|
|
99
|
-
WHERE scope_id = ?
|
|
100
|
-
AND tier != 'hot'
|
|
101
|
-
AND tier_pinned_at IS NULL
|
|
102
|
-
AND archived_at IS NULL
|
|
103
|
-
AND superseded_by IS NULL
|
|
104
|
-
AND entity_id IS NOT NULL
|
|
105
|
-
AND EXISTS (
|
|
106
|
-
SELECT 1
|
|
107
|
-
FROM mem_decisions d
|
|
108
|
-
WHERE d.scope_id = mem_observations.scope_id
|
|
109
|
-
AND d.entity_id = mem_observations.entity_id
|
|
110
|
-
AND d.archived_at IS NULL
|
|
111
|
-
AND d.superseded_by IS NULL
|
|
112
|
-
AND datetime(d.decided_at) >= datetime('now', '-7 days')
|
|
113
|
-
)
|
|
114
|
-
`, [scopeId]);
|
|
115
|
-
modified += updateTier(`
|
|
116
|
-
UPDATE mem_decisions
|
|
117
|
-
SET tier = 'hot', tier_reason = 'recent decision restatement'
|
|
118
|
-
WHERE scope_id = ?
|
|
119
|
-
AND tier != 'hot'
|
|
120
|
-
AND tier_pinned_at IS NULL
|
|
121
|
-
AND archived_at IS NULL
|
|
122
|
-
AND (
|
|
123
|
-
datetime(decided_at) >= datetime('now', '-7 days')
|
|
124
|
-
OR EXISTS (
|
|
125
|
-
SELECT 1
|
|
126
|
-
FROM mem_decisions old
|
|
127
|
-
WHERE old.superseded_by = mem_decisions.id
|
|
128
|
-
AND datetime(mem_decisions.decided_at) >= datetime('now', '-7 days')
|
|
129
|
-
)
|
|
130
|
-
)
|
|
131
|
-
`, [scopeId]);
|
|
132
|
-
modified += updateTier(`
|
|
133
|
-
UPDATE mem_entities
|
|
134
|
-
SET tier = 'hot', tier_reason = 'referenced by three recent observations'
|
|
135
|
-
WHERE scope_id = ?
|
|
136
|
-
AND tier != 'hot'
|
|
137
|
-
AND tier_pinned_at IS NULL
|
|
138
|
-
AND (
|
|
139
|
-
SELECT COUNT(*)
|
|
140
|
-
FROM mem_observations o
|
|
141
|
-
WHERE o.entity_id = mem_entities.id
|
|
142
|
-
AND o.archived_at IS NULL
|
|
143
|
-
AND o.superseded_by IS NULL
|
|
144
|
-
AND datetime(o.created_at) >= datetime('now', '-7 days')
|
|
145
|
-
) >= 3
|
|
146
|
-
`, [scopeId]);
|
|
147
|
-
modified += updateTier(`
|
|
148
|
-
UPDATE mem_observations
|
|
149
|
-
SET tier = 'warm', tier_reason = 'hot age threshold without recall'
|
|
150
|
-
WHERE scope_id = ?
|
|
151
|
-
AND tier = 'hot'
|
|
152
|
-
AND tier_pinned_at IS NULL
|
|
153
|
-
AND archived_at IS NULL
|
|
154
|
-
AND superseded_by IS NULL
|
|
155
|
-
AND last_recalled_at IS NULL
|
|
156
|
-
AND datetime(created_at) < datetime('now', ?)
|
|
157
|
-
`, [scopeId, `-${config.memoryHotAgeDays} days`]);
|
|
158
|
-
modified += updateTier(`
|
|
159
|
-
UPDATE mem_decisions
|
|
160
|
-
SET tier = 'warm', tier_reason = 'hot age threshold without recall'
|
|
161
|
-
WHERE scope_id = ?
|
|
162
|
-
AND tier = 'hot'
|
|
163
|
-
AND tier_pinned_at IS NULL
|
|
164
|
-
AND archived_at IS NULL
|
|
165
|
-
AND superseded_by IS NULL
|
|
166
|
-
AND last_recalled_at IS NULL
|
|
167
|
-
AND datetime(decided_at) < datetime('now', ?)
|
|
168
|
-
`, [scopeId, `-${config.memoryHotAgeDays} days`]);
|
|
169
|
-
modified += updateTier(`
|
|
170
|
-
UPDATE mem_action_items
|
|
171
|
-
SET tier = 'cold', tier_reason = 'resolved action item'
|
|
172
|
-
WHERE scope_id = ?
|
|
173
|
-
AND tier != 'cold'
|
|
174
|
-
AND tier_pinned_at IS NULL
|
|
175
|
-
AND status IN ('done', 'dropped')
|
|
176
|
-
`, [scopeId]);
|
|
177
|
-
modified += updateTier(`
|
|
178
|
-
UPDATE mem_action_items
|
|
179
|
-
SET tier = 'hot', tier_reason = 'open action item due soon'
|
|
180
|
-
WHERE scope_id = ?
|
|
181
|
-
AND tier != 'hot'
|
|
182
|
-
AND tier_pinned_at IS NULL
|
|
183
|
-
AND status = 'open'
|
|
184
|
-
AND due_at IS NOT NULL
|
|
185
|
-
AND datetime(due_at) <= datetime('now', '+7 days')
|
|
186
|
-
`, [scopeId]);
|
|
187
|
-
modified += updateTier(`
|
|
188
|
-
UPDATE mem_action_items
|
|
189
|
-
SET tier = 'warm', tier_reason = 'hot age threshold without recall'
|
|
190
|
-
WHERE scope_id = ?
|
|
191
|
-
AND tier = 'hot'
|
|
192
|
-
AND tier_pinned_at IS NULL
|
|
193
|
-
AND status = 'open'
|
|
194
|
-
AND last_recalled_at IS NULL
|
|
195
|
-
AND datetime(created_at) < datetime('now', ?)
|
|
196
|
-
`, [scopeId, `-${config.memoryHotAgeDays} days`]);
|
|
197
|
-
modified += updateTier(`
|
|
198
|
-
UPDATE mem_observations
|
|
199
|
-
SET tier = 'cold', tier_reason = 'stale low confidence'
|
|
200
|
-
WHERE scope_id = ?
|
|
201
|
-
AND tier = 'warm'
|
|
202
|
-
AND tier_pinned_at IS NULL
|
|
203
|
-
AND confidence < 0.3
|
|
204
|
-
AND datetime(created_at) < datetime('now', '-60 days')
|
|
205
|
-
`, [scopeId]);
|
|
206
|
-
modified += updateTier(`
|
|
207
|
-
UPDATE mem_entities
|
|
208
|
-
SET tier = 'cold', tier_reason = 'stale low confidence'
|
|
209
|
-
WHERE scope_id = ?
|
|
210
|
-
AND tier = 'warm'
|
|
211
|
-
AND tier_pinned_at IS NULL
|
|
212
|
-
AND confidence < 0.3
|
|
213
|
-
AND datetime(updated_at) < datetime('now', '-60 days')
|
|
214
|
-
`, [scopeId]);
|
|
215
|
-
});
|
|
216
|
-
tx();
|
|
217
|
-
return passSummary("tieringPass", counts.observations + counts.decisions + counts.entities + counts.action_items, modified);
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
return passSummary("tieringPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
//# sourceMappingURL=tiering.js.map
|