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.
- 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 +84 -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/package.json +1 -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
|
@@ -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(
|
|
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
|
-
|
|
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
|
@@ -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.
|