chapterhouse 0.4.3 → 0.5.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.
- package/dist/api/server.js +65 -2
- package/dist/api/server.test.js +63 -0
- package/dist/api/turn-sse.integration.test.js +12 -0
- package/dist/copilot/agents.js +13 -2
- package/dist/copilot/agents.test.js +43 -1
- package/dist/copilot/orchestrator.js +146 -29
- package/dist/copilot/orchestrator.test.js +232 -14
- package/dist/copilot/session-manager.js +11 -2
- package/dist/copilot/session-manager.test.js +25 -0
- package/dist/copilot/system-message.js +3 -3
- package/dist/copilot/system-message.test.js +10 -0
- package/dist/copilot/tools.agent.test.js +52 -4
- package/dist/copilot/tools.js +149 -13
- package/dist/copilot/tools.memory.test.js +139 -2
- package/dist/memory/active-scope.js +9 -0
- package/dist/memory/active-scope.test.js +7 -2
- package/dist/memory/eot.js +96 -8
- package/dist/memory/eot.test.js +186 -5
- package/dist/memory/hot-tier.test.js +14 -4
- package/dist/memory/housekeeping.test.js +20 -13
- package/dist/memory/index.js +1 -1
- package/dist/memory/scopes.test.js +0 -24
- package/dist/store/db.js +27 -19
- package/package.json +1 -1
- package/web/dist/assets/{index-D4-uRAi6.js → index-BfHqP3-C.js} +87 -87
- package/web/dist/assets/index-BfHqP3-C.js.map +1 -0
- package/web/dist/assets/index-_O6AoWOS.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BTI_m0OE.css +0 -10
- package/web/dist/assets/index-D4-uRAi6.js.map +0 -1
package/dist/memory/eot.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
const repoRoot = process.cwd();
|
|
@@ -15,7 +15,8 @@ async function loadModules(cacheBust = `${Date.now()}-${Math.random()}`) {
|
|
|
15
15
|
const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
|
|
16
16
|
const memoryModule = await import(new URL(`./index.js?case=${cacheBust}`, import.meta.url).href);
|
|
17
17
|
const eotModule = await import(new URL(`./eot.js?case=${cacheBust}`, import.meta.url).href);
|
|
18
|
-
|
|
18
|
+
const agentsModule = await import(new URL(`../copilot/agents.js?case=${cacheBust}`, import.meta.url).href);
|
|
19
|
+
return { dbModule, memoryModule, eotModule, agentsModule };
|
|
19
20
|
}
|
|
20
21
|
function getFunction(module, name) {
|
|
21
22
|
const value = module[name];
|
|
@@ -171,7 +172,6 @@ test("runEndOfTaskMemoryHook accepts action_item proposals into mem_action_items
|
|
|
171
172
|
payload: {
|
|
172
173
|
title: "Migrate feature ideas",
|
|
173
174
|
detail: "Move feature-ideas.md into mem_action_items.",
|
|
174
|
-
source: "subagent_proposal",
|
|
175
175
|
},
|
|
176
176
|
confidence: 0.9,
|
|
177
177
|
}));
|
|
@@ -189,11 +189,192 @@ test("runEndOfTaskMemoryHook accepts action_item proposals into mem_action_items
|
|
|
189
189
|
}),
|
|
190
190
|
});
|
|
191
191
|
const actionItems = listActionItems({ scope_id: chapterhouse.id });
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
const actionItem = actionItems.find((item) => item.title === "Migrate feature ideas");
|
|
193
|
+
assert.ok(actionItem);
|
|
194
|
+
assert.equal(actionItem.detail, "Move feature-ideas.md into mem_action_items.");
|
|
195
|
+
assert.equal(actionItem.status, "open");
|
|
196
|
+
assert.equal(actionItem.source, "subagent_proposal:coder");
|
|
194
197
|
const inbox = db.prepare(`SELECT status FROM mem_inbox WHERE id = ?`).get(Number(inserted.lastInsertRowid));
|
|
195
198
|
assert.equal(inbox.status, "accepted");
|
|
196
199
|
});
|
|
200
|
+
test("runEndOfTaskMemoryHook rejects invalid action_item proposals with a clear reason", async () => {
|
|
201
|
+
const { dbModule, memoryModule, eotModule } = await loadModules("action-item-invalid");
|
|
202
|
+
const db = dbModule.getDb();
|
|
203
|
+
const getScope = getFunction(memoryModule, "getScope");
|
|
204
|
+
const createScope = getFunction(memoryModule, "createScope");
|
|
205
|
+
const listActionItems = getFunction(memoryModule, "listActionItems");
|
|
206
|
+
const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
|
|
207
|
+
const chapterhouse = getScope("chapterhouse");
|
|
208
|
+
assert.ok(chapterhouse);
|
|
209
|
+
const inserted = db.prepare(`
|
|
210
|
+
INSERT INTO mem_inbox (scope_id, kind, payload, source_agent, source_task_id, status)
|
|
211
|
+
VALUES (?, 'memory_proposal', ?, 'coder', 'task-eot-action-item-invalid', 'pending')
|
|
212
|
+
`).run(chapterhouse.id, JSON.stringify({
|
|
213
|
+
kind: "action_item",
|
|
214
|
+
payload: {
|
|
215
|
+
detail: "A title is required before this can become an action item.",
|
|
216
|
+
},
|
|
217
|
+
confidence: 0.9,
|
|
218
|
+
}));
|
|
219
|
+
await runEndOfTaskMemoryHook({
|
|
220
|
+
taskId: "task-eot-action-item-invalid",
|
|
221
|
+
finalResult: "Completed and proposed a malformed follow-up.",
|
|
222
|
+
copilotClient: {},
|
|
223
|
+
callLLM: async () => JSON.stringify({
|
|
224
|
+
decisions: [{
|
|
225
|
+
proposal_id: Number(inserted.lastInsertRowid),
|
|
226
|
+
decision: "accept",
|
|
227
|
+
reason: "Concrete follow-up.",
|
|
228
|
+
}],
|
|
229
|
+
implicit_memories: [],
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
232
|
+
assert.deepEqual(listActionItems({ scope_id: chapterhouse.id }), []);
|
|
233
|
+
const inbox = db.prepare(`
|
|
234
|
+
SELECT status, resolution_reason, resolved_at
|
|
235
|
+
FROM mem_inbox
|
|
236
|
+
WHERE id = ?
|
|
237
|
+
`).get(Number(inserted.lastInsertRowid));
|
|
238
|
+
assert.equal(inbox.status, "rejected");
|
|
239
|
+
assert.match(inbox.resolution_reason ?? "", /title/i);
|
|
240
|
+
assert.ok(inbox.resolved_at);
|
|
241
|
+
});
|
|
242
|
+
test("runEndOfTaskMemoryHook rejects action_item proposals with ambiguous entity references", async () => {
|
|
243
|
+
const { dbModule, memoryModule, eotModule } = await loadModules("action-item-ambiguous-entity");
|
|
244
|
+
const db = dbModule.getDb();
|
|
245
|
+
const getScope = getFunction(memoryModule, "getScope");
|
|
246
|
+
const createScope = getFunction(memoryModule, "createScope");
|
|
247
|
+
const listActionItems = getFunction(memoryModule, "listActionItems");
|
|
248
|
+
const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
|
|
249
|
+
const chapterhouse = getScope("chapterhouse");
|
|
250
|
+
assert.ok(chapterhouse);
|
|
251
|
+
const inserted = db.prepare(`
|
|
252
|
+
INSERT INTO mem_inbox (scope_id, kind, payload, source_agent, source_task_id, status)
|
|
253
|
+
VALUES (?, 'memory_proposal', ?, 'coder', 'task-eot-action-item-ambiguous-entity', 'pending')
|
|
254
|
+
`).run(chapterhouse.id, JSON.stringify({
|
|
255
|
+
kind: "action_item",
|
|
256
|
+
payload: {
|
|
257
|
+
title: "Resolve ambiguous entity link",
|
|
258
|
+
entity_id: 1,
|
|
259
|
+
entity_name: "Bellonda",
|
|
260
|
+
entity_kind: "agent",
|
|
261
|
+
},
|
|
262
|
+
confidence: 0.9,
|
|
263
|
+
}));
|
|
264
|
+
await runEndOfTaskMemoryHook({
|
|
265
|
+
taskId: "task-eot-action-item-ambiguous-entity",
|
|
266
|
+
finalResult: "Completed and proposed an ambiguous follow-up.",
|
|
267
|
+
copilotClient: {},
|
|
268
|
+
callLLM: async () => JSON.stringify({
|
|
269
|
+
decisions: [{
|
|
270
|
+
proposal_id: Number(inserted.lastInsertRowid),
|
|
271
|
+
decision: "accept",
|
|
272
|
+
reason: "Concrete follow-up.",
|
|
273
|
+
}],
|
|
274
|
+
implicit_memories: [],
|
|
275
|
+
}),
|
|
276
|
+
});
|
|
277
|
+
assert.deepEqual(listActionItems({ scope_id: chapterhouse.id }), []);
|
|
278
|
+
const inbox = db.prepare(`
|
|
279
|
+
SELECT status, resolution_reason
|
|
280
|
+
FROM mem_inbox
|
|
281
|
+
WHERE id = ?
|
|
282
|
+
`).get(Number(inserted.lastInsertRowid));
|
|
283
|
+
assert.equal(inbox.status, "rejected");
|
|
284
|
+
assert.match(inbox.resolution_reason ?? "", /entity_id.*entity_name|entity_name.*entity_id/i);
|
|
285
|
+
});
|
|
286
|
+
test("runEndOfTaskMemoryHook falls back to the source agent bound scope for action_item proposals without scope_slug", async () => {
|
|
287
|
+
const home = process.env.CHAPTERHOUSE_HOME;
|
|
288
|
+
assert.ok(home, "test home should be set");
|
|
289
|
+
const { AGENTS_DIR: agentsDir } = await import("../paths.js");
|
|
290
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
291
|
+
writeFileSync(join(agentsDir, "bellonda.agent.md"), [
|
|
292
|
+
"---",
|
|
293
|
+
"name: Bellonda",
|
|
294
|
+
"description: Mentat of the infrastructure domain",
|
|
295
|
+
"model: claude-sonnet-4.6",
|
|
296
|
+
"persistent: true",
|
|
297
|
+
"scope: infra",
|
|
298
|
+
"---",
|
|
299
|
+
"",
|
|
300
|
+
"You are Bellonda.",
|
|
301
|
+
].join("\n"));
|
|
302
|
+
const { dbModule, memoryModule, eotModule, agentsModule } = await loadModules("action-item-bound-scope");
|
|
303
|
+
agentsModule.loadAgents();
|
|
304
|
+
const db = dbModule.getDb();
|
|
305
|
+
const getScope = getFunction(memoryModule, "getScope");
|
|
306
|
+
const createScope = getFunction(memoryModule, "createScope");
|
|
307
|
+
const listActionItems = getFunction(memoryModule, "listActionItems");
|
|
308
|
+
const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
|
|
309
|
+
const chapterhouse = getScope("chapterhouse");
|
|
310
|
+
const infra = createScope({
|
|
311
|
+
slug: "infra",
|
|
312
|
+
title: "Infra",
|
|
313
|
+
description: "Infra test scope",
|
|
314
|
+
keywords: ["infra"],
|
|
315
|
+
});
|
|
316
|
+
assert.ok(chapterhouse);
|
|
317
|
+
const inserted = db.prepare(`
|
|
318
|
+
INSERT INTO mem_inbox (scope_id, kind, payload, source_agent, source_task_id, status)
|
|
319
|
+
VALUES (?, 'memory_proposal', ?, 'bellonda', 'task-eot-action-item-bound-scope', 'pending')
|
|
320
|
+
`).run(chapterhouse.id, JSON.stringify({
|
|
321
|
+
kind: "action_item",
|
|
322
|
+
payload: {
|
|
323
|
+
title: "Review NAS disk alerts",
|
|
324
|
+
},
|
|
325
|
+
confidence: 0.9,
|
|
326
|
+
}));
|
|
327
|
+
await runEndOfTaskMemoryHook({
|
|
328
|
+
taskId: "task-eot-action-item-bound-scope",
|
|
329
|
+
finalResult: "Bellonda proposed an infra follow-up.",
|
|
330
|
+
copilotClient: {},
|
|
331
|
+
callLLM: async () => JSON.stringify({
|
|
332
|
+
decisions: [{
|
|
333
|
+
proposal_id: Number(inserted.lastInsertRowid),
|
|
334
|
+
decision: "accept",
|
|
335
|
+
reason: "Concrete infra follow-up.",
|
|
336
|
+
}],
|
|
337
|
+
implicit_memories: [],
|
|
338
|
+
}),
|
|
339
|
+
});
|
|
340
|
+
assert.equal(listActionItems({ scope_id: infra.id }).some((item) => item.title === "Review NAS disk alerts"), true);
|
|
341
|
+
assert.equal(listActionItems({ scope_id: chapterhouse.id }).some((item) => item.title === "Review NAS disk alerts"), false);
|
|
342
|
+
});
|
|
343
|
+
test("runEndOfTaskMemoryHook still accepts observation, decision, and entity proposals", async () => {
|
|
344
|
+
const { dbModule, memoryModule, eotModule } = await loadModules("existing-proposal-kinds");
|
|
345
|
+
const db = dbModule.getDb();
|
|
346
|
+
const getScope = getFunction(memoryModule, "getScope");
|
|
347
|
+
const listObservations = getFunction(memoryModule, "listObservations");
|
|
348
|
+
const listDecisions = getFunction(memoryModule, "listDecisions");
|
|
349
|
+
const listEntities = getFunction(memoryModule, "listEntities");
|
|
350
|
+
const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
|
|
351
|
+
const chapterhouse = getScope("chapterhouse");
|
|
352
|
+
assert.ok(chapterhouse);
|
|
353
|
+
const proposals = [
|
|
354
|
+
{ kind: "observation", payload: { content: "Existing observation proposals still persist." } },
|
|
355
|
+
{ kind: "decision", payload: { title: "Keep auto-accept", rationale: "It is required for EOT memory promotion." } },
|
|
356
|
+
{ kind: "entity", payload: { name: "Bellonda", entity_kind: "agent", summary: "Infrastructure mentat." } },
|
|
357
|
+
].map((envelope) => db.prepare(`
|
|
358
|
+
INSERT INTO mem_inbox (scope_id, kind, payload, source_agent, source_task_id, status)
|
|
359
|
+
VALUES (?, 'memory_proposal', ?, 'coder', 'task-eot-existing-kinds', 'pending')
|
|
360
|
+
`).run(chapterhouse.id, JSON.stringify(envelope)));
|
|
361
|
+
await runEndOfTaskMemoryHook({
|
|
362
|
+
taskId: "task-eot-existing-kinds",
|
|
363
|
+
finalResult: "Completed with several proposal kinds.",
|
|
364
|
+
copilotClient: {},
|
|
365
|
+
callLLM: async () => JSON.stringify({
|
|
366
|
+
decisions: proposals.map((proposal) => ({
|
|
367
|
+
proposal_id: Number(proposal.lastInsertRowid),
|
|
368
|
+
decision: "accept",
|
|
369
|
+
reason: "Valid durable memory.",
|
|
370
|
+
})),
|
|
371
|
+
implicit_memories: [],
|
|
372
|
+
}),
|
|
373
|
+
});
|
|
374
|
+
assert.equal(listObservations({ scope_id: chapterhouse.id }).some((row) => row.content === "Existing observation proposals still persist."), true);
|
|
375
|
+
assert.equal(listDecisions({ scope_id: chapterhouse.id }).some((row) => row.title === "Keep auto-accept" && row.rationale === "It is required for EOT memory promotion."), true);
|
|
376
|
+
assert.equal(listEntities({ scope_id: chapterhouse.id, kind: "agent" }).some((row) => row.name === "Bellonda"), true);
|
|
377
|
+
});
|
|
197
378
|
test("runEndOfTaskMemoryHook accepts entity proposals with entity_kind into mem_entities", async () => {
|
|
198
379
|
const { dbModule, memoryModule, eotModule } = await loadModules("entity-accept");
|
|
199
380
|
const db = dbModule.getDb();
|
|
@@ -108,12 +108,17 @@ test("active-scope hot-tier queries do not leak rows from other scopes", async (
|
|
|
108
108
|
const { dbModule, memoryModule, hotTierModule } = await loadModules();
|
|
109
109
|
dbModule.getDb();
|
|
110
110
|
const getScope = getFunction(memoryModule, "getScope");
|
|
111
|
+
const createScope = getFunction(memoryModule, "createScope");
|
|
111
112
|
const setActiveScope = getFunction(memoryModule, "setActiveScope");
|
|
112
113
|
const recordObservation = getFunction(memoryModule, "recordObservation");
|
|
113
114
|
const chapterhouse = getScope("chapterhouse");
|
|
114
|
-
const team =
|
|
115
|
+
const team = createScope({
|
|
116
|
+
slug: "team",
|
|
117
|
+
title: "Team",
|
|
118
|
+
description: "Team test scope",
|
|
119
|
+
keywords: ["team"],
|
|
120
|
+
});
|
|
115
121
|
assert.ok(chapterhouse);
|
|
116
|
-
assert.ok(team);
|
|
117
122
|
recordObservation({
|
|
118
123
|
scope_id: chapterhouse.id,
|
|
119
124
|
content: "Chapterhouse hot entry",
|
|
@@ -135,11 +140,16 @@ test("renderHotTierXML includes open active-scope action items in a bounded acti
|
|
|
135
140
|
const { dbModule, memoryModule, hotTierModule } = await loadModules();
|
|
136
141
|
dbModule.getDb();
|
|
137
142
|
const getScope = getFunction(memoryModule, "getScope");
|
|
143
|
+
const createScope = getFunction(memoryModule, "createScope");
|
|
138
144
|
const recordActionItem = getFunction(memoryModule, "recordActionItem");
|
|
139
145
|
const chapterhouse = getScope("chapterhouse");
|
|
140
|
-
const team =
|
|
146
|
+
const team = createScope({
|
|
147
|
+
slug: "team",
|
|
148
|
+
title: "Team",
|
|
149
|
+
description: "Team test scope",
|
|
150
|
+
keywords: ["team"],
|
|
151
|
+
});
|
|
141
152
|
assert.ok(chapterhouse);
|
|
142
|
-
assert.ok(team);
|
|
143
153
|
const urgent = recordActionItem({
|
|
144
154
|
scope_id: chapterhouse.id,
|
|
145
155
|
title: "Migrate <feature ideas>",
|
|
@@ -24,6 +24,15 @@ function getFunction(module, name) {
|
|
|
24
24
|
assert.equal(typeof value, "function", `expected ${name} to be exported`);
|
|
25
25
|
return value;
|
|
26
26
|
}
|
|
27
|
+
function createTestScope(memoryModule, slug) {
|
|
28
|
+
const createScope = getFunction(memoryModule, "createScope");
|
|
29
|
+
return createScope({
|
|
30
|
+
slug,
|
|
31
|
+
title: slug.split("-").map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" "),
|
|
32
|
+
description: `${slug} test scope`,
|
|
33
|
+
keywords: [slug],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
27
36
|
test.beforeEach(async () => {
|
|
28
37
|
const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
|
|
29
38
|
dbModule.closeDb();
|
|
@@ -42,8 +51,8 @@ test("dedupObservationsPass supersedes similar observations in scope determinist
|
|
|
42
51
|
const getScope = getFunction(memoryModule, "getScope");
|
|
43
52
|
const recordObservation = getFunction(memoryModule, "recordObservation");
|
|
44
53
|
const chapterhouse = getScope("chapterhouse");
|
|
45
|
-
const team =
|
|
46
|
-
assert.ok(chapterhouse
|
|
54
|
+
const team = createTestScope(memoryModule, "team");
|
|
55
|
+
assert.ok(chapterhouse);
|
|
47
56
|
const first = recordObservation({
|
|
48
57
|
scope_id: team.id,
|
|
49
58
|
content: "The worker event stream uses server sent events for live task output.",
|
|
@@ -88,9 +97,8 @@ test("dedupDecisionsPass supersedes similar active decisions within scope and ke
|
|
|
88
97
|
const getScope = getFunction(memoryModule, "getScope");
|
|
89
98
|
const upsertEntity = getFunction(memoryModule, "upsertEntity");
|
|
90
99
|
const recordDecision = getFunction(memoryModule, "recordDecision");
|
|
91
|
-
const team =
|
|
92
|
-
const infra =
|
|
93
|
-
assert.ok(team && infra);
|
|
100
|
+
const team = createTestScope(memoryModule, "team");
|
|
101
|
+
const infra = createTestScope(memoryModule, "infra");
|
|
94
102
|
const api = upsertEntity({ scope_id: team.id, kind: "component", name: "api" });
|
|
95
103
|
const web = upsertEntity({ scope_id: team.id, kind: "component", name: "web" });
|
|
96
104
|
const oldDecision = recordDecision({
|
|
@@ -139,8 +147,8 @@ test("orphanCleanupPass clears missing observation entity references without tou
|
|
|
139
147
|
const upsertEntity = getFunction(memoryModule, "upsertEntity");
|
|
140
148
|
const recordObservation = getFunction(memoryModule, "recordObservation");
|
|
141
149
|
const chapterhouse = getScope("chapterhouse");
|
|
142
|
-
const team =
|
|
143
|
-
assert.ok(chapterhouse
|
|
150
|
+
const team = createTestScope(memoryModule, "team");
|
|
151
|
+
assert.ok(chapterhouse);
|
|
144
152
|
const entity = upsertEntity({ scope_id: chapterhouse.id, kind: "tool", name: "sqlite" });
|
|
145
153
|
const valid = recordObservation({ scope_id: chapterhouse.id, entity_id: entity.id, content: "Valid entity reference", source: "test" });
|
|
146
154
|
const orphan = recordObservation({ scope_id: chapterhouse.id, content: "Will become orphaned", source: "test" });
|
|
@@ -164,8 +172,8 @@ test("decayPass archives old low-confidence observations only in scope and compa
|
|
|
164
172
|
const getScope = getFunction(memoryModule, "getScope");
|
|
165
173
|
const recordObservation = getFunction(memoryModule, "recordObservation");
|
|
166
174
|
const chapterhouse = getScope("chapterhouse");
|
|
167
|
-
const team =
|
|
168
|
-
assert.ok(chapterhouse
|
|
175
|
+
const team = createTestScope(memoryModule, "team");
|
|
176
|
+
assert.ok(chapterhouse);
|
|
169
177
|
const archiveMe = recordObservation({ scope_id: chapterhouse.id, content: "Old low confidence", source: "test", confidence: 0.2 });
|
|
170
178
|
const highConfidence = recordObservation({ scope_id: chapterhouse.id, content: "Old high confidence", source: "test", confidence: 0.9 });
|
|
171
179
|
const fresh = recordObservation({ scope_id: chapterhouse.id, content: "Fresh low confidence", source: "test", confidence: 0.2 });
|
|
@@ -201,8 +209,8 @@ test("runHousekeeping defaults to the active scope and can target all active sco
|
|
|
201
209
|
const setActiveScope = getFunction(memoryModule, "setActiveScope");
|
|
202
210
|
const recordObservation = getFunction(memoryModule, "recordObservation");
|
|
203
211
|
const chapterhouse = getScope("chapterhouse");
|
|
204
|
-
const team =
|
|
205
|
-
assert.ok(chapterhouse
|
|
212
|
+
const team = createTestScope(memoryModule, "team");
|
|
213
|
+
assert.ok(chapterhouse);
|
|
206
214
|
const chapterhouseOld = recordObservation({ scope_id: chapterhouse.id, content: "Chapterhouse old low", source: "test", confidence: 0.1 });
|
|
207
215
|
const teamOld = recordObservation({ scope_id: team.id, content: "Team old low", source: "test", confidence: 0.1 });
|
|
208
216
|
db.prepare(`UPDATE mem_observations SET created_at = datetime('now', '-31 days') WHERE id IN (?, ?)`).run(chapterhouseOld.id, teamOld.id);
|
|
@@ -225,8 +233,7 @@ test("tieringPass promotes and demotes rows from lifecycle signals and is idempo
|
|
|
225
233
|
const upsertEntity = getFunction(memoryModule, "upsertEntity");
|
|
226
234
|
const recordObservation = getFunction(memoryModule, "recordObservation");
|
|
227
235
|
const recordDecision = getFunction(memoryModule, "recordDecision");
|
|
228
|
-
const team =
|
|
229
|
-
assert.ok(team);
|
|
236
|
+
const team = createTestScope(memoryModule, "team");
|
|
230
237
|
const entity = upsertEntity({ scope_id: team.id, kind: "component", name: "memory", tier: "warm" });
|
|
231
238
|
const referencedObservation = recordObservation({
|
|
232
239
|
scope_id: team.id,
|
package/dist/memory/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { completeActionItem, dropActionItem, getActionItem, listActionItems, recordActionItem, snoozeActionItem, } from "./action-items.js";
|
|
2
|
-
export { getActiveScope, inferScopeFromText, setActiveScope } from "./active-scope.js";
|
|
2
|
+
export { getActiveScope, inferScopeFromText, setActiveScope, withActiveScope } from "./active-scope.js";
|
|
3
3
|
export { recordDecision, getDecision, listDecisions, supersedeDecision } from "./decisions.js";
|
|
4
4
|
export { getEntity, findEntityByName, listEntities, upsertEntity } from "./entities.js";
|
|
5
5
|
export { getHotTierEntries, renderHotTierForActiveScope, renderHotTierXML } from "./hot-tier.js";
|
|
@@ -68,13 +68,6 @@ test("getDb seeds canonical memory scopes on first run", async () => {
|
|
|
68
68
|
ORDER BY slug
|
|
69
69
|
`).all();
|
|
70
70
|
assert.deepEqual(rows, [
|
|
71
|
-
{
|
|
72
|
-
slug: "brian",
|
|
73
|
-
title: "Brian",
|
|
74
|
-
description: "Brian's preferences, context, working style",
|
|
75
|
-
keywords: JSON.stringify(["brian"]),
|
|
76
|
-
active: 1,
|
|
77
|
-
},
|
|
78
71
|
{
|
|
79
72
|
slug: "chapterhouse",
|
|
80
73
|
title: "Chapterhouse",
|
|
@@ -89,20 +82,6 @@ test("getDb seeds canonical memory scopes on first run", async () => {
|
|
|
89
82
|
keywords: JSON.stringify(["everywhere", "general"]),
|
|
90
83
|
active: 1,
|
|
91
84
|
},
|
|
92
|
-
{
|
|
93
|
-
slug: "infra",
|
|
94
|
-
title: "Infra",
|
|
95
|
-
description: "Infrastructure, hosting, deployment, CI/CD",
|
|
96
|
-
keywords: JSON.stringify(["infra"]),
|
|
97
|
-
active: 1,
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
slug: "team",
|
|
101
|
-
title: "Team",
|
|
102
|
-
description: "Team processes, rituals, OKRs",
|
|
103
|
-
keywords: JSON.stringify(["team"]),
|
|
104
|
-
active: 1,
|
|
105
|
-
},
|
|
106
85
|
]);
|
|
107
86
|
}
|
|
108
87
|
finally {
|
|
@@ -123,11 +102,8 @@ test("memory schema initialization is idempotent", async () => {
|
|
|
123
102
|
ORDER BY slug
|
|
124
103
|
`).all();
|
|
125
104
|
assert.deepEqual(counts, [
|
|
126
|
-
{ slug: "brian", count: 1 },
|
|
127
105
|
{ slug: "chapterhouse", count: 1 },
|
|
128
106
|
{ slug: "global", count: 1 },
|
|
129
|
-
{ slug: "infra", count: 1 },
|
|
130
|
-
{ slug: "team", count: 1 },
|
|
131
107
|
]);
|
|
132
108
|
reopened.dbModule.closeDb();
|
|
133
109
|
}
|
package/dist/store/db.js
CHANGED
|
@@ -216,24 +216,6 @@ const MEMORY_SCOPE_SEEDS = [
|
|
|
216
216
|
description: "Chapterhouse codebase, conventions, decisions, gotchas",
|
|
217
217
|
keywords: ["chapterhouse", "this repo", "this project", "the daemon"],
|
|
218
218
|
},
|
|
219
|
-
{
|
|
220
|
-
slug: "infra",
|
|
221
|
-
title: "Infra",
|
|
222
|
-
description: "Infrastructure, hosting, deployment, CI/CD",
|
|
223
|
-
keywords: ["infra"],
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
slug: "team",
|
|
227
|
-
title: "Team",
|
|
228
|
-
description: "Team processes, rituals, OKRs",
|
|
229
|
-
keywords: ["team"],
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
slug: "brian",
|
|
233
|
-
title: "Brian",
|
|
234
|
-
description: "Brian's preferences, context, working style",
|
|
235
|
-
keywords: ["brian"],
|
|
236
|
-
},
|
|
237
219
|
];
|
|
238
220
|
const CHAPTERHOUSE_WIKI_INDEX_SOURCE = "wiki:pages/projects/chapterhouse/index.md";
|
|
239
221
|
const CHAPTERHOUSE_WIKI_HOT_TIER_REASON = "P6 PR1 wiki migration hot-tier candidate";
|
|
@@ -628,13 +610,39 @@ export function getDb() {
|
|
|
628
610
|
db.exec(`
|
|
629
611
|
CREATE TABLE IF NOT EXISTS copilot_sessions (
|
|
630
612
|
session_key TEXT PRIMARY KEY,
|
|
631
|
-
mode TEXT NOT NULL CHECK(mode IN ('default', 'project')),
|
|
613
|
+
mode TEXT NOT NULL CHECK(mode IN ('default', 'project', 'agent')),
|
|
632
614
|
project_root TEXT,
|
|
633
615
|
copilot_session_id TEXT NOT NULL,
|
|
634
616
|
model TEXT,
|
|
635
617
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
636
618
|
)
|
|
637
619
|
`);
|
|
620
|
+
try {
|
|
621
|
+
db.prepare(`
|
|
622
|
+
INSERT INTO copilot_sessions (session_key, mode, copilot_session_id)
|
|
623
|
+
VALUES ('__mode_probe__', 'agent', '__probe__')
|
|
624
|
+
`).run();
|
|
625
|
+
db.prepare(`DELETE FROM copilot_sessions WHERE session_key = '__mode_probe__'`).run();
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
db.exec(`ALTER TABLE copilot_sessions RENAME TO copilot_sessions_old`);
|
|
629
|
+
db.exec(`
|
|
630
|
+
CREATE TABLE copilot_sessions (
|
|
631
|
+
session_key TEXT PRIMARY KEY,
|
|
632
|
+
mode TEXT NOT NULL CHECK(mode IN ('default', 'project', 'agent')),
|
|
633
|
+
project_root TEXT,
|
|
634
|
+
copilot_session_id TEXT NOT NULL,
|
|
635
|
+
model TEXT,
|
|
636
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
637
|
+
)
|
|
638
|
+
`);
|
|
639
|
+
db.exec(`
|
|
640
|
+
INSERT INTO copilot_sessions (session_key, mode, project_root, copilot_session_id, model, updated_at)
|
|
641
|
+
SELECT session_key, mode, project_root, copilot_session_id, model, updated_at
|
|
642
|
+
FROM copilot_sessions_old
|
|
643
|
+
`);
|
|
644
|
+
db.exec(`DROP TABLE copilot_sessions_old`);
|
|
645
|
+
}
|
|
638
646
|
// Migrate: add metadata columns to conversation_log if not present
|
|
639
647
|
const convCols = db.prepare(`PRAGMA table_info(conversation_log)`).all();
|
|
640
648
|
if (!convCols.some((c) => c.name === 'session_key')) {
|
package/package.json
CHANGED