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
@@ -0,0 +1,116 @@
1
+ // Write tools: cog_write, cog_edit, cog_append, cog_move. Each mutating tool
2
+ // runs through withMemoryWrite (write-lock + auto-commit).
3
+ // Ported from chgo's tools_write.go.
4
+ import { defineTool } from "@github/copilot-sdk";
5
+ import { z } from "zod";
6
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
7
+ import { dirname } from "path";
8
+ import { resolveMemoryPath } from "../path-guard.js";
9
+ import { hasL0Header } from "../markdown.js";
10
+ import { withMemoryWrite } from "./commit-wrapper.js";
11
+ import { toolError } from "./shared.js";
12
+ export function createWriteTools(manager) {
13
+ const root = manager.paths.memoryRoot;
14
+ return [
15
+ defineTool("cog_write", {
16
+ description: "Create or overwrite a memory file. A .md file must begin with an " +
17
+ "<!-- L0: summary --> line within the first few lines.",
18
+ parameters: z.object({
19
+ path: z.string().describe("File path relative to the memory root"),
20
+ content: z.string().describe("Full file content"),
21
+ }),
22
+ handler: async (args) => {
23
+ try {
24
+ const abs = resolveMemoryPath(root, args.path);
25
+ if (abs.endsWith(".md") && !hasL0Header(args.content)) {
26
+ return "Error: a .md memory file must begin with an <!-- L0: summary --> line";
27
+ }
28
+ return await withMemoryWrite(manager, `memory: write ${args.path}`, () => {
29
+ mkdirSync(dirname(abs), { recursive: true });
30
+ writeFileSync(abs, args.content, "utf8");
31
+ return `wrote ${args.path}`;
32
+ });
33
+ }
34
+ catch (err) {
35
+ return toolError(err);
36
+ }
37
+ },
38
+ }),
39
+ defineTool("cog_edit", {
40
+ description: "Replace an exact, unique string in a memory file (edit in place).",
41
+ parameters: z.object({
42
+ path: z.string().describe("File path"),
43
+ old: z.string().describe("Exact text to replace — must occur exactly once"),
44
+ new: z.string().describe("Replacement text"),
45
+ }),
46
+ handler: async (args) => {
47
+ try {
48
+ const abs = resolveMemoryPath(root, args.path);
49
+ const data = readFileSync(abs, "utf8");
50
+ const occurrences = data.split(args.old).length - 1;
51
+ if (occurrences === 0) {
52
+ return `Error: old string not found in ${args.path}`;
53
+ }
54
+ if (occurrences > 1) {
55
+ return `Error: old string is not unique in ${args.path} — include more context`;
56
+ }
57
+ return await withMemoryWrite(manager, `memory: edit ${args.path}`, () => {
58
+ writeFileSync(abs, data.replace(args.old, args.new), "utf8");
59
+ return `edited ${args.path}`;
60
+ });
61
+ }
62
+ catch (err) {
63
+ return toolError(err);
64
+ }
65
+ },
66
+ }),
67
+ defineTool("cog_append", {
68
+ description: "Append a block of text to a memory file (for append-only files like observations).",
69
+ parameters: z.object({
70
+ path: z.string().describe("File path"),
71
+ content: z.string().describe("Text to append"),
72
+ }),
73
+ handler: async (args) => {
74
+ try {
75
+ const abs = resolveMemoryPath(root, args.path);
76
+ const existing = readFileSync(abs, "utf8");
77
+ let next = existing;
78
+ if (next !== "" && !next.endsWith("\n"))
79
+ next += "\n";
80
+ next += args.content;
81
+ if (!next.endsWith("\n"))
82
+ next += "\n";
83
+ return await withMemoryWrite(manager, `memory: append ${args.path}`, () => {
84
+ writeFileSync(abs, next, "utf8");
85
+ return `appended to ${args.path}`;
86
+ });
87
+ }
88
+ catch (err) {
89
+ return toolError(err);
90
+ }
91
+ },
92
+ }),
93
+ defineTool("cog_move", {
94
+ description: "Move or rename a memory file (used for glacier archival).",
95
+ parameters: z.object({
96
+ from: z.string().describe("Source path"),
97
+ to: z.string().describe("Destination path"),
98
+ }),
99
+ handler: async (args) => {
100
+ try {
101
+ const from = resolveMemoryPath(root, args.from);
102
+ const to = resolveMemoryPath(root, args.to);
103
+ return await withMemoryWrite(manager, `memory: move ${args.from} -> ${args.to}`, () => {
104
+ mkdirSync(dirname(to), { recursive: true });
105
+ renameSync(from, to);
106
+ return `moved ${args.from} -> ${args.to}`;
107
+ });
108
+ }
109
+ catch (err) {
110
+ return toolError(err);
111
+ }
112
+ },
113
+ }),
114
+ ];
115
+ }
116
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1,107 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+ import { gitAvailable, gitLog } from "../git.js";
6
+ import { MemoryManager } from "../manager.js";
7
+ import { createMemoryTools } from "./index.js";
8
+ const sandbox = join(process.cwd(), ".test-work", `mem-tools-write-${process.pid}`);
9
+ function findTool(tools, name) {
10
+ const found = tools.find((t) => t.name === name);
11
+ assert.ok(found, `tool ${name} not found`);
12
+ return found;
13
+ }
14
+ async function call(tool, args) {
15
+ const result = await tool.handler(args, {
16
+ sessionId: "t",
17
+ toolCallId: "t",
18
+ toolName: tool.name,
19
+ arguments: args,
20
+ });
21
+ return String(result);
22
+ }
23
+ async function setup() {
24
+ const mgr = new MemoryManager(sandbox);
25
+ await mgr.ensureReady();
26
+ return { tools: createMemoryTools(mgr), root: mgr.paths.memoryRoot };
27
+ }
28
+ test.beforeEach(() => {
29
+ rmSync(sandbox, { recursive: true, force: true });
30
+ mkdirSync(sandbox, { recursive: true });
31
+ });
32
+ test.after(() => rmSync(sandbox, { recursive: true, force: true }));
33
+ test("cog_write rejects a .md file with no L0 header", async () => {
34
+ if (!gitAvailable())
35
+ return;
36
+ const { tools } = await setup();
37
+ const out = await call(findTool(tools, "cog_write"), {
38
+ path: "personal/note.md",
39
+ content: "# No L0 here\nbody",
40
+ });
41
+ assert.match(out, /<!-- L0: summary -->/);
42
+ });
43
+ test("cog_write accepts a .md file with an L0 header and commits it", async () => {
44
+ if (!gitAvailable())
45
+ return;
46
+ const { tools, root } = await setup();
47
+ const before = (await gitLog(root, 50)).trim().split("\n").length;
48
+ const out = await call(findTool(tools, "cog_write"), {
49
+ path: "personal/note.md",
50
+ content: "<!-- L0: a note -->\n# Note\nbody",
51
+ });
52
+ assert.match(out, /wrote personal\/note\.md/);
53
+ assert.ok(existsSync(join(root, "personal/note.md")));
54
+ const log = await gitLog(root, 50);
55
+ assert.ok(log.includes("memory: write personal/note.md"));
56
+ assert.equal(log.trim().split("\n").length, before + 1, "exactly one new commit");
57
+ });
58
+ test("cog_edit errors on missing and non-unique matches, succeeds when unique", async () => {
59
+ if (!gitAvailable())
60
+ return;
61
+ const { tools } = await setup();
62
+ await call(findTool(tools, "cog_write"), {
63
+ path: "personal/note.md",
64
+ content: "<!-- L0: a note -->\nalpha\nbeta\nalpha",
65
+ });
66
+ assert.match(await call(findTool(tools, "cog_edit"), { path: "personal/note.md", old: "zeta", new: "x" }), /not found/);
67
+ assert.match(await call(findTool(tools, "cog_edit"), { path: "personal/note.md", old: "alpha", new: "x" }), /not unique/);
68
+ assert.match(await call(findTool(tools, "cog_edit"), { path: "personal/note.md", old: "beta", new: "BETA" }), /edited/);
69
+ });
70
+ test("cog_append normalizes newlines", async () => {
71
+ if (!gitAvailable())
72
+ return;
73
+ const { tools, root } = await setup();
74
+ await call(findTool(tools, "cog_write"), {
75
+ path: "personal/observations.md",
76
+ content: "<!-- L0: obs -->\nfirst line",
77
+ });
78
+ await call(findTool(tools, "cog_append"), { path: "personal/observations.md", content: "second line" });
79
+ assert.equal(readFileSync(join(root, "personal/observations.md"), "utf8"), "<!-- L0: obs -->\nfirst line\nsecond line\n");
80
+ });
81
+ test("cog_move relocates a file", async () => {
82
+ if (!gitAvailable())
83
+ return;
84
+ const { tools, root } = await setup();
85
+ await call(findTool(tools, "cog_write"), {
86
+ path: "personal/old.md",
87
+ content: "<!-- L0: x -->\nbody",
88
+ });
89
+ const out = await call(findTool(tools, "cog_move"), {
90
+ from: "personal/old.md",
91
+ to: "glacier/personal/old.md",
92
+ });
93
+ assert.match(out, /moved/);
94
+ assert.ok(!existsSync(join(root, "personal/old.md")));
95
+ assert.ok(existsSync(join(root, "glacier/personal/old.md")));
96
+ });
97
+ test("cog_write rejects a path escaping the memory root", async () => {
98
+ if (!gitAvailable())
99
+ return;
100
+ const { tools } = await setup();
101
+ const out = await call(findTool(tools, "cog_write"), {
102
+ path: "../escape.md",
103
+ content: "<!-- L0: x -->\nbody",
104
+ });
105
+ assert.match(out, /escapes the memory root/);
106
+ });
107
+ //# sourceMappingURL=write.test.js.map
@@ -0,0 +1,39 @@
1
+ // Walks the markdown files of the memory tree (or one domain subtree),
2
+ // skipping the .git directory. Ported from chgo's tools_read.go walkMemoryMarkdown.
3
+ import { readdirSync } from "fs";
4
+ import { join, relative, sep } from "path";
5
+ import { resolveMemoryPath } from "./path-guard.js";
6
+ /**
7
+ * Calls `fn(rel, abs)` for every `.md` file under the memory root, or under a
8
+ * domain subdirectory when `domain` is given. `rel` is slash-separated and
9
+ * relative to the memory root.
10
+ */
11
+ export function walkMemoryMarkdown(memoryRoot, domain, fn) {
12
+ let base = memoryRoot;
13
+ if (domain && domain.trim() !== "") {
14
+ base = resolveMemoryPath(memoryRoot, domain);
15
+ }
16
+ const walk = (dir) => {
17
+ let entries;
18
+ try {
19
+ entries = readdirSync(dir, { withFileTypes: true });
20
+ }
21
+ catch {
22
+ return; // directory may not exist yet
23
+ }
24
+ for (const entry of entries) {
25
+ const full = join(dir, entry.name);
26
+ if (entry.isDirectory()) {
27
+ if (entry.name === ".git")
28
+ continue;
29
+ walk(full);
30
+ }
31
+ else if (entry.name.endsWith(".md")) {
32
+ const rel = relative(memoryRoot, full).split(sep).join("/");
33
+ fn(rel, full);
34
+ }
35
+ }
36
+ };
37
+ walk(base);
38
+ }
39
+ //# sourceMappingURL=walk.js.map
@@ -229,6 +229,46 @@ export function getSessionMessages(sessionKey, limit, options = {}) {
229
229
  return message;
230
230
  });
231
231
  }
232
+ /**
233
+ * Group conversation_log rows into sessions for the cog_sessions tool. Rows are
234
+ * grouped by (session_key, run_id); `agent_completion` rows map to role
235
+ * "assistant"; empty-content rows are skipped. When `since` (an ISO-8601
236
+ * string) is given, only later turns are kept. Sessions are sorted
237
+ * newest-first; `limit` (0 = no cap) bounds the count.
238
+ *
239
+ * Note: conversation_log is pruned to the last 1000 rows, so only recent
240
+ * history is visible — adequate for the reflect skill's cursor model.
241
+ */
242
+ export function getConversationSessions(since, limit = 0) {
243
+ const db = getDb();
244
+ const rows = db.prepare(`SELECT role, content, ts, session_key, run_id, agent_slug FROM conversation_log
245
+ WHERE role IN ('user', 'assistant', 'agent_completion')
246
+ ORDER BY session_key, run_id, id ASC`).all();
247
+ const sinceIso = since ? normalizeSqliteTsToIso(since) : undefined;
248
+ const byKey = new Map();
249
+ for (const r of rows) {
250
+ if (!r.content || r.content.trim() === "")
251
+ continue;
252
+ const at = normalizeSqliteTsToIso(r.ts);
253
+ if (sinceIso && at <= sinceIso)
254
+ continue;
255
+ const key = `${r.session_key}|${r.run_id ?? ""}`;
256
+ let session = byKey.get(key);
257
+ if (!session) {
258
+ session = { sessionKey: r.session_key, runId: r.run_id, startedAt: at, turns: [] };
259
+ byKey.set(key, session);
260
+ }
261
+ session.turns.push({
262
+ role: r.role === "agent_completion" ? "assistant" : r.role,
263
+ content: r.content,
264
+ ts: at,
265
+ agentSlug: r.agent_slug,
266
+ });
267
+ }
268
+ const sessions = Array.from(byKey.values()).filter((s) => s.turns.length > 0);
269
+ sessions.sort((a, b) => (a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : 0));
270
+ return limit > 0 ? sessions.slice(0, limit) : sessions;
271
+ }
232
272
  /**
233
273
  * Append one event to agent_task_events and return the new event.
234
274
  * Uses a transaction so seq is monotonically incremented.
@@ -3,8 +3,6 @@ import { execFileSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, renameSync } from "node:fs";
4
4
  import { dirname, join, basename } from "node:path";
5
5
  import { config } from "../config.js";
6
- import { recordActionItem } from "../memory/action-items.js";
7
- import { getScope } from "../memory/scopes.js";
8
6
  import { getChapterhouseHome, resolveWikiRelativePath } from "../paths.js";
9
7
  import { childLogger } from "../util/logger.js";
10
8
  import { deletePage, listPages, readPage, writePage } from "./fs.js";
@@ -77,13 +75,14 @@ export async function runConsolidationWithDeps(db, partialDeps = {}) {
77
75
  await deps.commitWikiChanges(runAt);
78
76
  return result;
79
77
  }
80
- function createDefaultDeps(db) {
78
+ function createDefaultDeps(_db) {
81
79
  return {
82
80
  now: () => new Date(),
83
81
  synthesizeTruth: synthesizeTruthWithCopilot,
84
82
  rebuildIndex: rebuildWikiIndex,
85
83
  commitWikiChanges: async (runAt) => commitWikiChanges(runAt),
86
- createActionItem: ({ title, detail, source }) => createStaleSessionActionItem(db, { title, detail, source }),
84
+ // Wiki consolidation no longer records action items into agent memory.
85
+ createActionItem: () => false,
87
86
  truthRewriteBudget: config.pkbTruthRewriteBudget,
88
87
  };
89
88
  }
@@ -466,33 +465,6 @@ function notifyStaleSessions(db, deps, runAt) {
466
465
  }
467
466
  return created;
468
467
  }
469
- function createStaleSessionActionItem(db, input) {
470
- const globalScope = getScope("global");
471
- if (!globalScope) {
472
- return false;
473
- }
474
- const existing = recordDuplicateActionItemCheck(db, globalScope.id, input.title);
475
- if (existing) {
476
- return false;
477
- }
478
- recordActionItem({
479
- scope_id: globalScope.id,
480
- title: input.title,
481
- detail: input.detail,
482
- source: input.source,
483
- tier: "warm",
484
- });
485
- return true;
486
- }
487
- function recordDuplicateActionItemCheck(db, scopeId, title) {
488
- const row = db.prepare(`
489
- SELECT 1
490
- FROM mem_action_items
491
- WHERE scope_id = ? AND title = ? AND status IN ('open', 'snoozed')
492
- LIMIT 1
493
- `).get(scopeId, title);
494
- return Boolean(row);
495
- }
496
468
  async function synthesizeTruthWithCopilot(input) {
497
469
  const token = config.copilotAuthToken;
498
470
  if (!token) {
@@ -118,25 +118,6 @@ test("runConsolidation removes orphaned wiki_links rows", async () => {
118
118
  assert.equal(result.linksRepaired, 1);
119
119
  assert.equal(orphanCount.c, 0);
120
120
  });
121
- test("runConsolidation creates an action item for research sessions inactive for 7+ days", async () => {
122
- const { dbModule, wikiFs, consolidation, memory } = await loadModules();
123
- const db = dbModule.getDb();
124
- wikiFs.ensureWikiStructure();
125
- const globalScope = memory.getScope("global");
126
- assert.ok(globalScope);
127
- db.prepare(`
128
- INSERT INTO wiki_sources (id, source_type, origin, title, ingested_at, raw_path, parsed_content, pages_updated, status, session_id, session_name)
129
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
130
- `).run("source-1", "url", "https://example.test/1", "Research note", "2026-05-01T10:00:00.000Z", "sources/source-1.md", "content", "[]", "active", "session-123", "Compiler research");
131
- const result = await consolidation.runConsolidationWithDeps(db, {
132
- now: () => new Date("2026-05-14T22:30:03.086Z"),
133
- synthesizeTruth: async () => "unused",
134
- commitWikiChanges: async () => false,
135
- });
136
- const actionItems = memory.listActionItems({ scope_id: globalScope.id, includeArchived: true });
137
- assert.equal(result.staleSessionsNotified, 1);
138
- assert.equal(actionItems.some((item) => item.title.includes("Research session 'Compiler research' has been inactive for 7+ days")), true);
139
- });
140
121
  test("runConsolidation caps truth rewrites before exceeding the LLM budget", async () => {
141
122
  const { dbModule, wikiFs, indexManager, consolidation } = await loadModules();
142
123
  const db = dbModule.getDb();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chapterhouse",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Chapterhouse — a team-level AI assistant for engineering teams, built on the GitHub Copilot SDK. Web UI only.",
5
5
  "bin": {
6
6
  "chapterhouse": "dist/cli.js"
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: evolve
3
+ description: Use for a systems-level audit of the memory architecture's rules and tiers. Trigger when the user says evolve, system audit, audit yourself, or check your architecture.
4
+ ---
5
+ **Tool enforcement:** All reads and writes to `memory/` MUST use cog_* tools (`cog_read`, `cog_write`, `cog_edit`, `cog_append`, `cog_move`, `cog_tree`, `cog_l0_scan`, `cog_l1_outline`, `cog_search`, `cog_stats`, `cog_domains`, `cog_sessions`, `cog_git`). **Do NOT use bash, cat, echo, find, or any shell command to read or write memory files.** If a cog_* tool call fails, note the error in self-observations and fall back to bash only for that specific operation.
6
+
7
+ **This is NOT /reflect.** Reflect = "what did I learn from interactions?" Evolve = "are the rules and architecture working?" **Evolve never touches memory content — it changes the rules that govern how content moves.**
8
+
9
+ ## Domain
10
+
11
+ Systems architecture — process rules, skill design, tier effectiveness, pipeline health.
12
+
13
+ ## Memory Files
14
+
15
+ Read FIRST — this is your continuity:
16
+ - `memory/cog-meta/evolve-log.md` — your run log
17
+ - `memory/cog-meta/evolve-observations.md` — architectural issues spotted
18
+
19
+ Architecture reference:
20
+ - `skills/system/housekeeping/SKILL.md` — housekeeping rules (read via `cog_read`)
21
+ - `skills/system/reflect/SKILL.md` — reflect rules (read via `cog_read`)
22
+ - The injected memory operating instructions — the platform equivalent of cog's `CLAUDE.md`. They are already in your context (injected into the system prompt); review them there, no `cog_read` needed.
23
+
24
+ Measure (don't edit content):
25
+ - `memory/hot-memory.md`
26
+ - `memory/cog-meta/patterns.md`
27
+ - Any domain satellite pattern files (e.g. `work/*/patterns.md`)
28
+
29
+ ## Orientation (run FIRST, before any file reads)
30
+
31
+ Use these tool calls to see exactly what changed since the last run:
32
+
33
+ 1. **What did housekeeping and reflect change recently?** — Call `cog_git` with op `diff`, `ref: "HEAD~1"`, and `paths: ["memory/"]` for a stat-style summary of recent changes across the memory tree.
34
+ 2. **Detailed diff of architectural files** — Call `cog_git` with op `diff`, `ref: "HEAD~1"`, and `paths: ["memory/cog-meta/patterns.md", "memory/hot-memory.md"]` to see exactly what changed in the files you care about.
35
+ 3. **What changed across recent runs?** — Call `cog_git` with op `log` to review the recent commit history. Each pipeline run commits its changes, so the log shows which runs touched memory and when — use it to scope which files to audit beyond the most recent diff.
36
+ 4. **Current prompt-weight components** — Call `cog_stats` on `memory/hot-memory.md`, `memory/cog-meta/patterns.md`, and `memory/cog-meta/briefing-bridge.md` to get current file sizes without opening those files.
37
+
38
+ Use the git diffs to understand what housekeeping/reflect actually did, instead of re-reading entire files.
39
+
40
+ ## Process
41
+
42
+ ### 1. Architecture Review
43
+
44
+ Evaluate the structural design:
45
+
46
+ - **Tier design** — are the tiers (hot-memory → patterns → observations → glacier) well-defined?
47
+ - **Condensation pipeline** — is the flow working? Where does it leak or stall?
48
+ - **File naming and organization** — any files in wrong domains? Orphaned files?
49
+ - **Skill boundaries** — are housekeeping/reflect/evolve boundaries clean? Any drift?
50
+
51
+ ### 2. Process Effectiveness Audit
52
+
53
+ Review the output of recent housekeeping and reflect runs by reading their skill files via `cog_read`:
54
+
55
+ **Housekeeping rules check** — read `skills/system/housekeeping/SKILL.md`:
56
+ - Did pruning priority order work? Or did it trim wrong things?
57
+ - Are glacier thresholds (50 obs, 10 action items) right?
58
+ - Is the 50-line hot-memory cap appropriate?
59
+ - Is entity format enforcement catching violations?
60
+
61
+ **Reflect rules check** — read `skills/system/reflect/SKILL.md`:
62
+ - Did condensation produce useful patterns, or noise?
63
+ - Did thread candidate detection work?
64
+ - Is reflect staying in its lane?
65
+ - Are patterns routing to the right file (core vs satellite)?
66
+
67
+ **Scorecard metrics** — measure and record in evolve-log:
68
+ - Core `patterns.md`: line count / 70, byte size / 5.5KB (target: ≤1.0)
69
+ - Satellite pattern files: list each with line count (soft cap: 30)
70
+ - Entity compression ratio: `(total entity lines across all files) / (total ### entries)` (target: ≤3.0)
71
+ - Hot-memory line counts vs caps
72
+
73
+ ### 3. Rule Change Proposals
74
+
75
+ Based on findings, propose concrete rule changes. Don't fix content — fix the rules.
76
+
77
+ For each proposal:
78
+ - What problem does it solve?
79
+ - What evidence supports it?
80
+ - What's the risk?
81
+ - Is this a rule change (apply directly) or architecture change (propose for user review)?
82
+
83
+ **Apply low-risk rule changes directly** to the relevant skill files using `cog_edit`. Propose architecture changes for user review.
84
+
85
+ ### 4. Route Content Issues
86
+
87
+ When you spot content problems during your audit, **don't fix them and don't defer them for yourself**. Route them explicitly.
88
+
89
+ Format in debrief:
90
+ ```
91
+ → housekeeping: entities.md at 290 lines, needs glacier pass
92
+ → reflect: hot-memory missing thread link for X
93
+ → reflect: patterns.md has stale snapshot data from Feb
94
+ ```
95
+
96
+ If the same content issue keeps appearing across runs, that's a **rule problem** — propose a rule change so housekeeping/reflect catch it themselves.
97
+
98
+ ### 5. Generate Scorecard
99
+
100
+ Use `cog_write` to overwrite `memory/cog-meta/scorecard.md` with current metrics:
101
+ - Core patterns.md: line count / 70, byte size / 5.5KB (target: ≤1.0)
102
+ - Satellite pattern files: list each with line count (soft cap: 30)
103
+ - Entity compression ratio: `(total entity lines across all files) / (total ### entries)` — target ≤3.0
104
+ - Hot-memory line counts vs caps
105
+ - Briefing bridge SSOT compliance (% of lines with [[source]] links)
106
+
107
+ ### 6. Write Observations & Update Log
108
+
109
+ **Observations** — Use `cog_append` to append to `memory/cog-meta/evolve-observations.md`:
110
+ - Format: `- YYYY-MM-DD [tag]: observation`
111
+ - Tags: bloat, staleness, redundancy, gap, architecture, opportunity, rule-drift, process-health
112
+
113
+ **Evolve Log** — Use `cog_append` to append to `memory/cog-meta/evolve-log.md`:
114
+ - Run number, process effectiveness findings, rule changes applied or proposed, deferred items
115
+ - Content issues routed (→ housekeeping / → reflect)
116
+ - Update "Next Run Priorities" section at top via `cog_edit`. **Only architecture/design items — never content work.**
117
+
118
+ ### 7. Debrief
119
+
120
+ Concise summary:
121
+ - *Process health* — did housekeeping/reflect follow their rules?
122
+ - *Rule changes* — applied or proposed, with rationale
123
+ - *Routed issues* — content problems sent to housekeeping/reflect
124
+ - *Architecture notes* — structural observations
125
+ - *Next evolve* — top 3 architecture priorities
126
+
127
+ Keep it actionable. Numbers over narrative.
128
+
129
+ ## Activation
130
+
131
+ Read `memory/cog-meta/evolve-log.md` and `memory/cog-meta/evolve-observations.md` FIRST for continuity. Then audit the system. You are the architect — you design the rules, you don't play by them.
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: foresight
3
+ description: Use for cross-domain strategic foresight — surfacing one high-value nudge. Trigger when the user says foresight, what should I be thinking about, what am I missing, or connect the dots.
4
+ ---
5
+ **Tool enforcement:** All reads and writes to `memory/` MUST use cog_* tools (`cog_read`, `cog_write`, `cog_edit`, `cog_append`, `cog_move`, `cog_tree`, `cog_l0_scan`, `cog_l1_outline`, `cog_search`, `cog_stats`, `cog_domains`, `cog_sessions`, `cog_git`). **Do NOT use bash, cat, echo, find, or any shell command to read or write memory files.** If a cog_* tool call fails, note the error in self-observations and fall back to bash only for that specific operation.
6
+
7
+ Use this skill for strategic foresight — connecting dots across domains and surfacing one high-value nudge. Trigger if the user says "foresight", "what should I be thinking about", "what am I missing", "strategic nudge", "connect the dots", or similar forward-looking synthesis requests.
8
+
9
+ **This is NOT the reflect skill.** Reflect = past-facing (mines interactions, fixes contradictions). Foresight = future-facing (scans broadly, projects trajectories, surfaces opportunities).
10
+
11
+ **This is NOT the evolve skill.** Evolve = system architecture. Foresight = life/work strategy.
12
+
13
+ ## Domain
14
+
15
+ Cross-domain strategic synthesis — personal, work, projects, health, family. The value is in the connections *between* domains.
16
+
17
+ ## Memory Files
18
+
19
+ Read broadly — this is a scan, not a focused lookup:
20
+
21
+ 1. Call `cog_domains` to discover all active domains
22
+ 2. For each domain, use `cog_read` to read `hot-memory.md` and `action-items.md` (if they exist)
23
+ 3. Also read via `cog_read`:
24
+ - `memory/hot-memory.md` (cross-domain strategic context)
25
+ - `memory/personal/entities.md` (upcoming birthdays, relationships)
26
+ - `memory/personal/calendar.md` (what's coming up)
27
+ - `memory/personal/health.md` (health trajectory)
28
+ - `memory/cog-meta/briefing-bridge.md` (housekeeping findings)
29
+ 4. Use `cog_l0_scan` to retrieve L0 summary comments across all domain files for quick routing signals
30
+ 5. Use `cog_search` to scan recent observations across all domains (last 7 days)
31
+ 6. Read thread current-state sections — what narratives are actively unfolding?
32
+
33
+ ## Process
34
+
35
+ ### 1. Cross-Domain Convergence Scan
36
+
37
+ Look for topics, people, or themes appearing in 2+ domains simultaneously. These are convergence points — where effort in one area compounds into another.
38
+
39
+ ### 2. Velocity & Stall Detection
40
+
41
+ Scan action-items across all domains. Classify each active item:
42
+ - **Accelerating** — multiple updates in the last week, clear momentum. Signal: ride the wave, don't interrupt.
43
+ - **Cruising** — steady progress, on track. Signal: nothing to flag.
44
+ - **Stalling** — no movement in 2+ weeks despite not being deferred. Signal: ask why. Blocked? Lost priority?
45
+ - **Dormant** — domain-level silence (0 observations in 4+ weeks). Signal: conscious choice or drift?
46
+
47
+ Stalls and dormant domains are high-value nudge material — they represent things the user cares about but isn't acting on.
48
+
49
+ ### 3. Timing Awareness
50
+
51
+ Use `cog_read` to read calendar and entities files for upcoming events in the next 2-4 weeks. Look for timing windows — things that should start NOW to be ready later.
52
+
53
+ ### 4. Pattern Projection
54
+
55
+ Use `cog_read` to read patterns and use `cog_search` for recent observations. Project forward: "If this continues for 2 more weeks, what happens?"
56
+
57
+ **Scenario candidate detection**: If a pattern projection reveals a genuine fork — two meaningfully different paths with real stakes and a closing decision window — flag it as a scenario candidate below the main nudge. A valid candidate needs: a fork (2+ paths), stakes (wrong choice has real cost), and time sensitivity (window closing). Don't flag routine decisions or hypotheticals with no deadline.
58
+
59
+ ### 5. Write One Strategic Nudge
60
+
61
+ Synthesize into **one nudge**. Not a list. One thing.
62
+
63
+ The nudge must:
64
+ - **Cite at least 2 source files**
65
+ - **Be something the user hasn't explicitly asked about**
66
+ - **Be actionable** — not "think about X" but "do Y because of X and Z"
67
+ - **Connect dots**
68
+
69
+ Use `cog_write` to write to `memory/cog-meta/foresight-nudge.md`:
70
+
71
+ ```markdown
72
+ # Foresight Nudge
73
+ <!-- Auto-generated by strategic foresight. -->
74
+ <!-- Last updated: YYYY-MM-DD -->
75
+
76
+ ## Signal
77
+ <What you noticed — the raw observation from 2+ domains>
78
+
79
+ ## Insight
80
+ <Why it matters — the connection, timing, or trajectory that makes this worth flagging>
81
+
82
+ ## Suggested Action
83
+ <One concrete thing to do — specific, actionable, grounded>
84
+
85
+ ---
86
+ Sources: [[file1]], [[file2]], [[file3]]
87
+
88
+ ## Scenario Candidate (optional)
89
+ <!-- Only include if pattern projection reveals a genuine fork worth simulating -->
90
+ Decision: <one-line framing>
91
+ Why now: <why the window is closing>
92
+ Domains: <affected domains>
93
+ ```
94
+
95
+ Overwrite the file each run. One nudge per run.
96
+
97
+ ## Rules
98
+
99
+ 1. **Read-only** — Foresight NEVER edits memory files. Writes ONLY to `memory/cog-meta/foresight-nudge.md` via `cog_write`. If you spot a memory error, note it in the nudge's signal section and let the reflect skill handle it.
100
+ 2. **One nudge, not a list** — force prioritization. If everything is equally important, nothing is.
101
+ 3. **Evidence-based** — every nudge cites at least 2 source files. No vibes.
102
+ 4. **Non-obvious** — the nudge should surprise. If the user already knows and is acting on it, pick something else.
103
+ 5. **Forward-looking** — avoid rehashing yesterday. Project into next week, next month.
104
+ 6. **Cross-domain preferred** — nudges that connect personal + work are higher value than single-domain insights.
105
+
106
+ ## Anti-Patterns
107
+
108
+ - Don't repeat what briefing-bridge already says (stale items, birthday prep) — that's housekeeping's job
109
+ - Don't recommend "reflect on X" — be specific about what to DO
110
+ - Don't flag things the user has explicitly deferred — respect the deferral
111
+ - Don't flag things that are cruising — focus on convergences, stalls, and timing windows
112
+ - Don't write a mini-briefing — one insight, one action
113
+
114
+ ## Activation
115
+
116
+ Call `cog_domains` and `cog_l0_scan` first for broad orientation. Then read broadly across all domains using `cog_read` and `cog_search`. Find the one thing worth saying.