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.
Files changed (132) 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 +92 -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/memory-assets/domain-skill.md +38 -0
  60. package/memory-assets/seed/cog-meta/improvements.md +8 -0
  61. package/memory-assets/seed/cog-meta/patterns.md +5 -0
  62. package/memory-assets/seed/cog-meta/reflect-cursor.md +4 -0
  63. package/memory-assets/seed/cog-meta/scenario-calibration.md +14 -0
  64. package/memory-assets/seed/cog-meta/self-observations.md +4 -0
  65. package/memory-assets/seed/domains.yml +19 -0
  66. package/memory-assets/seed/glacier/index.md +6 -0
  67. package/memory-assets/seed/hot-memory.md +5 -0
  68. package/memory-assets/seed/link-index.md +6 -0
  69. package/memory-assets/system-instructions.md +214 -0
  70. package/memory-assets/templates/action-items.md +8 -0
  71. package/memory-assets/templates/entities.md +4 -0
  72. package/memory-assets/templates/generic.md +2 -0
  73. package/memory-assets/templates/hot-memory.md +4 -0
  74. package/memory-assets/templates/observations.md +4 -0
  75. package/package.json +2 -1
  76. package/skills/system/evolve/SKILL.md +131 -0
  77. package/skills/system/foresight/SKILL.md +116 -0
  78. package/skills/system/history/SKILL.md +58 -0
  79. package/skills/system/housekeeping/SKILL.md +185 -0
  80. package/skills/system/reflect/SKILL.md +214 -0
  81. package/skills/system/scenario/SKILL.md +198 -0
  82. package/skills/system/setup/SKILL.md +113 -0
  83. package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
  84. package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
  85. package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
  86. package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
  87. package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
  88. package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
  89. package/web/dist/index.html +1 -1
  90. package/dist/api/routes/memory.js +0 -475
  91. package/dist/api/routes/memory.test.js +0 -108
  92. package/dist/copilot/tools/memory.js +0 -678
  93. package/dist/copilot/tools.memory.test.js +0 -590
  94. package/dist/memory/action-items.js +0 -100
  95. package/dist/memory/action-items.test.js +0 -83
  96. package/dist/memory/active-scope.js +0 -78
  97. package/dist/memory/active-scope.test.js +0 -80
  98. package/dist/memory/checkpoint-prompt.js +0 -71
  99. package/dist/memory/checkpoint.js +0 -274
  100. package/dist/memory/checkpoint.test.js +0 -275
  101. package/dist/memory/decisions.js +0 -54
  102. package/dist/memory/decisions.test.js +0 -92
  103. package/dist/memory/entities.js +0 -70
  104. package/dist/memory/entities.test.js +0 -65
  105. package/dist/memory/eot.js +0 -459
  106. package/dist/memory/eot.test.js +0 -949
  107. package/dist/memory/hooks.js +0 -149
  108. package/dist/memory/hooks.test.js +0 -325
  109. package/dist/memory/hot-tier.js +0 -283
  110. package/dist/memory/hot-tier.test.js +0 -275
  111. package/dist/memory/housekeeping-scheduler.js +0 -187
  112. package/dist/memory/housekeeping-scheduler.test.js +0 -236
  113. package/dist/memory/housekeeping.js +0 -497
  114. package/dist/memory/housekeeping.test.js +0 -410
  115. package/dist/memory/inbox.js +0 -83
  116. package/dist/memory/inbox.test.js +0 -178
  117. package/dist/memory/migration.js +0 -244
  118. package/dist/memory/migration.test.js +0 -108
  119. package/dist/memory/observations.js +0 -46
  120. package/dist/memory/observations.test.js +0 -86
  121. package/dist/memory/recall.js +0 -269
  122. package/dist/memory/recall.test.js +0 -265
  123. package/dist/memory/reflect.js +0 -273
  124. package/dist/memory/reflect.test.js +0 -256
  125. package/dist/memory/scope-lock.js +0 -26
  126. package/dist/memory/scope-lock.test.js +0 -118
  127. package/dist/memory/scopes.js +0 -89
  128. package/dist/memory/scopes.test.js +0 -176
  129. package/dist/memory/tiering.js +0 -223
  130. package/dist/memory/tiering.test.js +0 -323
  131. package/dist/memory/types.js +0 -2
  132. package/web/dist/assets/index-gAvLNEvJ.js.map +0 -1
@@ -1,244 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { existsSync, readdirSync, readFileSync } from "node:fs";
3
- import { join, relative } from "node:path";
4
- import { WIKI_PAGES_DIR } from "../paths.js";
5
- import { parseWikiFrontmatter } from "../wiki/frontmatter.js";
6
- const MIGRATION_NAME = "p6-wiki-seed";
7
- const MIGRATION_SOURCE = "migration:p6";
8
- export async function runP6Migration(db) {
9
- db.exec(`
10
- CREATE TABLE IF NOT EXISTS mem_migrations (
11
- id INTEGER PRIMARY KEY,
12
- name TEXT UNIQUE,
13
- run_at TEXT
14
- )
15
- `);
16
- const alreadyRan = db.prepare(`SELECT 1 FROM mem_migrations WHERE name = ?`).get(MIGRATION_NAME);
17
- if (alreadyRan) {
18
- return {
19
- entitiesCreated: 0,
20
- observationsCreated: 0,
21
- decisionsCreated: 0,
22
- skipped: -1,
23
- };
24
- }
25
- const globalScopeId = getScopeId(db, "global");
26
- if (!globalScopeId) {
27
- throw new Error("Missing required memory scope 'global'.");
28
- }
29
- const chapterhouseScopeId = getScopeId(db, "chapterhouse") ?? globalScopeId;
30
- const result = {
31
- entitiesCreated: 0,
32
- observationsCreated: 0,
33
- decisionsCreated: 0,
34
- skipped: 0,
35
- };
36
- if (existsSync(WIKI_PAGES_DIR)) {
37
- for (const kindDir of ["projects", "people", "topics"]) {
38
- for (const pagePath of findFiles(join(WIKI_PAGES_DIR, kindDir), "index.md")) {
39
- const relativeSlug = normalizeRelativePageSlug(pagePath);
40
- const content = readFileSync(pagePath, "utf-8");
41
- const { parsed, body } = parseWikiFrontmatter(content);
42
- const kind = toEntityKind(kindDir);
43
- const title = parsed.title?.trim() || deriveTitleFromSlug(relativeSlug);
44
- const summary = parsed.summary?.trim() || null;
45
- const entityId = upsertEntityBySlug(db, {
46
- scopeId: globalScopeId,
47
- slug: relativeSlug,
48
- kind,
49
- title,
50
- summary,
51
- });
52
- if (entityId.created) {
53
- result.entitiesCreated += 1;
54
- }
55
- const compiledTruth = extractCompiledTruth(body, title);
56
- if (!compiledTruth) {
57
- result.skipped += 1;
58
- continue;
59
- }
60
- const observationContent = compiledTruth.slice(0, 500);
61
- if (insertObservationIfMissing(db, {
62
- scopeId: globalScopeId,
63
- entityId: entityId.id,
64
- content: observationContent,
65
- })) {
66
- result.observationsCreated += 1;
67
- }
68
- }
69
- }
70
- for (const decisionPath of findFiles(WIKI_PAGES_DIR, "decisions.md")) {
71
- const entries = parseDecisionEntries(readFileSync(decisionPath, "utf-8"));
72
- for (const entry of entries) {
73
- if (!entry.rationale) {
74
- result.skipped += 1;
75
- continue;
76
- }
77
- if (insertDecisionIfMissing(db, {
78
- scopeId: chapterhouseScopeId,
79
- title: entry.title,
80
- rationale: entry.rationale,
81
- })) {
82
- result.decisionsCreated += 1;
83
- }
84
- }
85
- }
86
- }
87
- db.prepare(`INSERT INTO mem_migrations (name, run_at) VALUES (?, ?)`)
88
- .run(MIGRATION_NAME, new Date().toISOString());
89
- return result;
90
- }
91
- function getScopeId(db, slug) {
92
- const row = db.prepare(`SELECT id FROM mem_scopes WHERE slug = ?`).get(slug);
93
- return row?.id;
94
- }
95
- function findFiles(root, targetName) {
96
- if (!existsSync(root)) {
97
- return [];
98
- }
99
- const matches = [];
100
- for (const entry of readdirSync(root, { withFileTypes: true })) {
101
- const fullPath = join(root, entry.name);
102
- if (entry.isDirectory()) {
103
- matches.push(...findFiles(fullPath, targetName));
104
- continue;
105
- }
106
- if (entry.isFile() && entry.name === targetName) {
107
- matches.push(fullPath);
108
- }
109
- }
110
- return matches.sort((left, right) => left.localeCompare(right));
111
- }
112
- function normalizeRelativePageSlug(fullPath) {
113
- return relative(WIKI_PAGES_DIR, fullPath).replace(/\\/g, "/");
114
- }
115
- function toEntityKind(directory) {
116
- switch (directory) {
117
- case "projects": return "project";
118
- case "people": return "person";
119
- case "topics": return "topic";
120
- }
121
- }
122
- function deriveTitleFromSlug(slug) {
123
- const parts = slug.split("/").filter(Boolean);
124
- const base = parts[parts.length - 2] || parts[parts.length - 1] || slug;
125
- return base.split(/[-_]+/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
126
- }
127
- function extractCompiledTruth(body, fallback) {
128
- let text = body.replace(/\r\n/g, "\n").trim();
129
- text = text.replace(/^#\s+.+?(\n+|$)/, "").trim();
130
- const summaryMatch = text.match(/^##\s+Summary\s*\n+([\s\S]*?)(?=\n##\s+|$)/m);
131
- if (summaryMatch?.[1]?.trim()) {
132
- text = summaryMatch[1].trim();
133
- }
134
- const beforeTimeline = text.split(/\n##\s+Timeline\b/m)[0]?.trim() ?? "";
135
- return beforeTimeline || fallback;
136
- }
137
- function upsertEntityBySlug(db, input) {
138
- const existingBySlug = db.prepare(`SELECT id FROM mem_entities WHERE scope_id = ? AND slug = ?`).get(input.scopeId, input.slug);
139
- if (existingBySlug) {
140
- db.prepare(`
141
- UPDATE mem_entities
142
- SET kind = ?, name = ?, summary = ?, updated_at = CURRENT_TIMESTAMP
143
- WHERE id = ?
144
- `).run(input.kind, input.title, input.summary, existingBySlug.id);
145
- return { id: existingBySlug.id, created: false };
146
- }
147
- const existingByName = db.prepare(`
148
- SELECT id
149
- FROM mem_entities
150
- WHERE scope_id = ? AND kind = ? AND name = ?
151
- `).get(input.scopeId, input.kind, input.title);
152
- if (existingByName) {
153
- db.prepare(`
154
- UPDATE mem_entities
155
- SET slug = ?, summary = ?, updated_at = CURRENT_TIMESTAMP
156
- WHERE id = ?
157
- `).run(input.slug, input.summary, existingByName.id);
158
- return { id: existingByName.id, created: false };
159
- }
160
- const inserted = db.prepare(`
161
- INSERT INTO mem_entities (scope_id, slug, kind, name, summary, tier, confidence, created_at, updated_at)
162
- VALUES (?, ?, ?, ?, ?, 'warm', 1.0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
163
- `).run(input.scopeId, input.slug, input.kind, input.title, input.summary);
164
- return { id: Number(inserted.lastInsertRowid), created: true };
165
- }
166
- function insertObservationIfMissing(db, input) {
167
- const hash = contentHash(input.content);
168
- const existing = db.prepare(`
169
- SELECT id
170
- FROM mem_observations
171
- WHERE scope_id = ? AND entity_id = ? AND source = ? AND content = ?
172
- `).get(input.scopeId, input.entityId, MIGRATION_SOURCE, input.content);
173
- if (existing) {
174
- return false;
175
- }
176
- const duplicateByHash = db.prepare(`
177
- SELECT id, content
178
- FROM mem_observations
179
- WHERE scope_id = ? AND entity_id = ? AND source = ?
180
- `).all(input.scopeId, input.entityId, MIGRATION_SOURCE);
181
- if (duplicateByHash.some((row) => contentHash(row.content) === hash)) {
182
- return false;
183
- }
184
- db.prepare(`
185
- INSERT INTO mem_observations (scope_id, entity_id, content, source, tier, confidence, created_at)
186
- VALUES (?, ?, ?, ?, 'warm', 1.0, CURRENT_TIMESTAMP)
187
- `).run(input.scopeId, input.entityId, input.content, MIGRATION_SOURCE);
188
- return true;
189
- }
190
- function insertDecisionIfMissing(db, input) {
191
- const hash = contentHash(`${input.title}\n${input.rationale}`);
192
- const rows = db.prepare(`
193
- SELECT id, title, rationale
194
- FROM mem_decisions
195
- WHERE scope_id = ?
196
- `).all(input.scopeId);
197
- if (rows.some((row) => contentHash(`${row.title}\n${row.rationale}`) === hash)) {
198
- return false;
199
- }
200
- db.prepare(`
201
- INSERT INTO mem_decisions (scope_id, title, rationale, decided_at, source, tier, created_at)
202
- VALUES (?, ?, ?, ?, ?, 'warm', CURRENT_TIMESTAMP)
203
- `).run(input.scopeId, input.title, input.rationale, new Date().toISOString().slice(0, 10), MIGRATION_SOURCE);
204
- return true;
205
- }
206
- function parseDecisionEntries(content) {
207
- const lines = content.replace(/\r\n/g, "\n").split("\n");
208
- const entries = [];
209
- for (let index = 0; index < lines.length; index += 1) {
210
- const match = lines[index]?.match(/^###?\s+(.+)$/);
211
- if (!match) {
212
- continue;
213
- }
214
- const paragraph = [];
215
- let cursor = index + 1;
216
- while (cursor < lines.length && !lines[cursor]?.trim()) {
217
- cursor += 1;
218
- }
219
- while (cursor < lines.length) {
220
- const line = lines[cursor] ?? "";
221
- if (/^###?\s+/.test(line)) {
222
- break;
223
- }
224
- if (!line.trim()) {
225
- if (paragraph.length > 0) {
226
- break;
227
- }
228
- cursor += 1;
229
- continue;
230
- }
231
- paragraph.push(line.trim());
232
- cursor += 1;
233
- }
234
- entries.push({
235
- title: match[1].trim(),
236
- rationale: paragraph.join(" ").trim(),
237
- });
238
- }
239
- return entries;
240
- }
241
- function contentHash(content) {
242
- return createHash("sha256").update(content).digest("hex");
243
- }
244
- //# sourceMappingURL=migration.js.map
@@ -1,108 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import test from "node:test";
5
- import { resetSingletons } from "../test/helpers/reset-singletons.js";
6
- let sandboxRoot;
7
- let chapterhouseHome;
8
- let dbModule;
9
- let migrationModule;
10
- function resetSandbox() {
11
- rmSync(chapterhouseHome, { recursive: true, force: true });
12
- mkdirSync(chapterhouseHome, { recursive: true });
13
- }
14
- function writePage(relativePath, content) {
15
- const fullPath = join(chapterhouseHome, "wiki", relativePath);
16
- mkdirSync(join(fullPath, ".."), { recursive: true });
17
- writeFileSync(fullPath, content, "utf-8");
18
- }
19
- test.before(async () => {
20
- mkdirSync(join(process.cwd(), ".test-work"), { recursive: true });
21
- sandboxRoot = mkdtempSync(join(process.cwd(), ".test-work", "memory-migration-"));
22
- process.env.CHAPTERHOUSE_HOME = sandboxRoot;
23
- chapterhouseHome = join(sandboxRoot, ".chapterhouse");
24
- resetSingletons();
25
- const nonce = `${Date.now()}-${Math.random()}`;
26
- dbModule = await import(new URL(`../store/db.js?case=${nonce}`, import.meta.url).href);
27
- migrationModule = await import(new URL(`./migration.js?case=${nonce}`, import.meta.url).href);
28
- });
29
- test.beforeEach(() => {
30
- process.env.CHAPTERHOUSE_HOME = sandboxRoot;
31
- dbModule.closeDb();
32
- resetSingletons();
33
- resetSandbox();
34
- });
35
- test.after(() => {
36
- try {
37
- dbModule.closeDb();
38
- }
39
- catch { }
40
- try {
41
- resetSingletons();
42
- }
43
- catch { }
44
- try {
45
- rmSync(sandboxRoot, { recursive: true, force: true });
46
- }
47
- catch { }
48
- });
49
- test("runP6Migration seeds project, person, topic, and decision pages from the wiki", async () => {
50
- const longTruth = `${"Compiled truth. ".repeat(60)}Detailed migration note.`;
51
- writePage("pages/projects/project-x/index.md", `---\ntitle: Project X\nsummary: Flagship delivery program\ntags: [alpha, backend]\n---\n\n# Project X\n\n${longTruth}\n`);
52
- writePage("pages/people/alice-example/index.md", `---\ntitle: Alice Example\nsummary: Principal engineer\ntags: [people]\n---\n\n# Alice Example\n\nAlice leads backend delivery.\n`);
53
- writePage("pages/topics/observability/index.md", `---\ntitle: Observability\nsummary: Shared telemetry topic\ntags: [topic]\n---\n\n# Observability\n\nTracing, logs, and metrics.\n`);
54
- writePage("pages/projects/chapterhouse/decisions.md", `# Decisions\n\n## Keep SQLite for memory\nSQLite keeps the daemon self-contained.\n\n### Append-only timelines\nTimeline history remains append-only for auditability.\n`);
55
- const db = dbModule.getDb();
56
- const result = await migrationModule.runP6Migration(db);
57
- assert.deepEqual(result, {
58
- entitiesCreated: 3,
59
- observationsCreated: 3,
60
- decisionsCreated: 2,
61
- skipped: 0,
62
- });
63
- const entities = db.prepare(`SELECT slug, kind, name, summary FROM mem_entities ORDER BY slug`).all();
64
- assert.deepEqual(entities.filter((row) => row.slug?.startsWith("projects/") || row.slug?.startsWith("people/") || row.slug?.startsWith("topics/")), [
65
- { slug: "people/alice-example/index.md", kind: "person", name: "Alice Example", summary: "Principal engineer" },
66
- { slug: "projects/project-x/index.md", kind: "project", name: "Project X", summary: "Flagship delivery program" },
67
- { slug: "topics/observability/index.md", kind: "topic", name: "Observability", summary: "Shared telemetry topic" },
68
- ]);
69
- const observation = db.prepare(`SELECT content, source, tier FROM mem_observations WHERE source = 'migration:p6' ORDER BY id LIMIT 1`).get();
70
- assert.equal(observation.source, "migration:p6");
71
- assert.equal(observation.tier, "warm");
72
- assert.equal(observation.content.length, 500);
73
- assert.equal(observation.content, longTruth.slice(0, 500));
74
- const chapterhouseScope = db.prepare(`SELECT id FROM mem_scopes WHERE slug = 'chapterhouse'`).get();
75
- const decisions = db.prepare(`SELECT title, rationale, scope_id FROM mem_decisions WHERE title IN (?, ?) ORDER BY title`).all("Append-only timelines", "Keep SQLite for memory");
76
- assert.deepEqual(decisions, [
77
- { title: "Append-only timelines", rationale: "Timeline history remains append-only for auditability.", scope_id: chapterhouseScope.id },
78
- { title: "Keep SQLite for memory", rationale: "SQLite keeps the daemon self-contained.", scope_id: chapterhouseScope.id },
79
- ]);
80
- const migrationRow = db.prepare(`SELECT name FROM mem_migrations WHERE name = 'p6-wiki-seed'`).get();
81
- assert.deepEqual(migrationRow, { name: "p6-wiki-seed" });
82
- });
83
- test("runP6Migration is idempotent across repeated runs", async () => {
84
- writePage("pages/projects/project-x/index.md", `---\ntitle: Project X\nsummary: Flagship delivery program\ntags: []\n---\n\n# Project X\n\nCompiled truth lives here.\n`);
85
- const db = dbModule.getDb();
86
- const first = await migrationModule.runP6Migration(db);
87
- const second = await migrationModule.runP6Migration(db);
88
- assert.equal(first.entitiesCreated, 1);
89
- assert.equal(first.observationsCreated, 1);
90
- assert.equal(second.skipped, -1);
91
- assert.equal(db.prepare(`SELECT COUNT(*) AS count FROM mem_entities WHERE slug = 'projects/project-x/index.md'`).get().count, 1);
92
- assert.equal(db.prepare(`SELECT COUNT(*) AS count FROM mem_observations WHERE source = 'migration:p6'`).get().count, 1);
93
- assert.equal(db.prepare(`SELECT COUNT(*) AS count FROM mem_migrations WHERE name = 'p6-wiki-seed'`).get().count, 1);
94
- });
95
- test("runP6Migration skips gracefully when the wiki skeleton exists but contains no migratable pages", async () => {
96
- const db = dbModule.getDb();
97
- assert.equal(existsSync(join(chapterhouseHome, "wiki", "pages")), true);
98
- const result = await migrationModule.runP6Migration(db);
99
- assert.deepEqual(result, {
100
- entitiesCreated: 0,
101
- observationsCreated: 0,
102
- decisionsCreated: 0,
103
- skipped: 0,
104
- });
105
- assert.equal(db.prepare(`SELECT COUNT(*) AS count FROM mem_entities`).get().count >= 0, true);
106
- assert.equal(db.prepare(`SELECT COUNT(*) AS count FROM mem_migrations WHERE name = 'p6-wiki-seed'`).get().count, 1);
107
- });
108
- //# sourceMappingURL=migration.test.js.map
@@ -1,46 +0,0 @@
1
- import { getDb } from "../store/db.js";
2
- function toObservation(row) {
3
- return {
4
- id: row.id,
5
- scopeId: row.scope_id,
6
- entityId: row.entity_id ?? undefined,
7
- content: row.content,
8
- source: row.source,
9
- tier: row.tier,
10
- confidence: row.confidence,
11
- embedding: row.embedding ?? undefined,
12
- supersededBy: row.superseded_by ?? undefined,
13
- archivedAt: row.archived_at ?? undefined,
14
- createdAt: row.created_at,
15
- };
16
- }
17
- export function recordObservation(input) {
18
- const result = getDb().prepare(`
19
- INSERT INTO mem_observations (scope_id, entity_id, content, source, tier, confidence, created_at)
20
- VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
21
- `).run(input.scope_id, input.entity_id ?? null, input.content, input.source, input.tier ?? "warm", input.confidence ?? 1.0);
22
- return getObservation(Number(result.lastInsertRowid));
23
- }
24
- export function getObservation(id) {
25
- const row = getDb().prepare(`
26
- SELECT id, scope_id, entity_id, content, source, tier, confidence, embedding, superseded_by, archived_at, created_at
27
- FROM mem_observations
28
- WHERE id = ?
29
- `).get(id);
30
- return row ? toObservation(row) : undefined;
31
- }
32
- export function listObservations(input = {}) {
33
- const rows = getDb().prepare(`
34
- SELECT id, scope_id, entity_id, content, source, tier, confidence, embedding, superseded_by, archived_at, created_at
35
- FROM mem_observations
36
- WHERE (? IS NULL OR scope_id = ?)
37
- AND (? IS NULL OR entity_id = ?)
38
- ORDER BY id DESC
39
- LIMIT ? OFFSET ?
40
- `).all(input.scope_id ?? null, input.scope_id ?? null, input.entity_id ?? null, input.entity_id ?? null, input.limit ?? 50, input.offset ?? 0);
41
- return rows.map(toObservation);
42
- }
43
- export function deleteObservation(id) {
44
- getDb().prepare(`DELETE FROM mem_observations WHERE id = ?`).run(id);
45
- }
46
- //# sourceMappingURL=observations.js.map
@@ -1,86 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdirSync, rmSync } from "node:fs";
3
- import { join } from "node:path";
4
- import test from "node:test";
5
- const repoRoot = process.cwd();
6
- const sandboxRoot = join(repoRoot, ".test-work", `memory-observations-${process.pid}`);
7
- const chapterhouseHome = join(sandboxRoot, ".chapterhouse");
8
- process.env.CHAPTERHOUSE_HOME = sandboxRoot;
9
- function resetSandbox() {
10
- mkdirSync(join(repoRoot, ".test-work"), { recursive: true });
11
- rmSync(sandboxRoot, { recursive: true, force: true });
12
- mkdirSync(chapterhouseHome, { recursive: true });
13
- }
14
- async function loadModules() {
15
- const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
16
- const memoryModule = await import(new URL("./index.js", import.meta.url).href);
17
- return { dbModule, memoryModule };
18
- }
19
- function getFunction(module, name) {
20
- const value = module[name];
21
- assert.equal(typeof value, "function", `expected ${name} to be exported`);
22
- return value;
23
- }
24
- test.beforeEach(async () => {
25
- const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
26
- dbModule.closeDb();
27
- resetSandbox();
28
- });
29
- test.after(async () => {
30
- const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
31
- dbModule.closeDb();
32
- rmSync(sandboxRoot, { recursive: true, force: true });
33
- });
34
- test("recordObservation supports CRUD and keeps the observation FTS index in sync", async () => {
35
- const { dbModule, memoryModule } = await loadModules();
36
- const db = dbModule.getDb();
37
- const getScope = getFunction(memoryModule, "getScope");
38
- const recordObservation = getFunction(memoryModule, "recordObservation");
39
- const getObservation = getFunction(memoryModule, "getObservation");
40
- const listObservations = getFunction(memoryModule, "listObservations");
41
- const deleteObservation = getFunction(memoryModule, "deleteObservation");
42
- const chapterhouse = getScope("chapterhouse");
43
- assert.ok(chapterhouse, "chapterhouse scope should be seeded");
44
- const created = recordObservation({
45
- scope_id: chapterhouse.id,
46
- content: "SQLite WAL mode keeps Chapterhouse memory writes fast.",
47
- source: "user",
48
- tier: "hot",
49
- confidence: 0.9,
50
- });
51
- assert.equal(created.content, "SQLite WAL mode keeps Chapterhouse memory writes fast.");
52
- assert.deepEqual(getObservation(created.id)?.content, created.content);
53
- assert.equal(listObservations({ scope_id: chapterhouse.id }).some((row) => row.id === created.id), true);
54
- const initialHits = db.prepare(`
55
- SELECT rowid
56
- FROM mem_observations_fts
57
- WHERE mem_observations_fts MATCH 'WAL'
58
- `).all();
59
- assert.deepEqual(initialHits, [{ rowid: created.id }]);
60
- db.prepare(`
61
- UPDATE mem_observations
62
- SET content = ?
63
- WHERE id = ?
64
- `).run("FTS rebuild test for hot-tier prompts and scoped recall.", created.id);
65
- const walHits = db.prepare(`
66
- SELECT rowid
67
- FROM mem_observations_fts
68
- WHERE mem_observations_fts MATCH 'WAL'
69
- `).all();
70
- const rebuildHits = db.prepare(`
71
- SELECT rowid
72
- FROM mem_observations_fts
73
- WHERE mem_observations_fts MATCH 'rebuild'
74
- `).all();
75
- assert.deepEqual(walHits, []);
76
- assert.deepEqual(rebuildHits, [{ rowid: created.id }]);
77
- deleteObservation(created.id);
78
- assert.equal(getObservation(created.id), undefined);
79
- const postDeleteHits = db.prepare(`
80
- SELECT rowid
81
- FROM mem_observations_fts
82
- WHERE mem_observations_fts MATCH 'rebuild'
83
- `).all();
84
- assert.deepEqual(postDeleteHits, []);
85
- });
86
- //# sourceMappingURL=observations.test.js.map