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.
- 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 +92 -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/memory-assets/domain-skill.md +38 -0
- package/memory-assets/seed/cog-meta/improvements.md +8 -0
- package/memory-assets/seed/cog-meta/patterns.md +5 -0
- package/memory-assets/seed/cog-meta/reflect-cursor.md +4 -0
- package/memory-assets/seed/cog-meta/scenario-calibration.md +14 -0
- package/memory-assets/seed/cog-meta/self-observations.md +4 -0
- package/memory-assets/seed/domains.yml +19 -0
- package/memory-assets/seed/glacier/index.md +6 -0
- package/memory-assets/seed/hot-memory.md +5 -0
- package/memory-assets/seed/link-index.md +6 -0
- package/memory-assets/system-instructions.md +214 -0
- package/memory-assets/templates/action-items.md +8 -0
- package/memory-assets/templates/entities.md +4 -0
- package/memory-assets/templates/generic.md +2 -0
- package/memory-assets/templates/hot-memory.md +4 -0
- package/memory-assets/templates/observations.md +4 -0
- package/package.json +2 -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
|
@@ -169,7 +169,7 @@ describe("route coverage — static analysis", () => {
|
|
|
169
169
|
const routeFiles = readdirSync(ROUTES_DIR)
|
|
170
170
|
.filter((name) => name.endsWith(".ts") && !name.endsWith(".test.ts"))
|
|
171
171
|
.sort();
|
|
172
|
-
assert.deepEqual(routeFiles, ["agents.ts", "
|
|
172
|
+
assert.deepEqual(routeFiles, ["agents.ts", "projects.ts", "sessions.ts", "system.ts", "wiki.ts"]);
|
|
173
173
|
});
|
|
174
174
|
test("server routes cover every unique normalised path (no obvious duplicates leaked)", () => {
|
|
175
175
|
const serverPaths = extractServerPaths(readServerRouteSources());
|
|
@@ -191,8 +191,6 @@ describe("explicit auth route safeguards — static analysis", () => {
|
|
|
191
191
|
assert.match(serverSrc, /\b(?:app|router)\.get\("\/api\/wiki\/korg\/sessions",\s*authMiddleware,/);
|
|
192
192
|
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/wiki\/ingest",\s*authMiddleware,\s*async \(req: Request, res: Response\) => \{/);
|
|
193
193
|
assert.match(serverSrc, /\b(?:app|router)\.get\("\/api\/wiki\/search",\s*authMiddleware,\s*async \(req: Request, res: Response\) => \{/);
|
|
194
|
-
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/memory\/hooks\/git-commit",\s*authMiddleware,/);
|
|
195
|
-
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/memory\/hooks\/pr-merge",\s*authMiddleware,/);
|
|
196
194
|
});
|
|
197
195
|
});
|
|
198
196
|
describe("SSE exhaustiveness — static analysis", () => {
|
package/dist/api/server.js
CHANGED
|
@@ -15,7 +15,6 @@ import { apiNotFoundHandler, createApiErrorHandler } from "./errors.js";
|
|
|
15
15
|
import { childLogger } from "../util/logger.js";
|
|
16
16
|
import { assertAuthenticationConfigured, getDisplayHost, shouldServeSpaPath } from "./server-runtime.js";
|
|
17
17
|
import { createAgentsRouter } from "./routes/agents.js";
|
|
18
|
-
import { createMemoryRouter } from "./routes/memory.js";
|
|
19
18
|
import { createProjectsRouter } from "./routes/projects.js";
|
|
20
19
|
import { createSessionsRouter } from "./routes/sessions.js";
|
|
21
20
|
import { createSystemRouter } from "./routes/system.js";
|
|
@@ -136,7 +135,6 @@ setAgentSaveRuntimeHooks({
|
|
|
136
135
|
});
|
|
137
136
|
app.use(createSystemRouter({ apiToken }));
|
|
138
137
|
app.use(createAgentsRouter({ modeContext }));
|
|
139
|
-
app.use(createMemoryRouter({ authMiddleware }));
|
|
140
138
|
app.use(createProjectsRouter({ modeContext }));
|
|
141
139
|
app.use(createSessionsRouter());
|
|
142
140
|
app.use(createWikiRouter({ authMiddleware, modeContext }));
|
package/dist/api/server.test.js
CHANGED
|
@@ -195,25 +195,6 @@ function markBundledAgent(testRoot, slug, hash = "bundled-hash") {
|
|
|
195
195
|
db.close();
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
function setMemoryActiveScope(testRoot, slug) {
|
|
199
|
-
const db = new Database(getProjectDbPath(testRoot));
|
|
200
|
-
try {
|
|
201
|
-
if (slug === null) {
|
|
202
|
-
db.prepare(`DELETE FROM mem_settings WHERE key = ?`).run("current_scope_slug");
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const scope = db.prepare(`SELECT slug FROM mem_scopes WHERE slug = ?`).get(slug);
|
|
206
|
-
assert.ok(scope, `expected seeded memory scope '${slug}' to exist`);
|
|
207
|
-
db.prepare(`
|
|
208
|
-
INSERT INTO mem_settings (key, value)
|
|
209
|
-
VALUES (?, ?)
|
|
210
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
211
|
-
`).run("current_scope_slug", slug);
|
|
212
|
-
}
|
|
213
|
-
finally {
|
|
214
|
-
db.close();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
198
|
// Standalone mode triggers a full daemon init via the server→daemon circular import
|
|
218
199
|
// (server.ts imports restartDaemon from daemon.ts, which has module-level main()).
|
|
219
200
|
// The Copilot SDK's client.start() then blocks the event loop while authenticating,
|
|
@@ -705,74 +686,6 @@ test("server refuses standalone mode on non-loopback binds", async () => {
|
|
|
705
686
|
assert.match(logs.join(""), /Set API_TOKEN or configure Entra auth/);
|
|
706
687
|
rmSync(testRoot, { recursive: true, force: true });
|
|
707
688
|
});
|
|
708
|
-
test("server exposes the active memory scope API and requires auth", async () => {
|
|
709
|
-
await withStartedServer(async ({ baseUrl, authHeader, testRoot }) => {
|
|
710
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/active-scope`);
|
|
711
|
-
assert.equal(unauthorized.status, 401);
|
|
712
|
-
const noScope = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
713
|
-
headers: { authorization: authHeader },
|
|
714
|
-
});
|
|
715
|
-
assert.equal(noScope.status, 200);
|
|
716
|
-
assert.equal(await noScope.json(), null);
|
|
717
|
-
setMemoryActiveScope(testRoot, "chapterhouse");
|
|
718
|
-
const activeScope = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
719
|
-
headers: { authorization: authHeader },
|
|
720
|
-
});
|
|
721
|
-
assert.equal(activeScope.status, 200);
|
|
722
|
-
assert.deepEqual(await activeScope.json(), {
|
|
723
|
-
slug: "chapterhouse",
|
|
724
|
-
title: "Chapterhouse",
|
|
725
|
-
});
|
|
726
|
-
});
|
|
727
|
-
});
|
|
728
|
-
test("server creates memory scopes with duplicate and slug validation", async () => {
|
|
729
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
730
|
-
const unauthorized = await fetch(`${baseUrl}/api/scopes`, {
|
|
731
|
-
method: "POST",
|
|
732
|
-
headers: { "content-type": "application/json" },
|
|
733
|
-
body: JSON.stringify({ slug: "docs-site", title: "Docs Site" }),
|
|
734
|
-
});
|
|
735
|
-
assert.equal(unauthorized.status, 401);
|
|
736
|
-
const created = await fetch(`${baseUrl}/api/scopes`, {
|
|
737
|
-
method: "POST",
|
|
738
|
-
headers: {
|
|
739
|
-
authorization: authHeader,
|
|
740
|
-
"content-type": "application/json",
|
|
741
|
-
},
|
|
742
|
-
body: JSON.stringify({
|
|
743
|
-
slug: "docs-site",
|
|
744
|
-
title: "Docs Site",
|
|
745
|
-
description: "Documentation publishing and content workflows",
|
|
746
|
-
}),
|
|
747
|
-
});
|
|
748
|
-
assert.equal(created.status, 201);
|
|
749
|
-
assert.deepEqual(await created.json(), {
|
|
750
|
-
slug: "docs-site",
|
|
751
|
-
title: "Docs Site",
|
|
752
|
-
description: "Documentation publishing and content workflows",
|
|
753
|
-
active: true,
|
|
754
|
-
});
|
|
755
|
-
const duplicate = await fetch(`${baseUrl}/api/scopes`, {
|
|
756
|
-
method: "POST",
|
|
757
|
-
headers: {
|
|
758
|
-
authorization: authHeader,
|
|
759
|
-
"content-type": "application/json",
|
|
760
|
-
},
|
|
761
|
-
body: JSON.stringify({ slug: "docs-site", title: "Docs Site Again" }),
|
|
762
|
-
});
|
|
763
|
-
assert.equal(duplicate.status, 409);
|
|
764
|
-
assert.deepEqual(await duplicate.json(), { error: "Memory scope 'docs-site' already exists" });
|
|
765
|
-
const invalid = await fetch(`${baseUrl}/api/scopes`, {
|
|
766
|
-
method: "POST",
|
|
767
|
-
headers: {
|
|
768
|
-
authorization: authHeader,
|
|
769
|
-
"content-type": "application/json",
|
|
770
|
-
},
|
|
771
|
-
body: JSON.stringify({ slug: "Docs Site", title: "Docs Site" }),
|
|
772
|
-
});
|
|
773
|
-
assert.equal(invalid.status, 400);
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
689
|
test("server bootstrap rejects non-loopback origins", async () => {
|
|
777
690
|
await withStartedServer(async ({ baseUrl }) => {
|
|
778
691
|
const response = await fetch(`${baseUrl}/api/bootstrap`, {
|
|
@@ -1634,200 +1547,6 @@ test("server caps concurrent SSE connections per IP", async () => {
|
|
|
1634
1547
|
API_RATE_LIMIT_SSE_MAX_CONNECTIONS: "2",
|
|
1635
1548
|
});
|
|
1636
1549
|
});
|
|
1637
|
-
test("POST /api/memory/active-scope sets and clears the active scope", async () => {
|
|
1638
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
1639
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
1640
|
-
method: "POST",
|
|
1641
|
-
headers: { "content-type": "application/json" },
|
|
1642
|
-
body: JSON.stringify({ scope: "chapterhouse" }),
|
|
1643
|
-
});
|
|
1644
|
-
assert.equal(unauthorized.status, 401);
|
|
1645
|
-
const set = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
1646
|
-
method: "POST",
|
|
1647
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1648
|
-
body: JSON.stringify({ scope: "chapterhouse" }),
|
|
1649
|
-
});
|
|
1650
|
-
assert.equal(set.status, 200);
|
|
1651
|
-
assert.deepEqual(await set.json(), { ok: true, scope: "chapterhouse" });
|
|
1652
|
-
const verify = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
1653
|
-
headers: { authorization: authHeader },
|
|
1654
|
-
});
|
|
1655
|
-
assert.deepEqual(await verify.json(), { slug: "chapterhouse", title: "Chapterhouse" });
|
|
1656
|
-
const clear = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
1657
|
-
method: "POST",
|
|
1658
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1659
|
-
body: JSON.stringify({ scope: null }),
|
|
1660
|
-
});
|
|
1661
|
-
assert.equal(clear.status, 200);
|
|
1662
|
-
assert.deepEqual(await clear.json(), { ok: true, scope: null });
|
|
1663
|
-
const unknownScope = await fetch(`${baseUrl}/api/memory/active-scope`, {
|
|
1664
|
-
method: "POST",
|
|
1665
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1666
|
-
body: JSON.stringify({ scope: "nonexistent-scope" }),
|
|
1667
|
-
});
|
|
1668
|
-
assert.equal(unknownScope.status, 404);
|
|
1669
|
-
});
|
|
1670
|
-
});
|
|
1671
|
-
test("GET /api/memory/scopes lists all scopes with entry counts", async () => {
|
|
1672
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
1673
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/scopes`);
|
|
1674
|
-
assert.equal(unauthorized.status, 401);
|
|
1675
|
-
const res = await fetch(`${baseUrl}/api/memory/scopes`, {
|
|
1676
|
-
headers: { authorization: authHeader },
|
|
1677
|
-
});
|
|
1678
|
-
assert.equal(res.status, 200);
|
|
1679
|
-
const body = await res.json();
|
|
1680
|
-
assert.ok(Array.isArray(body.scopes));
|
|
1681
|
-
assert.ok(body.scopes.length >= 2, "expected at least 2 seeded scopes");
|
|
1682
|
-
const chapterhouse = body.scopes.find((s) => s.slug === "chapterhouse");
|
|
1683
|
-
assert.ok(chapterhouse, "expected chapterhouse scope");
|
|
1684
|
-
assert.ok(typeof chapterhouse.counts.observations === "number");
|
|
1685
|
-
assert.ok(typeof chapterhouse.counts.decisions === "number");
|
|
1686
|
-
});
|
|
1687
|
-
});
|
|
1688
|
-
test("GET /api/memory/:scope returns entries for a scope and 404 for unknown", async () => {
|
|
1689
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
1690
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/chapterhouse`);
|
|
1691
|
-
assert.equal(unauthorized.status, 401);
|
|
1692
|
-
const res = await fetch(`${baseUrl}/api/memory/chapterhouse`, {
|
|
1693
|
-
headers: { authorization: authHeader },
|
|
1694
|
-
});
|
|
1695
|
-
assert.equal(res.status, 200);
|
|
1696
|
-
const body = await res.json();
|
|
1697
|
-
assert.ok(Array.isArray(body.entries));
|
|
1698
|
-
assert.ok(typeof body.total === "number");
|
|
1699
|
-
const withStore = await fetch(`${baseUrl}/api/memory/chapterhouse?store=decisions`, {
|
|
1700
|
-
headers: { authorization: authHeader },
|
|
1701
|
-
});
|
|
1702
|
-
assert.equal(withStore.status, 200);
|
|
1703
|
-
const withTier = await fetch(`${baseUrl}/api/memory/chapterhouse?store=observations&tier=hot`, {
|
|
1704
|
-
headers: { authorization: authHeader },
|
|
1705
|
-
});
|
|
1706
|
-
assert.equal(withTier.status, 200);
|
|
1707
|
-
const notFound = await fetch(`${baseUrl}/api/memory/no-such-scope`, {
|
|
1708
|
-
headers: { authorization: authHeader },
|
|
1709
|
-
});
|
|
1710
|
-
assert.equal(notFound.status, 404);
|
|
1711
|
-
assert.deepEqual(await notFound.json(), { error: "Memory scope 'no-such-scope' not found" });
|
|
1712
|
-
});
|
|
1713
|
-
});
|
|
1714
|
-
test("GET /api/memory/:scope returns a cursor for additional pages", async () => {
|
|
1715
|
-
await withStartedServer(async ({ baseUrl, authHeader, testRoot }) => {
|
|
1716
|
-
const db = new Database(getProjectDbPath(testRoot));
|
|
1717
|
-
try {
|
|
1718
|
-
const scope = db.prepare(`SELECT id FROM mem_scopes WHERE slug = ?`).get("chapterhouse");
|
|
1719
|
-
const insert = db.prepare(`
|
|
1720
|
-
INSERT INTO mem_observations (scope_id, content, source, tier, created_at)
|
|
1721
|
-
VALUES (?, ?, 'test', 'hot', ?)
|
|
1722
|
-
`);
|
|
1723
|
-
for (let i = 0; i < 105; i++) {
|
|
1724
|
-
insert.run(scope.id, `Paginated observation ${i}`, `2026-05-15T00:${String(i).padStart(2, "0")}:00.000Z`);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
finally {
|
|
1728
|
-
db.close();
|
|
1729
|
-
}
|
|
1730
|
-
const first = await fetch(`${baseUrl}/api/memory/chapterhouse?store=observations&tier=hot`, {
|
|
1731
|
-
headers: { authorization: authHeader },
|
|
1732
|
-
});
|
|
1733
|
-
assert.equal(first.status, 200);
|
|
1734
|
-
const firstBody = await first.json();
|
|
1735
|
-
assert.equal(firstBody.entries.length, 100);
|
|
1736
|
-
assert.ok(firstBody.total >= 105);
|
|
1737
|
-
assert.ok(firstBody.nextCursor);
|
|
1738
|
-
const second = await fetch(`${baseUrl}/api/memory/chapterhouse?store=observations&tier=hot&cursor=${encodeURIComponent(firstBody.nextCursor)}`, {
|
|
1739
|
-
headers: { authorization: authHeader },
|
|
1740
|
-
});
|
|
1741
|
-
assert.equal(second.status, 200);
|
|
1742
|
-
const secondBody = await second.json();
|
|
1743
|
-
assert.ok(secondBody.entries.length >= 5);
|
|
1744
|
-
assert.equal(secondBody.total, firstBody.total);
|
|
1745
|
-
assert.equal(secondBody.nextCursor, undefined);
|
|
1746
|
-
assert.ok(Math.max(...secondBody.entries.map((entry) => entry.id)) < Math.min(...firstBody.entries.map((entry) => entry.id)));
|
|
1747
|
-
});
|
|
1748
|
-
});
|
|
1749
|
-
test("POST /api/memory/:scope/remember writes an observation or decision", async () => {
|
|
1750
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
1751
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/chapterhouse/remember`, {
|
|
1752
|
-
method: "POST",
|
|
1753
|
-
headers: { "content-type": "application/json" },
|
|
1754
|
-
body: JSON.stringify({ content: "Test observation" }),
|
|
1755
|
-
});
|
|
1756
|
-
assert.equal(unauthorized.status, 401);
|
|
1757
|
-
const obs = await fetch(`${baseUrl}/api/memory/chapterhouse/remember`, {
|
|
1758
|
-
method: "POST",
|
|
1759
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1760
|
-
body: JSON.stringify({ content: "Chapterhouse uses SQLite for memory." }),
|
|
1761
|
-
});
|
|
1762
|
-
assert.equal(obs.status, 200);
|
|
1763
|
-
const obsBody = await obs.json();
|
|
1764
|
-
assert.equal(obsBody.ok, true);
|
|
1765
|
-
assert.ok(typeof obsBody.id === "string");
|
|
1766
|
-
const dec = await fetch(`${baseUrl}/api/memory/chapterhouse/remember`, {
|
|
1767
|
-
method: "POST",
|
|
1768
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1769
|
-
body: JSON.stringify({ kind: "decision", title: "Use SQLite", content: "SQLite chosen for persistence." }),
|
|
1770
|
-
});
|
|
1771
|
-
assert.equal(dec.status, 200);
|
|
1772
|
-
const decBody = await dec.json();
|
|
1773
|
-
assert.equal(decBody.ok, true);
|
|
1774
|
-
const noTitle = await fetch(`${baseUrl}/api/memory/chapterhouse/remember`, {
|
|
1775
|
-
method: "POST",
|
|
1776
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1777
|
-
body: JSON.stringify({ kind: "decision", content: "Missing title." }),
|
|
1778
|
-
});
|
|
1779
|
-
assert.equal(noTitle.status, 400);
|
|
1780
|
-
const notFound = await fetch(`${baseUrl}/api/memory/missing-scope/remember`, {
|
|
1781
|
-
method: "POST",
|
|
1782
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1783
|
-
body: JSON.stringify({ content: "Will not land." }),
|
|
1784
|
-
});
|
|
1785
|
-
assert.equal(notFound.status, 404);
|
|
1786
|
-
});
|
|
1787
|
-
});
|
|
1788
|
-
test("GET /api/memory/inbox lists pending proposals", async () => {
|
|
1789
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
1790
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/inbox`);
|
|
1791
|
-
assert.equal(unauthorized.status, 401);
|
|
1792
|
-
const empty = await fetch(`${baseUrl}/api/memory/inbox`, {
|
|
1793
|
-
headers: { authorization: authHeader },
|
|
1794
|
-
});
|
|
1795
|
-
assert.equal(empty.status, 200);
|
|
1796
|
-
const body = await empty.json();
|
|
1797
|
-
assert.ok(Array.isArray(body.items));
|
|
1798
|
-
assert.ok(typeof body.total === "number");
|
|
1799
|
-
assert.equal(body.items.length, body.total);
|
|
1800
|
-
});
|
|
1801
|
-
});
|
|
1802
|
-
test("POST /api/memory/inbox/:id/route accepts and rejects proposals", async () => {
|
|
1803
|
-
await withStartedServer(async ({ baseUrl, authHeader }) => {
|
|
1804
|
-
// First queue a proposal via the remember endpoint so we have something to route
|
|
1805
|
-
const rememberRes = await fetch(`${baseUrl}/api/memory/chapterhouse/remember`, {
|
|
1806
|
-
method: "POST",
|
|
1807
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1808
|
-
body: JSON.stringify({ content: "Proposal for inbox routing test." }),
|
|
1809
|
-
});
|
|
1810
|
-
assert.equal(rememberRes.status, 200);
|
|
1811
|
-
const notFound = await fetch(`${baseUrl}/api/memory/inbox/99999/route`, {
|
|
1812
|
-
method: "POST",
|
|
1813
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1814
|
-
body: JSON.stringify({ action: "accept" }),
|
|
1815
|
-
});
|
|
1816
|
-
assert.equal(notFound.status, 404);
|
|
1817
|
-
const badId = await fetch(`${baseUrl}/api/memory/inbox/not-a-number/route`, {
|
|
1818
|
-
method: "POST",
|
|
1819
|
-
headers: { authorization: authHeader, "content-type": "application/json" },
|
|
1820
|
-
body: JSON.stringify({ action: "accept" }),
|
|
1821
|
-
});
|
|
1822
|
-
assert.equal(badId.status, 400);
|
|
1823
|
-
const unauthorized = await fetch(`${baseUrl}/api/memory/inbox/1/route`, {
|
|
1824
|
-
method: "POST",
|
|
1825
|
-
headers: { "content-type": "application/json" },
|
|
1826
|
-
body: JSON.stringify({ action: "accept" }),
|
|
1827
|
-
});
|
|
1828
|
-
assert.equal(unauthorized.status, 401);
|
|
1829
|
-
});
|
|
1830
|
-
});
|
|
1831
1550
|
test("GET /api/wiki/korg/sessions returns grouped active research sessions", async () => {
|
|
1832
1551
|
await withStartedServer(async ({ baseUrl, authHeader, testRoot }) => {
|
|
1833
1552
|
const db = new Database(getProjectDbPath(testRoot));
|
package/dist/config.js
CHANGED
|
@@ -68,24 +68,7 @@ const configSchema = z.object({
|
|
|
68
68
|
CHAPTERHOUSE_SESSION_MAX_ACTIVE: z.string().optional(),
|
|
69
69
|
CHAPTERHOUSE_ORCHESTRATOR_TIMEOUT_MS: z.string().optional(),
|
|
70
70
|
CHAPTERHOUSE_JSON_LIMIT: z.string().optional(),
|
|
71
|
-
|
|
72
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_TURNS: z.string().optional(),
|
|
73
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_ON_SCOPE_CHANGE: z.string().optional(),
|
|
74
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_MIN_TURNS_FOR_SCOPE_FIRE: z.string().optional(),
|
|
75
|
-
CHAPTERHOUSE_MEMORY_INJECT: z.string().optional(),
|
|
76
|
-
CHAPTERHOUSE_MEMORY_AUTO_ACCEPT: z.string().optional(),
|
|
77
|
-
CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED: z.string().optional(),
|
|
78
|
-
CHAPTERHOUSE_MEMORY_EOT_FRICTION_ENABLED: z.string().optional(),
|
|
79
|
-
CHAPTERHOUSE_MEMORY_HOOKS_ENABLED: z.string().optional(),
|
|
80
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED: z.string().optional(),
|
|
81
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_TURNS: z.string().optional(),
|
|
82
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_SIMILARITY_THRESHOLD: z.string().optional(),
|
|
83
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_DUPLICATE_THRESHOLD: z.string().optional(),
|
|
84
|
-
CHAPTERHOUSE_MEMORY_DECAY_DAYS: z.string().optional(),
|
|
85
|
-
CHAPTERHOUSE_MEMORY_INBOX_RETENTION_DAYS: z.string().optional(),
|
|
86
|
-
CHAPTERHOUSE_MEMORY_TIERING_ENABLED: z.string().optional(),
|
|
87
|
-
CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST: z.string().optional(),
|
|
88
|
-
CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS: z.string().optional(),
|
|
71
|
+
CHAPTERHOUSE_MEMORY_ENABLED: z.string().optional(),
|
|
89
72
|
CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED: z.string().optional(),
|
|
90
73
|
CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR: z.string().optional(),
|
|
91
74
|
CHAPTERHOUSE_PKB_TRUTH_REWRITE_BUDGET: z.string().optional(),
|
|
@@ -158,28 +141,6 @@ function parsePositiveIntegerEnv(name, rawValue, defaultValue) {
|
|
|
158
141
|
}
|
|
159
142
|
return parsed;
|
|
160
143
|
}
|
|
161
|
-
function parsePositiveNumberEnv(name, rawValue, defaultValue) {
|
|
162
|
-
const normalized = rawValue?.trim();
|
|
163
|
-
if (!normalized) {
|
|
164
|
-
return defaultValue;
|
|
165
|
-
}
|
|
166
|
-
const parsed = Number(normalized);
|
|
167
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
168
|
-
throw new Error(`${name} must be a positive number, got: "${rawValue}"`);
|
|
169
|
-
}
|
|
170
|
-
return parsed;
|
|
171
|
-
}
|
|
172
|
-
function parseUnitIntervalEnv(name, rawValue, defaultValue) {
|
|
173
|
-
const normalized = rawValue?.trim();
|
|
174
|
-
if (!normalized) {
|
|
175
|
-
return defaultValue;
|
|
176
|
-
}
|
|
177
|
-
const parsed = Number(normalized);
|
|
178
|
-
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
179
|
-
throw new Error(`${name} must be a number between 0 and 1, got: "${rawValue}"`);
|
|
180
|
-
}
|
|
181
|
-
return parsed;
|
|
182
|
-
}
|
|
183
144
|
function parseHourEnv(name, rawValue, defaultValue) {
|
|
184
145
|
const normalized = rawValue?.trim();
|
|
185
146
|
if (!normalized) {
|
|
@@ -342,15 +303,6 @@ export function parseRuntimeConfig(env, options = {}) {
|
|
|
342
303
|
const apiRateLimitSseMaxConnections = parsePositiveIntegerEnv("API_RATE_LIMIT_SSE_MAX_CONNECTIONS", raw.API_RATE_LIMIT_SSE_MAX_CONNECTIONS, DEFAULT_API_RATE_LIMIT_SSE_MAX_CONNECTIONS);
|
|
343
304
|
const sseBufferCapacity = parsePositiveIntegerEnv("CHAPTERHOUSE_SSE_BUFFER_CAPACITY", raw.CHAPTERHOUSE_SSE_BUFFER_CAPACITY, DEFAULT_SSE_BUFFER_CAPACITY);
|
|
344
305
|
const sseReplayLimit = parsePositiveIntegerEnv("CHAPTERHOUSE_SSE_REPLAY_LIMIT", raw.CHAPTERHOUSE_SSE_REPLAY_LIMIT, DEFAULT_SSE_REPLAY_LIMIT);
|
|
345
|
-
const memoryCheckpointTurns = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_CHECKPOINT_TURNS", raw.CHAPTERHOUSE_MEMORY_CHECKPOINT_TURNS, 5);
|
|
346
|
-
const memoryCheckpointMinTurnsForScopeFire = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_CHECKPOINT_MIN_TURNS_FOR_SCOPE_FIRE", raw.CHAPTERHOUSE_MEMORY_CHECKPOINT_MIN_TURNS_FOR_SCOPE_FIRE, 2);
|
|
347
|
-
const memoryHousekeepingTurns = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_HOUSEKEEPING_TURNS", raw.CHAPTERHOUSE_MEMORY_HOUSEKEEPING_TURNS, 50);
|
|
348
|
-
const memoryHousekeepingSimilarityThreshold = parseUnitIntervalEnv("CHAPTERHOUSE_MEMORY_HOUSEKEEPING_SIMILARITY_THRESHOLD", raw.CHAPTERHOUSE_MEMORY_HOUSEKEEPING_SIMILARITY_THRESHOLD, 0.8);
|
|
349
|
-
const memoryCheckpointDuplicateThreshold = parseUnitIntervalEnv("CHAPTERHOUSE_MEMORY_CHECKPOINT_DUPLICATE_THRESHOLD", raw.CHAPTERHOUSE_MEMORY_CHECKPOINT_DUPLICATE_THRESHOLD, 0.85);
|
|
350
|
-
const memoryDecayDays = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_DECAY_DAYS", raw.CHAPTERHOUSE_MEMORY_DECAY_DAYS, 30);
|
|
351
|
-
const memoryInboxRetentionDays = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_INBOX_RETENTION_DAYS", raw.CHAPTERHOUSE_MEMORY_INBOX_RETENTION_DAYS, 7);
|
|
352
|
-
const memoryHotRecallBoost = parsePositiveNumberEnv("CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST", raw.CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST, 1.5);
|
|
353
|
-
const memoryHotAgeDays = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS", raw.CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS, 30);
|
|
354
306
|
const pkbConsolidationEnabled = parseBooleanEnv("CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED", raw.CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED, true);
|
|
355
307
|
const pkbConsolidationHour = parseHourEnv("CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR", raw.CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR, 3);
|
|
356
308
|
const jsonBodyLimit = raw.CHAPTERHOUSE_JSON_LIMIT?.trim() || DEFAULT_JSON_BODY_LIMIT;
|
|
@@ -422,24 +374,7 @@ export function parseRuntimeConfig(env, options = {}) {
|
|
|
422
374
|
orchestratorTimeoutMs,
|
|
423
375
|
chatSseEnabled: parseZeroOneEnv("CHAPTERHOUSE_CHAT_SSE", raw.CHAPTERHOUSE_CHAT_SSE, true),
|
|
424
376
|
jsonBodyLimit,
|
|
425
|
-
|
|
426
|
-
memoryCheckpointTurns,
|
|
427
|
-
memoryCheckpointOnScopeChange: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_CHECKPOINT_ON_SCOPE_CHANGE", raw.CHAPTERHOUSE_MEMORY_CHECKPOINT_ON_SCOPE_CHANGE, true),
|
|
428
|
-
memoryCheckpointMinTurnsForScopeFire,
|
|
429
|
-
memoryInjectEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_INJECT", raw.CHAPTERHOUSE_MEMORY_INJECT, true),
|
|
430
|
-
memoryAutoAcceptEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_AUTO_ACCEPT", raw.CHAPTERHOUSE_MEMORY_AUTO_ACCEPT, true),
|
|
431
|
-
memoryEndOfTaskHookEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED", raw.CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED, true),
|
|
432
|
-
memoryEndOfTaskFrictionEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_EOT_FRICTION_ENABLED", raw.CHAPTERHOUSE_MEMORY_EOT_FRICTION_ENABLED, false),
|
|
433
|
-
memoryHooksEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_HOOKS_ENABLED", raw.CHAPTERHOUSE_MEMORY_HOOKS_ENABLED, true),
|
|
434
|
-
memoryHousekeepingEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED", raw.CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED, true),
|
|
435
|
-
memoryHousekeepingTurns,
|
|
436
|
-
memoryHousekeepingSimilarityThreshold,
|
|
437
|
-
memoryCheckpointDuplicateThreshold,
|
|
438
|
-
memoryDecayDays,
|
|
439
|
-
memoryInboxRetentionDays,
|
|
440
|
-
memoryTieringEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_TIERING_ENABLED", raw.CHAPTERHOUSE_MEMORY_TIERING_ENABLED, true),
|
|
441
|
-
memoryHotRecallBoost,
|
|
442
|
-
memoryHotAgeDays,
|
|
377
|
+
memoryEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_ENABLED", raw.CHAPTERHOUSE_MEMORY_ENABLED, true),
|
|
443
378
|
pkbConsolidationEnabled,
|
|
444
379
|
pkbConsolidationHour,
|
|
445
380
|
pkbTruthRewriteBudget,
|
|
@@ -507,24 +442,7 @@ export const config = {
|
|
|
507
442
|
get orchestratorTimeoutMs() { return currentRuntimeConfig().orchestratorTimeoutMs; },
|
|
508
443
|
get chatSseEnabled() { return currentRuntimeConfig().chatSseEnabled; },
|
|
509
444
|
get jsonBodyLimit() { return currentRuntimeConfig().jsonBodyLimit; },
|
|
510
|
-
get
|
|
511
|
-
get memoryCheckpointTurns() { return currentRuntimeConfig().memoryCheckpointTurns; },
|
|
512
|
-
get memoryCheckpointOnScopeChange() { return currentRuntimeConfig().memoryCheckpointOnScopeChange; },
|
|
513
|
-
get memoryCheckpointMinTurnsForScopeFire() { return currentRuntimeConfig().memoryCheckpointMinTurnsForScopeFire; },
|
|
514
|
-
get memoryInjectEnabled() { return currentRuntimeConfig().memoryInjectEnabled; },
|
|
515
|
-
get memoryAutoAcceptEnabled() { return currentRuntimeConfig().memoryAutoAcceptEnabled; },
|
|
516
|
-
get memoryEndOfTaskHookEnabled() { return currentRuntimeConfig().memoryEndOfTaskHookEnabled; },
|
|
517
|
-
get memoryEndOfTaskFrictionEnabled() { return currentRuntimeConfig().memoryEndOfTaskFrictionEnabled; },
|
|
518
|
-
get memoryHooksEnabled() { return currentRuntimeConfig().memoryHooksEnabled; },
|
|
519
|
-
get memoryHousekeepingEnabled() { return currentRuntimeConfig().memoryHousekeepingEnabled; },
|
|
520
|
-
get memoryHousekeepingTurns() { return currentRuntimeConfig().memoryHousekeepingTurns; },
|
|
521
|
-
get memoryHousekeepingSimilarityThreshold() { return currentRuntimeConfig().memoryHousekeepingSimilarityThreshold; },
|
|
522
|
-
get memoryCheckpointDuplicateThreshold() { return currentRuntimeConfig().memoryCheckpointDuplicateThreshold; },
|
|
523
|
-
get memoryDecayDays() { return currentRuntimeConfig().memoryDecayDays; },
|
|
524
|
-
get memoryInboxRetentionDays() { return currentRuntimeConfig().memoryInboxRetentionDays; },
|
|
525
|
-
get memoryTieringEnabled() { return currentRuntimeConfig().memoryTieringEnabled; },
|
|
526
|
-
get memoryHotRecallBoost() { return currentRuntimeConfig().memoryHotRecallBoost; },
|
|
527
|
-
get memoryHotAgeDays() { return currentRuntimeConfig().memoryHotAgeDays; },
|
|
445
|
+
get memoryEnabled() { return currentRuntimeConfig().memoryEnabled; },
|
|
528
446
|
get pkbConsolidationEnabled() { return currentRuntimeConfig().pkbConsolidationEnabled; },
|
|
529
447
|
get pkbConsolidationHour() { return currentRuntimeConfig().pkbConsolidationHour; },
|
|
530
448
|
get pkbTruthRewriteBudget() { return currentRuntimeConfig().pkbTruthRewriteBudget; },
|
package/dist/config.test.js
CHANGED
|
@@ -125,130 +125,12 @@ test("parses configurable JSON body and consolidation truth rewrite limits", asy
|
|
|
125
125
|
assert.equal(parsedExplicit.jsonBodyLimit, "8mb");
|
|
126
126
|
assert.equal(parsedExplicit.pkbTruthRewriteBudget, 7);
|
|
127
127
|
});
|
|
128
|
-
test("defaults memory
|
|
128
|
+
test("defaults memory enabled on and honors CHAPTERHOUSE_MEMORY_ENABLED overrides", async () => {
|
|
129
129
|
const configModule = await import("./config.js");
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
135
|
-
const parsedTen = configModule.parseRuntimeConfig({
|
|
136
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_TURNS: "10",
|
|
137
|
-
});
|
|
138
|
-
assert.equal(parsedDefault.memoryCheckpointTurns, 5);
|
|
139
|
-
assert.equal(parsedThree.memoryCheckpointTurns, 3);
|
|
140
|
-
assert.equal(parsedTen.memoryCheckpointTurns, 10);
|
|
141
|
-
});
|
|
142
|
-
test("parses PKB consolidation defaults and explicit hour overrides", async () => {
|
|
143
|
-
const configModule = await import("./config.js");
|
|
144
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
145
|
-
const parsedDefault = configModule.parseRuntimeConfig({});
|
|
146
|
-
const parsedExplicit = configModule.parseRuntimeConfig({
|
|
147
|
-
CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED: "false",
|
|
148
|
-
CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR: "5",
|
|
149
|
-
});
|
|
150
|
-
assert.equal(parsedDefault.pkbConsolidationEnabled, true);
|
|
151
|
-
assert.equal(parsedDefault.pkbConsolidationHour, 3);
|
|
152
|
-
assert.equal(parsedExplicit.pkbConsolidationEnabled, false);
|
|
153
|
-
assert.equal(parsedExplicit.pkbConsolidationHour, 5);
|
|
154
|
-
});
|
|
155
|
-
test("rejects invalid PKB consolidation hours outside 0-23", async () => {
|
|
156
|
-
const configModule = await import("./config.js");
|
|
157
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
158
|
-
assert.throws(() => configModule.parseRuntimeConfig({
|
|
159
|
-
CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR: "24",
|
|
160
|
-
}), /CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR must be an integer between 0 and 23/);
|
|
161
|
-
});
|
|
162
|
-
test("defaults memory injection on and still honors explicit CHAPTERHOUSE_MEMORY_INJECT overrides", async () => {
|
|
163
|
-
const configModule = await import("./config.js");
|
|
164
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
165
|
-
const parsedDefault = configModule.parseRuntimeConfig({});
|
|
166
|
-
const parsedDisabled = configModule.parseRuntimeConfig({
|
|
167
|
-
CHAPTERHOUSE_MEMORY_INJECT: "0",
|
|
168
|
-
});
|
|
169
|
-
const parsedEnabled = configModule.parseRuntimeConfig({
|
|
170
|
-
CHAPTERHOUSE_MEMORY_INJECT: "1",
|
|
171
|
-
});
|
|
172
|
-
assert.equal(parsedDefault.memoryInjectEnabled, true);
|
|
173
|
-
assert.equal(parsedDisabled.memoryInjectEnabled, false);
|
|
174
|
-
assert.equal(parsedEnabled.memoryInjectEnabled, true);
|
|
175
|
-
});
|
|
176
|
-
test("defaults memory checkpoint extraction on and still honors explicit CHAPTERHOUSE_MEMORY_CHECKPOINT_ENABLED overrides", async () => {
|
|
177
|
-
const configModule = await import("./config.js");
|
|
178
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
179
|
-
const parsedDefault = configModule.parseRuntimeConfig({});
|
|
180
|
-
const parsedDisabled = configModule.parseRuntimeConfig({
|
|
181
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_ENABLED: "0",
|
|
182
|
-
});
|
|
183
|
-
const parsedEnabled = configModule.parseRuntimeConfig({
|
|
184
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_ENABLED: "1",
|
|
185
|
-
});
|
|
186
|
-
assert.equal(parsedDefault.memoryCheckpointEnabled, true);
|
|
187
|
-
assert.equal(parsedDisabled.memoryCheckpointEnabled, false);
|
|
188
|
-
assert.equal(parsedEnabled.memoryCheckpointEnabled, true);
|
|
189
|
-
});
|
|
190
|
-
test("defaults end-of-task memory processing on and still honors explicit CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED overrides", async () => {
|
|
191
|
-
const configModule = await import("./config.js");
|
|
192
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
193
|
-
const parsedDefault = configModule.parseRuntimeConfig({});
|
|
194
|
-
const parsedDisabled = configModule.parseRuntimeConfig({
|
|
195
|
-
CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED: "0",
|
|
196
|
-
});
|
|
197
|
-
const parsedEnabled = configModule.parseRuntimeConfig({
|
|
198
|
-
CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED: "1",
|
|
199
|
-
});
|
|
200
|
-
assert.equal(parsedDefault.memoryEndOfTaskHookEnabled, true);
|
|
201
|
-
assert.equal(parsedDisabled.memoryEndOfTaskHookEnabled, false);
|
|
202
|
-
assert.equal(parsedEnabled.memoryEndOfTaskHookEnabled, true);
|
|
203
|
-
});
|
|
204
|
-
test("parses memory housekeeping config defaults and overrides", async () => {
|
|
205
|
-
const configModule = await import("./config.js");
|
|
206
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
207
|
-
const parsedDefault = configModule.parseRuntimeConfig({});
|
|
208
|
-
const parsedOverride = configModule.parseRuntimeConfig({
|
|
209
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED: "0",
|
|
210
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_TURNS: "12",
|
|
211
|
-
CHAPTERHOUSE_MEMORY_DECAY_DAYS: "45",
|
|
212
|
-
CHAPTERHOUSE_MEMORY_INBOX_RETENTION_DAYS: "14",
|
|
213
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_SIMILARITY_THRESHOLD: "0.91",
|
|
214
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_DUPLICATE_THRESHOLD: "0.72",
|
|
215
|
-
});
|
|
216
|
-
assert.equal(parsedDefault.memoryHousekeepingEnabled, true);
|
|
217
|
-
assert.equal(parsedDefault.memoryHousekeepingTurns, 50);
|
|
218
|
-
assert.equal(parsedDefault.memoryDecayDays, 30);
|
|
219
|
-
assert.equal(parsedDefault.memoryInboxRetentionDays, 7);
|
|
220
|
-
assert.equal(parsedDefault.memoryHousekeepingSimilarityThreshold, 0.8);
|
|
221
|
-
assert.equal(parsedDefault.memoryCheckpointDuplicateThreshold, 0.85);
|
|
222
|
-
assert.equal(parsedOverride.memoryHousekeepingEnabled, false);
|
|
223
|
-
assert.equal(parsedOverride.memoryHousekeepingTurns, 12);
|
|
224
|
-
assert.equal(parsedOverride.memoryDecayDays, 45);
|
|
225
|
-
assert.equal(parsedOverride.memoryInboxRetentionDays, 14);
|
|
226
|
-
assert.equal(parsedOverride.memoryHousekeepingSimilarityThreshold, 0.91);
|
|
227
|
-
assert.equal(parsedOverride.memoryCheckpointDuplicateThreshold, 0.72);
|
|
228
|
-
});
|
|
229
|
-
test("rejects memory similarity thresholds outside the 0..1 range", async () => {
|
|
230
|
-
const configModule = await import("./config.js");
|
|
231
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
232
|
-
assert.throws(() => configModule.parseRuntimeConfig({
|
|
233
|
-
CHAPTERHOUSE_MEMORY_HOUSEKEEPING_SIMILARITY_THRESHOLD: "1.1",
|
|
234
|
-
}), /CHAPTERHOUSE_MEMORY_HOUSEKEEPING_SIMILARITY_THRESHOLD must be a number between 0 and 1/);
|
|
235
|
-
assert.throws(() => configModule.parseRuntimeConfig({
|
|
236
|
-
CHAPTERHOUSE_MEMORY_CHECKPOINT_DUPLICATE_THRESHOLD: "-0.1",
|
|
237
|
-
}), /CHAPTERHOUSE_MEMORY_CHECKPOINT_DUPLICATE_THRESHOLD must be a number between 0 and 1/);
|
|
238
|
-
});
|
|
239
|
-
test("defaults automatic proposal acceptance on and still honors explicit CHAPTERHOUSE_MEMORY_AUTO_ACCEPT overrides", async () => {
|
|
240
|
-
const configModule = await import("./config.js");
|
|
241
|
-
assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
|
|
242
|
-
const parsedDefault = configModule.parseRuntimeConfig({});
|
|
243
|
-
const parsedDisabled = configModule.parseRuntimeConfig({
|
|
244
|
-
CHAPTERHOUSE_MEMORY_AUTO_ACCEPT: "0",
|
|
245
|
-
});
|
|
246
|
-
const parsedEnabled = configModule.parseRuntimeConfig({
|
|
247
|
-
CHAPTERHOUSE_MEMORY_AUTO_ACCEPT: "1",
|
|
248
|
-
});
|
|
249
|
-
assert.equal(parsedDefault.memoryAutoAcceptEnabled, true);
|
|
250
|
-
assert.equal(parsedDisabled.memoryAutoAcceptEnabled, false);
|
|
251
|
-
assert.equal(parsedEnabled.memoryAutoAcceptEnabled, true);
|
|
130
|
+
const parse = configModule.parseRuntimeConfig;
|
|
131
|
+
assert.equal(parse({}).memoryEnabled, true);
|
|
132
|
+
assert.equal(parse({ CHAPTERHOUSE_MEMORY_ENABLED: "0" }).memoryEnabled, false);
|
|
133
|
+
assert.equal(parse({ CHAPTERHOUSE_MEMORY_ENABLED: "1" }).memoryEnabled, true);
|
|
252
134
|
});
|
|
253
135
|
test("prefers COPILOT_TOKEN over GITHUB_TOKEN for Copilot SDK auth", async () => {
|
|
254
136
|
const configModule = await import("./config.js");
|