chapterhouse 0.9.2 → 0.10.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/README.md +1 -1
- package/dist/api/auth.js +11 -1
- package/dist/api/auth.test.js +29 -0
- package/dist/api/errors.js +23 -0
- package/dist/api/route-coverage.test.js +61 -21
- package/dist/api/routes/agents.js +472 -0
- package/dist/api/routes/memory.js +299 -0
- package/dist/api/routes/projects.js +170 -0
- package/dist/api/routes/sessions.js +347 -0
- package/dist/api/routes/system.js +82 -0
- package/dist/api/routes/wiki.js +455 -0
- package/dist/api/routes/wiki.test.js +49 -0
- package/dist/api/send-json.js +16 -0
- package/dist/api/send-json.test.js +18 -0
- package/dist/api/server-runtime.js +45 -3
- package/dist/api/server.js +34 -1764
- package/dist/api/server.test.js +239 -8
- package/dist/api/sse-hub.js +37 -0
- package/dist/cli.js +1 -1
- package/dist/config.js +151 -58
- package/dist/config.test.js +29 -0
- package/dist/copilot/okr-mapper.js +2 -11
- package/dist/copilot/orchestrator.js +358 -352
- package/dist/copilot/orchestrator.test.js +139 -4
- package/dist/copilot/prompt-date.js +2 -1
- package/dist/copilot/session-manager.js +25 -23
- package/dist/copilot/session-manager.test.js +35 -1
- package/dist/copilot/standup.js +2 -2
- package/dist/copilot/task-event-log.js +7 -1
- package/dist/copilot/task-event-log.test.js +13 -0
- package/dist/copilot/tools/agent.js +608 -0
- package/dist/copilot/tools/index.js +19 -0
- package/dist/copilot/tools/memory.js +678 -0
- package/dist/copilot/tools/models.js +2 -0
- package/dist/copilot/tools/okr.js +171 -0
- package/dist/copilot/tools/wiki.js +333 -0
- package/dist/copilot/tools-deps.js +4 -0
- package/dist/copilot/tools.agent.test.js +10 -8
- package/dist/copilot/tools.inventory.test.js +76 -0
- package/dist/copilot/tools.js +1 -1780
- package/dist/copilot/tools.okr.test.js +31 -0
- package/dist/copilot/tools.wiki.test.js +6 -3
- package/dist/copilot/turn-event-log.js +31 -4
- package/dist/copilot/turn-event-log.test.js +24 -2
- package/dist/copilot/workiq-installer.test.js +2 -2
- package/dist/daemon-install.js +3 -2
- package/dist/daemon.js +9 -17
- package/dist/integrations/ado-client.js +90 -9
- package/dist/integrations/ado-client.test.js +56 -0
- package/dist/integrations/team-push.js +1 -0
- package/dist/integrations/team-push.test.js +6 -0
- package/dist/integrations/teams-notify.js +1 -0
- package/dist/integrations/teams-notify.test.js +5 -0
- package/dist/memory/active-scope.test.js +0 -1
- package/dist/memory/checkpoint.js +89 -72
- package/dist/memory/checkpoint.test.js +23 -3
- package/dist/memory/eot.js +87 -85
- package/dist/memory/eot.test.js +71 -3
- package/dist/memory/hooks.js +2 -4
- package/dist/memory/housekeeping-scheduler.js +1 -1
- package/dist/memory/housekeeping-scheduler.test.js +1 -2
- package/dist/memory/housekeeping.js +100 -3
- package/dist/memory/housekeeping.test.js +33 -2
- package/dist/memory/reflect.test.js +2 -0
- package/dist/memory/scope-lock.js +26 -0
- package/dist/memory/scope-lock.test.js +118 -0
- package/dist/memory/scopes.test.js +0 -1
- package/dist/mode-context.js +58 -5
- package/dist/mode-context.test.js +68 -0
- package/dist/paths.js +1 -0
- package/dist/setup.js +3 -2
- package/dist/shared/api-schemas.js +48 -5
- package/dist/store/connection.js +96 -0
- package/dist/store/db.js +5 -1498
- package/dist/store/db.test.js +182 -1
- package/dist/store/migrations.js +460 -0
- package/dist/store/repositories/memory.js +281 -0
- package/dist/store/repositories/okr.js +3 -0
- package/dist/store/repositories/projects.js +5 -0
- package/dist/store/repositories/sessions.js +284 -0
- package/dist/store/repositories/wiki.js +60 -0
- package/dist/store/schema.js +501 -0
- package/dist/util/logger.js +3 -2
- package/dist/wiki/consolidation.js +50 -9
- package/dist/wiki/consolidation.test.js +45 -0
- package/dist/wiki/frontmatter.js +43 -13
- package/dist/wiki/frontmatter.test.js +24 -0
- package/dist/wiki/fs.js +16 -4
- package/dist/wiki/fs.test.js +84 -0
- package/dist/wiki/index-manager.js +30 -2
- package/dist/wiki/index-manager.test.js +43 -12
- package/dist/wiki/ingest.js +1 -1
- package/dist/wiki/lock.js +11 -1
- package/dist/wiki/log-manager.js +2 -7
- package/dist/wiki/migrate.js +44 -17
- package/dist/wiki/project-registry.js +10 -5
- package/dist/wiki/project-registry.test.js +14 -0
- package/dist/wiki/scheduler.js +1 -1
- package/dist/wiki/seed-team-wiki.js +2 -1
- package/dist/wiki/team-sync.js +31 -6
- package/dist/wiki/team-sync.test.js +81 -0
- package/package.json +1 -1
- package/web/dist/assets/WikiEdit-BZXAdarz.js +30 -0
- package/web/dist/assets/WikiEdit-BZXAdarz.js.map +1 -0
- package/web/dist/assets/WikiGraph-KrCYco4v.js +2 -0
- package/web/dist/assets/WikiGraph-KrCYco4v.js.map +1 -0
- package/web/dist/assets/index-CUm2Wbuh.js +250 -0
- package/web/dist/assets/index-CUm2Wbuh.js.map +1 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-iQrv3lQN.js +0 -286
- package/web/dist/assets/index-iQrv3lQN.js.map +0 -1
package/dist/store/db.test.js
CHANGED
|
@@ -26,6 +26,58 @@ test.after(() => {
|
|
|
26
26
|
resetSingletons();
|
|
27
27
|
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
28
28
|
});
|
|
29
|
+
test("getDb returns the same singleton connection until closeDb is called", async () => {
|
|
30
|
+
const dbModule = await loadDbModule();
|
|
31
|
+
try {
|
|
32
|
+
const first = dbModule.getDb();
|
|
33
|
+
const second = dbModule.getDb();
|
|
34
|
+
assert.equal(second, first);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
dbModule.closeDb();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test("closeDb allows getDb to reinitialize a new usable connection without losing persisted state", async () => {
|
|
41
|
+
const dbModule = await loadDbModule();
|
|
42
|
+
try {
|
|
43
|
+
const first = dbModule.getDb();
|
|
44
|
+
dbModule.setState("db-reinit-sentinel", "persisted");
|
|
45
|
+
dbModule.closeDb();
|
|
46
|
+
const second = dbModule.getDb();
|
|
47
|
+
assert.notEqual(second, first);
|
|
48
|
+
assert.equal(dbModule.getState("db-reinit-sentinel"), "persisted");
|
|
49
|
+
assert.deepEqual(second.prepare("SELECT 1 AS ok").get(), { ok: 1 });
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
dbModule.closeDb();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
test("getDb records migrations once in registry order across repeated startups", async () => {
|
|
56
|
+
const dbModule = await loadDbModule();
|
|
57
|
+
try {
|
|
58
|
+
let db = dbModule.getDb();
|
|
59
|
+
const firstRows = db.prepare(`
|
|
60
|
+
SELECT version, name
|
|
61
|
+
FROM __db_migrations
|
|
62
|
+
ORDER BY rowid
|
|
63
|
+
`).all();
|
|
64
|
+
assert.ok(firstRows.length > 0, "expected at least one tracked migration");
|
|
65
|
+
assert.deepEqual(firstRows.map((row) => row.version), [...firstRows].map((row) => row.version).sort((a, b) => a - b), "migrations should be recorded in ascending registry order");
|
|
66
|
+
assert.equal(new Set(firstRows.map((row) => row.version)).size, firstRows.length, "migration versions should be unique");
|
|
67
|
+
assert.equal(new Set(firstRows.map((row) => row.name)).size, firstRows.length, "migration names should be unique");
|
|
68
|
+
dbModule.closeDb();
|
|
69
|
+
db = dbModule.getDb();
|
|
70
|
+
const secondRows = db.prepare(`
|
|
71
|
+
SELECT version, name
|
|
72
|
+
FROM __db_migrations
|
|
73
|
+
ORDER BY rowid
|
|
74
|
+
`).all();
|
|
75
|
+
assert.deepEqual(secondRows, firstRows);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
dbModule.closeDb();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
29
81
|
test("getDb startup reindex populates wiki_pages when pages exist on disk", async () => {
|
|
30
82
|
mkdirSync(join(chapterhouseHome, "wiki", "pages", "topics", "rust"), { recursive: true });
|
|
31
83
|
writeFileSync(join(chapterhouseHome, "wiki", "pages", "topics", "rust", "index.md"), "---\ntitle: Rust\nsummary: Systems language\nupdated: 2026-05-15\n---\n\n# Rust\n\nFearless concurrency.\n", "utf-8");
|
|
@@ -68,6 +120,18 @@ test("getDb startup reindex creates the wiki log directory when it is missing",
|
|
|
68
120
|
dbModule.closeDb();
|
|
69
121
|
}
|
|
70
122
|
});
|
|
123
|
+
test("getDb reports FTS5 readiness after deferred rebuild work finishes", async () => {
|
|
124
|
+
const dbModule = await loadDbModule();
|
|
125
|
+
try {
|
|
126
|
+
dbModule.getDb();
|
|
127
|
+
assert.equal(typeof dbModule.isFts5Ready, "function");
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
129
|
+
assert.equal(dbModule.isFts5Ready(), true);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
dbModule.closeDb();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
71
135
|
test("getDb initializes schema, state helpers, and conversation formatting", async () => {
|
|
72
136
|
const dbModule = await loadDbModule();
|
|
73
137
|
try {
|
|
@@ -91,13 +155,23 @@ test("getDb initializes schema, state helpers, and conversation formatting", asy
|
|
|
91
155
|
dbModule.deleteState("router_config");
|
|
92
156
|
assert.equal(dbModule.getState("router_config"), undefined);
|
|
93
157
|
const longReply = "A".repeat(1_550);
|
|
158
|
+
const oversizedTurn = "B".repeat(10_050);
|
|
94
159
|
dbModule.logConversation("system", "Started background sync", "worker");
|
|
95
160
|
dbModule.logConversation("user", "Please summarize the last deploy", "web");
|
|
96
161
|
dbModule.logConversation("assistant", longReply, "web");
|
|
162
|
+
dbModule.logConversation("assistant", oversizedTurn, "web");
|
|
163
|
+
const storedOversizedTurn = db.prepare(`
|
|
164
|
+
SELECT content
|
|
165
|
+
FROM conversation_log
|
|
166
|
+
WHERE content LIKE 'B%'
|
|
167
|
+
ORDER BY id DESC
|
|
168
|
+
LIMIT 1
|
|
169
|
+
`).get();
|
|
170
|
+
assert.equal(storedOversizedTurn.content.length, 10_000);
|
|
97
171
|
assert.equal(dbModule.getRecentConversation(3), [
|
|
98
|
-
"[worker] System: Started background sync",
|
|
99
172
|
"[web] User: Please summarize the last deploy",
|
|
100
173
|
`Chapterhouse: ${"A".repeat(1_500)}…`,
|
|
174
|
+
`Chapterhouse: ${"B".repeat(1_500)}…`,
|
|
101
175
|
].join("\n\n"));
|
|
102
176
|
}
|
|
103
177
|
finally {
|
|
@@ -115,6 +189,7 @@ test("getDb initializes action-item memory schema, reflect patterns schema, and
|
|
|
115
189
|
assert.equal(tableNames.has("mem_patterns"), true, "expected mem_patterns table");
|
|
116
190
|
const columns = db.prepare(`PRAGMA table_info(mem_action_items)`).all();
|
|
117
191
|
const columnNames = new Set(columns.map((column) => column.name));
|
|
192
|
+
const columnTypes = new Map(columns.map((column) => [column.name, column.type]));
|
|
118
193
|
for (const name of [
|
|
119
194
|
"id",
|
|
120
195
|
"scope_id",
|
|
@@ -136,6 +211,7 @@ test("getDb initializes action-item memory schema, reflect patterns schema, and
|
|
|
136
211
|
]) {
|
|
137
212
|
assert.equal(columnNames.has(name), true, `expected mem_action_items.${name}`);
|
|
138
213
|
}
|
|
214
|
+
assert.equal(columnTypes.get("created_at"), "DATETIME");
|
|
139
215
|
const patternColumns = db.prepare(`PRAGMA table_info(mem_patterns)`).all();
|
|
140
216
|
const patternColumnNames = new Set(patternColumns.map((column) => column.name));
|
|
141
217
|
for (const name of [
|
|
@@ -153,6 +229,10 @@ test("getDb initializes action-item memory schema, reflect patterns schema, and
|
|
|
153
229
|
}
|
|
154
230
|
const patternIndexes = db.prepare(`PRAGMA index_list(mem_patterns)`).all();
|
|
155
231
|
assert.equal(patternIndexes.some((index) => index.name === "mem_patterns_scope_tier_idx"), true, "expected mem_patterns scope/tier index");
|
|
232
|
+
const observationIndexes = db.prepare(`PRAGMA index_list(mem_observations)`).all();
|
|
233
|
+
const decisionIndexes = db.prepare(`PRAGMA index_list(mem_decisions)`).all();
|
|
234
|
+
assert.equal(observationIndexes.some((index) => index.name === "mem_observations_scope_superseded_by_idx"), true);
|
|
235
|
+
assert.equal(decisionIndexes.some((index) => index.name === "mem_decisions_scope_superseded_by_idx"), true);
|
|
156
236
|
const scope = db.prepare(`SELECT id FROM mem_scopes WHERE slug = 'chapterhouse'`).get();
|
|
157
237
|
const inserted = db.prepare(`
|
|
158
238
|
INSERT INTO mem_action_items (scope_id, title, detail, source)
|
|
@@ -169,6 +249,107 @@ test("getDb initializes action-item memory schema, reflect patterns schema, and
|
|
|
169
249
|
dbModule.closeDb();
|
|
170
250
|
}
|
|
171
251
|
});
|
|
252
|
+
test("getDb migrates legacy mem_action_items created_at TEXT schemas without losing rows or indexes", async () => {
|
|
253
|
+
const seedDb = new Database(dbPath);
|
|
254
|
+
seedDb.exec(`
|
|
255
|
+
CREATE TABLE mem_scopes (
|
|
256
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
257
|
+
slug TEXT NOT NULL,
|
|
258
|
+
title TEXT NOT NULL,
|
|
259
|
+
description TEXT NOT NULL,
|
|
260
|
+
keywords TEXT NOT NULL DEFAULT '[]',
|
|
261
|
+
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
262
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
263
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
264
|
+
);
|
|
265
|
+
CREATE TABLE mem_action_items (
|
|
266
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
267
|
+
scope_id INTEGER NOT NULL REFERENCES mem_scopes(id),
|
|
268
|
+
entity_id INTEGER,
|
|
269
|
+
title TEXT NOT NULL,
|
|
270
|
+
detail TEXT,
|
|
271
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'done', 'dropped', 'snoozed')),
|
|
272
|
+
due_at TEXT,
|
|
273
|
+
snooze_until TEXT,
|
|
274
|
+
source TEXT,
|
|
275
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
276
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
277
|
+
resolved_at TEXT,
|
|
278
|
+
resolution_reason TEXT,
|
|
279
|
+
tier TEXT NOT NULL DEFAULT 'warm' CHECK(tier IN ('hot', 'warm', 'cold')),
|
|
280
|
+
tier_pinned_at TEXT,
|
|
281
|
+
tier_reason TEXT,
|
|
282
|
+
last_recalled_at TEXT
|
|
283
|
+
);
|
|
284
|
+
INSERT INTO mem_scopes (slug, title, description) VALUES ('chapterhouse', 'Chapterhouse', 'Test scope');
|
|
285
|
+
INSERT INTO mem_action_items (
|
|
286
|
+
scope_id, title, detail, status, due_at, source, created_at, updated_at, tier, tier_reason
|
|
287
|
+
) VALUES (
|
|
288
|
+
1, 'Follow up on migration', 'Preserve this row', 'open', '2026-05-20 12:00:00', 'test',
|
|
289
|
+
'2026-05-14 10:11:12', '2026-05-14 10:11:13', 'hot', 'legacy'
|
|
290
|
+
);
|
|
291
|
+
`);
|
|
292
|
+
seedDb.close();
|
|
293
|
+
const dbModule = await loadDbModule();
|
|
294
|
+
try {
|
|
295
|
+
const db = dbModule.getDb();
|
|
296
|
+
const columns = db.prepare(`PRAGMA table_info(mem_action_items)`).all();
|
|
297
|
+
const columnTypes = new Map(columns.map((column) => [column.name, column.type]));
|
|
298
|
+
assert.equal(columnTypes.get("created_at"), "DATETIME");
|
|
299
|
+
assert.deepEqual(db.prepare(`SELECT title, detail, created_at, tier, tier_reason FROM mem_action_items`).get(), {
|
|
300
|
+
title: "Follow up on migration",
|
|
301
|
+
detail: "Preserve this row",
|
|
302
|
+
created_at: "2026-05-14 10:11:12",
|
|
303
|
+
tier: "hot",
|
|
304
|
+
tier_reason: "legacy",
|
|
305
|
+
});
|
|
306
|
+
const indexes = db.prepare(`PRAGMA index_list(mem_action_items)`).all();
|
|
307
|
+
assert.equal(indexes.some((index) => index.name === "idx_mem_action_items_scope_status"), true);
|
|
308
|
+
assert.equal(indexes.some((index) => index.name === "idx_mem_action_items_due"), true);
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
dbModule.closeDb();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
test("getDb legacy entity tier rebuild promotes recent high-confidence entities to hot", async () => {
|
|
315
|
+
const seedDb = new Database(dbPath);
|
|
316
|
+
seedDb.exec(`
|
|
317
|
+
CREATE TABLE mem_scopes (
|
|
318
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
319
|
+
slug TEXT NOT NULL,
|
|
320
|
+
title TEXT NOT NULL,
|
|
321
|
+
description TEXT NOT NULL,
|
|
322
|
+
keywords TEXT NOT NULL DEFAULT '[]',
|
|
323
|
+
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
324
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
325
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
326
|
+
);
|
|
327
|
+
CREATE TABLE mem_entities (
|
|
328
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
329
|
+
scope_id INTEGER NOT NULL REFERENCES mem_scopes(id),
|
|
330
|
+
kind TEXT NOT NULL,
|
|
331
|
+
name TEXT NOT NULL,
|
|
332
|
+
summary TEXT,
|
|
333
|
+
tier TEXT NOT NULL DEFAULT 'warm' CHECK(tier IN ('hot', 'warm', 'cold', 'glacier', 'unknown')),
|
|
334
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
335
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
336
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
337
|
+
);
|
|
338
|
+
INSERT INTO mem_scopes (slug, title, description) VALUES ('chapterhouse', 'Chapterhouse', 'Test scope');
|
|
339
|
+
INSERT INTO mem_entities (scope_id, kind, name, tier, confidence, updated_at)
|
|
340
|
+
VALUES (1, 'component', 'memory-db', 'unknown', 0.95, datetime('now'));
|
|
341
|
+
`);
|
|
342
|
+
seedDb.close();
|
|
343
|
+
const dbModule = await loadDbModule();
|
|
344
|
+
try {
|
|
345
|
+
const db = dbModule.getDb();
|
|
346
|
+
const row = db.prepare(`SELECT tier FROM mem_entities WHERE name = 'memory-db'`).get();
|
|
347
|
+
assert.equal(row.tier, "hot");
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
dbModule.closeDb();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
172
353
|
test("getDb migrates legacy conversation_log tables to allow system messages", async () => {
|
|
173
354
|
const seedDb = new Database(dbPath);
|
|
174
355
|
seedDb.exec(`
|