chapterhouse 0.13.1 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/route-coverage.test.js +1 -3
- package/dist/api/server.js +0 -2
- package/dist/api/server.test.js +0 -281
- package/dist/config.js +3 -85
- package/dist/config.test.js +5 -123
- package/dist/copilot/agents.js +13 -10
- package/dist/copilot/agents.test.js +10 -11
- package/dist/copilot/memory-coordinator.js +12 -227
- package/dist/copilot/memory-coordinator.test.js +31 -250
- package/dist/copilot/orchestrator.js +8 -66
- package/dist/copilot/orchestrator.test.js +9 -467
- package/dist/copilot/skills.js +15 -1
- package/dist/copilot/system-message.js +9 -15
- package/dist/copilot/system-message.test.js +9 -22
- package/dist/copilot/tools/index.js +3 -3
- package/dist/copilot/tools-deps.js +1 -1
- package/dist/copilot/tools.agent.test.js +6 -0
- package/dist/copilot/tools.inventory.test.js +1 -14
- package/dist/daemon.js +7 -9
- package/dist/memory/assets.js +33 -0
- package/dist/memory/domains.js +58 -0
- package/dist/memory/domains.test.js +47 -0
- package/dist/memory/git.js +66 -0
- package/dist/memory/git.test.js +32 -0
- package/dist/memory/history.js +19 -0
- package/dist/memory/hottier.js +32 -0
- package/dist/memory/hottier.test.js +33 -0
- package/dist/memory/index.js +5 -13
- package/dist/memory/instructions.js +17 -0
- package/dist/memory/manager.js +92 -0
- package/dist/memory/markdown.js +78 -0
- package/dist/memory/markdown.test.js +42 -0
- package/dist/memory/mutex.js +18 -0
- package/dist/memory/path-guard.js +26 -0
- package/dist/memory/path-guard.test.js +27 -0
- package/dist/memory/paths.js +12 -0
- package/dist/memory/reconcile.js +75 -0
- package/dist/memory/reconcile.test.js +50 -0
- package/dist/memory/scaffold.js +37 -0
- package/dist/memory/scaffold.test.js +52 -0
- package/dist/memory/tools/commit-wrapper.js +32 -0
- package/dist/memory/tools/domains.js +73 -0
- package/dist/memory/tools/domains.test.js +66 -0
- package/dist/memory/tools/git.js +52 -0
- package/dist/memory/tools/index.js +25 -0
- package/dist/memory/tools/read.js +101 -0
- package/dist/memory/tools/read.test.js +69 -0
- package/dist/memory/tools/search.js +103 -0
- package/dist/memory/tools/search.test.js +63 -0
- package/dist/memory/tools/sessions.js +45 -0
- package/dist/memory/tools/sessions.test.js +74 -0
- package/dist/memory/tools/shared.js +7 -0
- package/dist/memory/tools/write.js +116 -0
- package/dist/memory/tools/write.test.js +107 -0
- package/dist/memory/walk.js +39 -0
- package/dist/store/repositories/sessions.js +40 -0
- package/dist/wiki/consolidation.js +3 -31
- package/dist/wiki/consolidation.test.js +0 -19
- package/memory-assets/domain-skill.md +38 -0
- package/memory-assets/seed/cog-meta/improvements.md +8 -0
- package/memory-assets/seed/cog-meta/patterns.md +5 -0
- package/memory-assets/seed/cog-meta/reflect-cursor.md +4 -0
- package/memory-assets/seed/cog-meta/scenario-calibration.md +14 -0
- package/memory-assets/seed/cog-meta/self-observations.md +4 -0
- package/memory-assets/seed/domains.yml +19 -0
- package/memory-assets/seed/glacier/index.md +6 -0
- package/memory-assets/seed/hot-memory.md +5 -0
- package/memory-assets/seed/link-index.md +6 -0
- package/memory-assets/system-instructions.md +214 -0
- package/memory-assets/templates/action-items.md +8 -0
- package/memory-assets/templates/entities.md +4 -0
- package/memory-assets/templates/generic.md +2 -0
- package/memory-assets/templates/hot-memory.md +4 -0
- package/memory-assets/templates/observations.md +4 -0
- package/package.json +2 -1
- package/skills/system/evolve/SKILL.md +131 -0
- package/skills/system/foresight/SKILL.md +116 -0
- package/skills/system/history/SKILL.md +58 -0
- package/skills/system/housekeeping/SKILL.md +185 -0
- package/skills/system/reflect/SKILL.md +214 -0
- package/skills/system/scenario/SKILL.md +198 -0
- package/skills/system/setup/SKILL.md +113 -0
- package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
- package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
- package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
- package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
- package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
- package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
- package/web/dist/index.html +1 -1
- package/dist/api/routes/memory.js +0 -475
- package/dist/api/routes/memory.test.js +0 -108
- package/dist/copilot/tools/memory.js +0 -678
- package/dist/copilot/tools.memory.test.js +0 -590
- package/dist/memory/action-items.js +0 -100
- package/dist/memory/action-items.test.js +0 -83
- package/dist/memory/active-scope.js +0 -78
- package/dist/memory/active-scope.test.js +0 -80
- package/dist/memory/checkpoint-prompt.js +0 -71
- package/dist/memory/checkpoint.js +0 -274
- package/dist/memory/checkpoint.test.js +0 -275
- package/dist/memory/decisions.js +0 -54
- package/dist/memory/decisions.test.js +0 -92
- package/dist/memory/entities.js +0 -70
- package/dist/memory/entities.test.js +0 -65
- package/dist/memory/eot.js +0 -459
- package/dist/memory/eot.test.js +0 -949
- package/dist/memory/hooks.js +0 -149
- package/dist/memory/hooks.test.js +0 -325
- package/dist/memory/hot-tier.js +0 -283
- package/dist/memory/hot-tier.test.js +0 -275
- package/dist/memory/housekeeping-scheduler.js +0 -187
- package/dist/memory/housekeeping-scheduler.test.js +0 -236
- package/dist/memory/housekeeping.js +0 -497
- package/dist/memory/housekeeping.test.js +0 -410
- package/dist/memory/inbox.js +0 -83
- package/dist/memory/inbox.test.js +0 -178
- package/dist/memory/migration.js +0 -244
- package/dist/memory/migration.test.js +0 -108
- package/dist/memory/observations.js +0 -46
- package/dist/memory/observations.test.js +0 -86
- package/dist/memory/recall.js +0 -269
- package/dist/memory/recall.test.js +0 -265
- package/dist/memory/reflect.js +0 -273
- package/dist/memory/reflect.test.js +0 -256
- package/dist/memory/scope-lock.js +0 -26
- package/dist/memory/scope-lock.test.js +0 -118
- package/dist/memory/scopes.js +0 -89
- package/dist/memory/scopes.test.js +0 -176
- package/dist/memory/tiering.js +0 -223
- package/dist/memory/tiering.test.js +0 -323
- package/dist/memory/types.js +0 -2
- package/web/dist/assets/index-gAvLNEvJ.js.map +0 -1
|
@@ -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();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{ID}}
|
|
3
|
+
description: Use when the conversation involves {{LABEL}}. Triggers — {{TRIGGER_LINE}}. Do not use for topics belonging to other domains.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# {{LABEL}}
|
|
7
|
+
|
|
8
|
+
Use this skill when the user discusses {{LABEL}} topics.
|
|
9
|
+
|
|
10
|
+
## Memory Files
|
|
11
|
+
|
|
12
|
+
Always read on activation:
|
|
13
|
+
- `memory/{{PATH}}/hot-memory.md` (via `cog_read`)
|
|
14
|
+
|
|
15
|
+
Then load additional files per the memory retrieval protocol based on the query:
|
|
16
|
+
- Status/task query → `memory/{{PATH}}/action-items.md`
|
|
17
|
+
- Entity/people query → `memory/{{PATH}}/entities.md`
|
|
18
|
+
- Update/observation → the target file only
|
|
19
|
+
- Complex query → hot-memory first, then drill into referenced files
|
|
20
|
+
|
|
21
|
+
Available warm files: {{FILES}}
|
|
22
|
+
|
|
23
|
+
To find files without opening them, run `cog_l0_scan` scoped to `{{PATH}}`.
|
|
24
|
+
Historical data: `cog_read` on `memory/glacier/index.md`, filter by domain `{{ID}}`.
|
|
25
|
+
|
|
26
|
+
## Routing
|
|
27
|
+
|
|
28
|
+
When the user shares information or asks to save something, write with the
|
|
29
|
+
`cog_*` tools:
|
|
30
|
+
- Task/todo → `memory/{{PATH}}/action-items.md`
|
|
31
|
+
- Person/entity → `memory/{{PATH}}/entities.md`
|
|
32
|
+
- Update/log → `memory/{{PATH}}/observations.md`
|
|
33
|
+
- Status/overview → `memory/{{PATH}}/hot-memory.md`
|
|
34
|
+
|
|
35
|
+
## Activation
|
|
36
|
+
|
|
37
|
+
Read the hot-memory file, classify the query, load the minimum files needed,
|
|
38
|
+
and respond.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- L0: Prediction accuracy tracker for decision scenarios -->
|
|
2
|
+
# Scenario Calibration
|
|
3
|
+
|
|
4
|
+
<!-- Updated by the reflect skill when scenarios resolve. -->
|
|
5
|
+
|
|
6
|
+
## Resolved Scenarios
|
|
7
|
+
|
|
8
|
+
| Scenario | Created | Resolved | Predicted Branch | Actual Branch | Accuracy | Lesson |
|
|
9
|
+
|----------|---------|----------|-----------------|---------------|----------|--------|
|
|
10
|
+
|
|
11
|
+
## Metrics
|
|
12
|
+
|
|
13
|
+
- Total resolved: 0
|
|
14
|
+
- Accuracy rate: —
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Chapterhouse Domain Manifest
|
|
2
|
+
# Single source of truth for all memory domains.
|
|
3
|
+
# Add domains with the setup skill, or edit this file directly — Chapterhouse
|
|
4
|
+
# reconciles directories and skill files from it on every startup.
|
|
5
|
+
|
|
6
|
+
domains:
|
|
7
|
+
- id: personal
|
|
8
|
+
path: personal
|
|
9
|
+
type: personal
|
|
10
|
+
label: "Family, health, calendar, day-to-day"
|
|
11
|
+
triggers: [family, health, kids, calendar, personal, home, car, finance]
|
|
12
|
+
files: [hot-memory, action-items, entities, observations, habits, health, calendar]
|
|
13
|
+
|
|
14
|
+
- id: cog-meta
|
|
15
|
+
path: cog-meta
|
|
16
|
+
type: system
|
|
17
|
+
label: "Memory self-knowledge, pipeline health, architecture"
|
|
18
|
+
triggers: [meta, evolve, pipeline, memory system, architecture]
|
|
19
|
+
files: [self-observations, patterns, improvements, scenario-calibration, reflect-cursor]
|