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.
Files changed (116) hide show
  1. package/dist/api/route-coverage.test.js +1 -3
  2. package/dist/api/server.js +0 -2
  3. package/dist/api/server.test.js +0 -281
  4. package/dist/config.js +3 -85
  5. package/dist/config.test.js +5 -123
  6. package/dist/copilot/agents.js +13 -10
  7. package/dist/copilot/agents.test.js +10 -11
  8. package/dist/copilot/memory-coordinator.js +12 -227
  9. package/dist/copilot/memory-coordinator.test.js +31 -250
  10. package/dist/copilot/orchestrator.js +8 -66
  11. package/dist/copilot/orchestrator.test.js +9 -467
  12. package/dist/copilot/skills.js +15 -1
  13. package/dist/copilot/system-message.js +9 -15
  14. package/dist/copilot/system-message.test.js +9 -22
  15. package/dist/copilot/tools/index.js +3 -3
  16. package/dist/copilot/tools-deps.js +1 -1
  17. package/dist/copilot/tools.agent.test.js +6 -0
  18. package/dist/copilot/tools.inventory.test.js +1 -14
  19. package/dist/daemon.js +7 -9
  20. package/dist/memory/assets.js +33 -0
  21. package/dist/memory/domains.js +58 -0
  22. package/dist/memory/domains.test.js +47 -0
  23. package/dist/memory/git.js +66 -0
  24. package/dist/memory/git.test.js +32 -0
  25. package/dist/memory/history.js +19 -0
  26. package/dist/memory/hottier.js +32 -0
  27. package/dist/memory/hottier.test.js +33 -0
  28. package/dist/memory/index.js +5 -13
  29. package/dist/memory/instructions.js +17 -0
  30. package/dist/memory/manager.js +84 -0
  31. package/dist/memory/markdown.js +78 -0
  32. package/dist/memory/markdown.test.js +42 -0
  33. package/dist/memory/mutex.js +18 -0
  34. package/dist/memory/path-guard.js +26 -0
  35. package/dist/memory/path-guard.test.js +27 -0
  36. package/dist/memory/paths.js +12 -0
  37. package/dist/memory/reconcile.js +75 -0
  38. package/dist/memory/reconcile.test.js +50 -0
  39. package/dist/memory/scaffold.js +37 -0
  40. package/dist/memory/scaffold.test.js +52 -0
  41. package/dist/memory/tools/commit-wrapper.js +32 -0
  42. package/dist/memory/tools/domains.js +73 -0
  43. package/dist/memory/tools/domains.test.js +66 -0
  44. package/dist/memory/tools/git.js +52 -0
  45. package/dist/memory/tools/index.js +25 -0
  46. package/dist/memory/tools/read.js +101 -0
  47. package/dist/memory/tools/read.test.js +69 -0
  48. package/dist/memory/tools/search.js +103 -0
  49. package/dist/memory/tools/search.test.js +63 -0
  50. package/dist/memory/tools/sessions.js +45 -0
  51. package/dist/memory/tools/sessions.test.js +74 -0
  52. package/dist/memory/tools/shared.js +7 -0
  53. package/dist/memory/tools/write.js +116 -0
  54. package/dist/memory/tools/write.test.js +107 -0
  55. package/dist/memory/walk.js +39 -0
  56. package/dist/store/repositories/sessions.js +40 -0
  57. package/dist/wiki/consolidation.js +3 -31
  58. package/dist/wiki/consolidation.test.js +0 -19
  59. package/package.json +1 -1
  60. package/skills/system/evolve/SKILL.md +131 -0
  61. package/skills/system/foresight/SKILL.md +116 -0
  62. package/skills/system/history/SKILL.md +58 -0
  63. package/skills/system/housekeeping/SKILL.md +185 -0
  64. package/skills/system/reflect/SKILL.md +214 -0
  65. package/skills/system/scenario/SKILL.md +198 -0
  66. package/skills/system/setup/SKILL.md +113 -0
  67. package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
  68. package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
  69. package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
  70. package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
  71. package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
  72. package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
  73. package/web/dist/index.html +1 -1
  74. package/dist/api/routes/memory.js +0 -475
  75. package/dist/api/routes/memory.test.js +0 -108
  76. package/dist/copilot/tools/memory.js +0 -678
  77. package/dist/copilot/tools.memory.test.js +0 -590
  78. package/dist/memory/action-items.js +0 -100
  79. package/dist/memory/action-items.test.js +0 -83
  80. package/dist/memory/active-scope.js +0 -78
  81. package/dist/memory/active-scope.test.js +0 -80
  82. package/dist/memory/checkpoint-prompt.js +0 -71
  83. package/dist/memory/checkpoint.js +0 -274
  84. package/dist/memory/checkpoint.test.js +0 -275
  85. package/dist/memory/decisions.js +0 -54
  86. package/dist/memory/decisions.test.js +0 -92
  87. package/dist/memory/entities.js +0 -70
  88. package/dist/memory/entities.test.js +0 -65
  89. package/dist/memory/eot.js +0 -459
  90. package/dist/memory/eot.test.js +0 -949
  91. package/dist/memory/hooks.js +0 -149
  92. package/dist/memory/hooks.test.js +0 -325
  93. package/dist/memory/hot-tier.js +0 -283
  94. package/dist/memory/hot-tier.test.js +0 -275
  95. package/dist/memory/housekeeping-scheduler.js +0 -187
  96. package/dist/memory/housekeeping-scheduler.test.js +0 -236
  97. package/dist/memory/housekeeping.js +0 -497
  98. package/dist/memory/housekeeping.test.js +0 -410
  99. package/dist/memory/inbox.js +0 -83
  100. package/dist/memory/inbox.test.js +0 -178
  101. package/dist/memory/migration.js +0 -244
  102. package/dist/memory/migration.test.js +0 -108
  103. package/dist/memory/observations.js +0 -46
  104. package/dist/memory/observations.test.js +0 -86
  105. package/dist/memory/recall.js +0 -269
  106. package/dist/memory/recall.test.js +0 -265
  107. package/dist/memory/reflect.js +0 -273
  108. package/dist/memory/reflect.test.js +0 -256
  109. package/dist/memory/scope-lock.js +0 -26
  110. package/dist/memory/scope-lock.test.js +0 -118
  111. package/dist/memory/scopes.js +0 -89
  112. package/dist/memory/scopes.test.js +0 -176
  113. package/dist/memory/tiering.js +0 -223
  114. package/dist/memory/tiering.test.js +0 -323
  115. package/dist/memory/types.js +0 -2
  116. 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", "memory.ts", "projects.ts", "sessions.ts", "system.ts", "wiki.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", () => {
@@ -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 }));
@@ -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
- CHAPTERHOUSE_MEMORY_CHECKPOINT_ENABLED: z.string().optional(),
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
- memoryCheckpointEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_CHECKPOINT_ENABLED", raw.CHAPTERHOUSE_MEMORY_CHECKPOINT_ENABLED, true),
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 memoryCheckpointEnabled() { return currentRuntimeConfig().memoryCheckpointEnabled; },
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; },
@@ -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 checkpoint turns to 5 and parses integer overrides", async () => {
128
+ test("defaults memory enabled on and honors CHAPTERHOUSE_MEMORY_ENABLED overrides", async () => {
129
129
  const configModule = await import("./config.js");
130
- assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
131
- const parsedDefault = configModule.parseRuntimeConfig({});
132
- const parsedThree = configModule.parseRuntimeConfig({
133
- CHAPTERHOUSE_MEMORY_CHECKPOINT_TURNS: "3",
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");