chapterhouse 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/agents/korg.agent.md +65 -0
  2. package/dist/api/korg.js +34 -0
  3. package/dist/api/korg.test.js +42 -0
  4. package/dist/api/server.js +238 -2
  5. package/dist/api/server.test.js +199 -0
  6. package/dist/config.js +28 -0
  7. package/dist/config.test.js +20 -0
  8. package/dist/copilot/agents.js +3 -4
  9. package/dist/copilot/agents.test.js +12 -1
  10. package/dist/copilot/orchestrator.js +12 -1
  11. package/dist/copilot/orchestrator.test.js +3 -7
  12. package/dist/copilot/system-message.js +11 -10
  13. package/dist/copilot/system-message.test.js +6 -1
  14. package/dist/copilot/tools.js +184 -376
  15. package/dist/copilot/tools.memory.test.js +32 -0
  16. package/dist/copilot/tools.wiki.test.js +53 -59
  17. package/dist/daemon.js +9 -0
  18. package/dist/memory/decisions.js +6 -5
  19. package/dist/memory/entities.js +20 -9
  20. package/dist/memory/hooks.js +151 -0
  21. package/dist/memory/hooks.test.js +325 -0
  22. package/dist/memory/hot-tier.js +37 -0
  23. package/dist/memory/hot-tier.test.js +30 -0
  24. package/dist/memory/housekeeping-scheduler.js +35 -0
  25. package/dist/memory/housekeeping-scheduler.test.js +50 -0
  26. package/dist/memory/inbox.js +10 -0
  27. package/dist/memory/index.js +3 -1
  28. package/dist/memory/migration.js +244 -0
  29. package/dist/memory/migration.test.js +100 -0
  30. package/dist/memory/reflect.js +273 -0
  31. package/dist/memory/reflect.test.js +254 -0
  32. package/dist/store/db.js +119 -4
  33. package/dist/store/db.test.js +19 -1
  34. package/dist/test/setup-env.js +1 -0
  35. package/dist/wiki/consolidation.js +641 -0
  36. package/dist/wiki/consolidation.test.js +140 -0
  37. package/dist/wiki/frontmatter.js +48 -0
  38. package/dist/wiki/frontmatter.test.js +42 -0
  39. package/dist/wiki/index-manager.js +246 -330
  40. package/dist/wiki/index-manager.test.js +138 -145
  41. package/dist/wiki/ingest.js +347 -0
  42. package/dist/wiki/ingest.test.js +111 -0
  43. package/dist/wiki/links.js +151 -0
  44. package/dist/wiki/links.test.js +176 -0
  45. package/dist/wiki/migrate-topics.test.js +16 -6
  46. package/dist/wiki/scheduler.js +118 -0
  47. package/dist/wiki/scheduler.test.js +64 -0
  48. package/dist/wiki/timeline.js +51 -0
  49. package/dist/wiki/timeline.test.js +65 -0
  50. package/dist/wiki/topic-structure.js +1 -1
  51. package/package.json +1 -1
  52. package/skills/pkb-ideas/SKILL.md +78 -0
  53. package/skills/pkb-ideas/_meta.json +4 -0
  54. package/skills/pkb-org/SKILL.md +82 -0
  55. package/skills/pkb-org/_meta.json +4 -0
  56. package/skills/pkb-people/SKILL.md +74 -0
  57. package/skills/pkb-people/_meta.json +4 -0
  58. package/skills/pkb-research/SKILL.md +83 -0
  59. package/skills/pkb-research/_meta.json +4 -0
  60. package/skills/pkb-source/SKILL.md +38 -0
  61. package/skills/pkb-source/_meta.json +4 -0
  62. package/skills/wiki-conventions/SKILL.md +5 -5
  63. package/web/dist/assets/{index-DuKYxMIR.css → index-5kz9aRU9.css} +1 -1
  64. package/web/dist/assets/{index-DytB69KC.js → index-BbX9RKf3.js} +91 -89
  65. package/web/dist/assets/index-BbX9RKf3.js.map +1 -0
  66. package/web/dist/index.html +2 -2
  67. package/dist/wiki/context.js +0 -138
  68. package/dist/wiki/fix.js +0 -335
  69. package/dist/wiki/fix.test.js +0 -350
  70. package/dist/wiki/lint.js +0 -451
  71. package/dist/wiki/lint.test.js +0 -329
  72. package/web/dist/assets/index-DytB69KC.js.map +0 -1
@@ -1,329 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdirSync, rmSync, utimesSync } 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", `wiki-lint-${process.pid}`);
7
- const wikiDir = join(sandboxRoot, ".chapterhouse", "wiki");
8
- process.env.CHAPTERHOUSE_HOME = sandboxRoot;
9
- async function loadModules() {
10
- const nonce = `${Date.now()}-${Math.random()}`;
11
- const lint = await import(new URL(`./lint.js?case=${nonce}`, import.meta.url).href);
12
- const wikiFs = await import(new URL(`./fs.js?case=${nonce}`, import.meta.url).href);
13
- return { lint, wikiFs };
14
- }
15
- function resetSandbox() {
16
- mkdirSync(join(repoRoot, ".test-work"), { recursive: true });
17
- rmSync(sandboxRoot, { recursive: true, force: true });
18
- }
19
- test.beforeEach(() => {
20
- resetSandbox();
21
- });
22
- test.after(() => {
23
- rmSync(sandboxRoot, { recursive: true, force: true });
24
- });
25
- function wikiPage(frontmatter, body) {
26
- return `---\n${frontmatter.join("\n")}\n---\n\n${body.trim()}\n`;
27
- }
28
- function wikiIndexWithSharedEntries(...entries) {
29
- return `# Wiki Index
30
-
31
- ## Index
32
-
33
- - [Wiki](pages/index.md) — Index of all wiki pages.
34
-
35
- ## Shared
36
-
37
- ${entries.join("\n")}
38
- `;
39
- }
40
- function setPageAge(relativePath, ageInDays) {
41
- const stamped = new Date(Date.now() - ageInDays * 24 * 60 * 60 * 1000);
42
- const fullPath = join(wikiDir, relativePath);
43
- utimesSync(fullPath, stamped, stamped);
44
- }
45
- test("lintWiki reports orphan pages and index entries pointing to missing pages", async () => {
46
- const { lint, wikiFs } = await loadModules();
47
- wikiFs.ensureWikiStructure();
48
- wikiFs.writePage("pages/shared/orphan.md", wikiPage([
49
- "title: Orphan",
50
- "summary: Not indexed yet",
51
- "updated: 2026-05-12",
52
- "tags: [engineering]",
53
- ], "# Orphan\n\nThis orphan page has enough descriptive body text to avoid the premature-page lint while still remaining absent from the index."));
54
- wikiFs.writeIndexFile(wikiIndexWithSharedEntries("- [Missing](pages/shared/missing.md) — Not on disk"));
55
- const report = lint.lintWiki();
56
- assert.equal(report.pageCount, 2);
57
- assert.equal(report.sourceCount, 0);
58
- assert.deepEqual(report.issues.map((issue) => `${issue.rule}:${issue.severity}:${issue.path ?? "-"}`).sort(), [
59
- "missing-page:warning:pages/shared/missing.md",
60
- "orphan-page:warning:pages/shared/orphan.md",
61
- ]);
62
- });
63
- test("renderWikiLintReport preserves the current wiki_lint report format", async () => {
64
- const { lint, wikiFs } = await loadModules();
65
- wikiFs.ensureWikiStructure();
66
- wikiFs.writePage("pages/shared/orphan.md", wikiPage([
67
- "title: Orphan",
68
- "summary: Not indexed yet",
69
- "updated: 2026-05-12",
70
- "tags: [engineering]",
71
- ], "# Orphan\n\nThis orphan page has enough descriptive body text to avoid the premature-page lint while still remaining absent from the index."));
72
- wikiFs.writeIndexFile(wikiIndexWithSharedEntries("- [Tracked](pages/shared/tracked.md) — Present only in the index"));
73
- const rendered = lint.renderWikiLintReport(lint.lintWiki());
74
- assert.match(rendered, /^Wiki health report \(2 pages, 0 sources\):/);
75
- assert.match(rendered, /warning\s+\|\s+orphan-page\s+\|\s+pages\/shared\/orphan\.md/);
76
- assert.match(rendered, /warning\s+\|\s+missing-page\s+\|\s+pages\/shared\/tracked\.md/);
77
- assert.match(rendered, /\*\*Suggestions\*\*: Look for pages that should link to each other/);
78
- });
79
- test("renderWikiLintReport reports the healthy wiki message when no issues are found", async () => {
80
- const { lint, wikiFs } = await loadModules();
81
- wikiFs.ensureWikiStructure();
82
- wikiFs.writePage("pages/shared/indexed.md", wikiPage([
83
- "title: Indexed",
84
- "summary: Tracked page",
85
- "updated: 2026-05-12",
86
- "tags: [engineering]",
87
- ], "# Indexed\n\n## Overview\n\nTracked page with enough detail to stay above the premature-page threshold and remain clearly established as a real page in the healthy-wiki case."));
88
- wikiFs.writeIndexFile(wikiIndexWithSharedEntries("- [Indexed](pages/shared/indexed.md) — Tracked page"));
89
- const rendered = lint.renderWikiLintReport(lint.lintWiki());
90
- assert.match(rendered, /✅ No issues found\. Index and pages are in sync\./);
91
- });
92
- test("lintWiki reports the remaining Phase 1 structural and content rules", async () => {
93
- const { lint, wikiFs } = await loadModules();
94
- wikiFs.writePage("pages/_meta/taxonomy.md", `## Engineering
95
- - engineering
96
- - release
97
-
98
- ## Unused
99
- - ghost-tag
100
- `);
101
- wikiFs.writePage("pages/projects/chapterhouse/feature-ideas.md", wikiPage([
102
- "title: Chapterhouse ideas",
103
- "summary: Candidate follow-up work",
104
- "updated: 2026-05-12",
105
- "tags: [engineering]",
106
- ], "# Chapterhouse ideas\n\n## Ideas\n\n- Add a conventions skill."));
107
- setPageAge("pages/projects/chapterhouse/feature-ideas.md", 91);
108
- wikiFs.writePage("pages/shared/missing-updated.md", wikiPage([
109
- "title: Missing updated",
110
- "summary: This page omits the updated field",
111
- "tags: [engineering]",
112
- ], "# Missing updated\n\nThis page forgot its date stamp."));
113
- wikiFs.writePage("pages/shared/invalid-frontmatter.md", wikiPage([
114
- "title: Invalid frontmatter",
115
- "summary: **Not plain text**",
116
- "tags: [engineering, unknown-tag]",
117
- ], "# Invalid frontmatter\n\nBody content."));
118
- wikiFs.writePage("pages/shared/skipped-heading.md", wikiPage([
119
- "title: Skipped heading",
120
- "summary: Heading depth should not skip levels",
121
- "updated: 2026-05-12",
122
- "tags: [engineering]",
123
- ], "# Skipped heading\n\n### Too deep"));
124
- wikiFs.writePage("pages/shared/decisions.md", wikiPage([
125
- "title: Misfiled decisions",
126
- "summary: Decisions must live under an entity directory",
127
- "updated: 2026-05-12",
128
- "tags: [decision]",
129
- ], "# Misfiled decisions\n\n## Record\n\nA misplaced decision log."));
130
- wikiFs.writePage("pages/shared/large-warning.md", wikiPage([
131
- "title: Large warning",
132
- "summary: Page size warning threshold coverage",
133
- "updated: 2026-05-12",
134
- "tags: [engineering]",
135
- ], `# Large warning\n\n${Array.from({ length: 301 }, (_, index) => `Line ${index + 1}`).join("\n")}`));
136
- wikiFs.writePage("pages/shared/large-error.md", wikiPage([
137
- "title: Large error",
138
- "summary: Page size error threshold coverage",
139
- "updated: 2026-05-12",
140
- "tags: [engineering]",
141
- ], `# Large error\n\n${Array.from({ length: 801 }, (_, index) => `Line ${index + 1}`).join("\n")}`));
142
- wikiFs.writePage("pages/shared/tiny.md", wikiPage([
143
- "title: Tiny page",
144
- "summary: Tiny pages should be linted as premature",
145
- "updated: 2026-05-12",
146
- "tags: [engineering]",
147
- ], "# Tiny page\n\nTiny."));
148
- wikiFs.writePage("pages/shared/stub.md", wikiPage([
149
- "title: Stub page",
150
- "summary: Stub pages should be flipped once they grow up",
151
- "autostub: true",
152
- "tags: [autostub]",
153
- ], `# Stub page\n\n${Array.from({ length: 12 }, (_, index) => `- fleshed out line ${index + 1}`).join("\n")}`));
154
- const report = lint.lintWiki();
155
- const signatures = report.issues.map((issue) => `${issue.rule}:${issue.severity}:${issue.path ?? "-"}`);
156
- assert.ok(signatures.includes("index-integrity:error:pages/projects/chapterhouse"));
157
- assert.ok(signatures.includes("stale-page:warning:pages/projects/chapterhouse/feature-ideas.md"));
158
- assert.ok(signatures.includes("missing-updated:warning:pages/shared/missing-updated.md"));
159
- assert.ok(signatures.includes("frontmatter-shape:error:pages/shared/invalid-frontmatter.md"));
160
- assert.ok(signatures.includes("heading-depth:warning:pages/shared/skipped-heading.md"));
161
- assert.ok(signatures.includes("decision-misfile:error:pages/shared/decisions.md"));
162
- assert.ok(signatures.includes("page-size:warning:pages/shared/large-warning.md"));
163
- assert.ok(signatures.includes("page-size:error:pages/shared/large-error.md"));
164
- assert.ok(signatures.includes("premature-page:info:pages/shared/tiny.md"));
165
- assert.ok(signatures.includes("autostub-not-flipped:info:pages/shared/stub.md"));
166
- assert.ok(signatures.includes("dead-taxonomy-entry:info:pages/_meta/taxonomy.md"));
167
- });
168
- test("lintWiki exempts decision and conversation pages from staleness and exempts autostubs from selected rules", async () => {
169
- const { lint, wikiFs } = await loadModules();
170
- wikiFs.writePage("pages/projects/chapterhouse/index.md", wikiPage([
171
- "title: Chapterhouse",
172
- "summary: Overview page required for entity facets",
173
- "updated: 2026-05-12",
174
- "tags: [engineering]",
175
- ], "# Chapterhouse\n\n## Overview\n\nProject overview."));
176
- wikiFs.writePage("pages/projects/chapterhouse/decisions.md", wikiPage([
177
- "title: Chapterhouse decisions",
178
- "summary: Decision logs should never go stale",
179
- "updated: 2026-05-12",
180
- "tags: [decision]",
181
- ], "# Chapterhouse decisions\n\n## Decisions\n\nRecorded decisions."));
182
- setPageAge("pages/projects/chapterhouse/decisions.md", 365);
183
- wikiFs.writePage("pages/conversations/2026-01-01.md", wikiPage([
184
- "title: Conversations on 2026-01-01",
185
- "summary: Daily conversation summary",
186
- "updated: 2026-01-01",
187
- "tags: [engineering]",
188
- ], "# Conversations on 2026-01-01\n\nSummary body."));
189
- setPageAge("pages/conversations/2026-01-01.md", 365);
190
- wikiFs.writePage("pages/shared/autostub.md", wikiPage([
191
- "title: Stub",
192
- "summary: Placeholder page",
193
- "autostub: true",
194
- "tags: [autostub]",
195
- ], `# Stub\n\n### Placeholder\n\n${Array.from({ length: 900 }, (_, index) => `Line ${index + 1}`).join("\n")}`));
196
- const report = lint.lintWiki();
197
- const signatures = report.issues.map((issue) => `${issue.rule}:${issue.path ?? "-"}`);
198
- assert.equal(signatures.includes("stale-page:pages/projects/chapterhouse/decisions.md"), false);
199
- assert.equal(signatures.includes("stale-page:pages/conversations/2026-01-01.md"), false);
200
- assert.equal(signatures.includes("missing-updated:pages/shared/autostub.md"), false);
201
- assert.equal(signatures.includes("premature-page:pages/shared/autostub.md"), false);
202
- assert.equal(signatures.includes("heading-depth:pages/shared/autostub.md"), false);
203
- assert.equal(signatures.includes("page-size:pages/shared/autostub.md"), false);
204
- });
205
- test("lintWiki accepts valid project rules frontmatter on rules.md pages", async () => {
206
- const { lint, wikiFs } = await loadModules();
207
- wikiFs.ensureWikiStructure();
208
- wikiFs.writePage("pages/projects/chapterhouse/index.md", wikiPage([
209
- "title: Chapterhouse",
210
- "summary: Project overview",
211
- "updated: 2026-05-12",
212
- "tags: [engineering]",
213
- ], "# Chapterhouse\n\n## Overview\n\nProject overview body with enough content to stay out of the premature-page bucket."));
214
- wikiFs.writePage("pages/projects/chapterhouse/rules.md", wikiPage([
215
- "title: Project rules for chapterhouse",
216
- "summary: Project-specific operating rules for Chapterhouse and delegated agents.",
217
- "updated: 2026-05-12",
218
- "tags: [engineering]",
219
- "auto_pr: true",
220
- "require_worktree: false",
221
- "pr_draft_default: false",
222
- "default_branch: main",
223
- 'commit_co_author: "Copilot <223556219+Copilot@users.noreply.github.com>"',
224
- 'test_command: "npm test"',
225
- 'build_command: "npm run build"',
226
- 'lint_command: "npm run lint"',
227
- "require_clean_worktree: true",
228
- ], "# Project rules\n\n## Soft Rules\n\n- Keep rules concise and operational.\n- Record workflow changes here when they become durable."));
229
- const report = lint.lintWiki();
230
- assert.equal(report.issues.some((issue) => issue.path === "pages/projects/chapterhouse/rules.md" && issue.rule === "frontmatter-shape"), false);
231
- assert.equal(report.issues.some((issue) => issue.path === "pages/projects/chapterhouse/rules.md" && issue.rule === "unknown-project-rule-key"), false);
232
- });
233
- test("lintWiki reports invalid known project rule field types on rules.md pages", async () => {
234
- const { lint, wikiFs } = await loadModules();
235
- wikiFs.ensureWikiStructure();
236
- wikiFs.writePage("pages/projects/chapterhouse/index.md", wikiPage([
237
- "title: Chapterhouse",
238
- "summary: Project overview",
239
- "updated: 2026-05-12",
240
- "tags: [engineering]",
241
- ], "# Chapterhouse\n\n## Overview\n\nProject overview body with enough content to stay out of the premature-page bucket."));
242
- wikiFs.writePage("pages/projects/chapterhouse/rules.md", wikiPage([
243
- "title: Project rules for chapterhouse",
244
- "summary: Project-specific operating rules for Chapterhouse and delegated agents.",
245
- "updated: 2026-05-12",
246
- "tags: [engineering]",
247
- "auto_pr: [true]",
248
- ], "# Project rules\n\n## Soft Rules\n\n- Keep rules concise and operational."));
249
- const report = lint.lintWiki();
250
- const issue = report.issues.find((candidate) => candidate.path === "pages/projects/chapterhouse/rules.md" && candidate.rule === "frontmatter-shape");
251
- assert.ok(issue);
252
- assert.equal(issue.severity, "error");
253
- assert.match(issue.message, /invalid 'auto_pr' type/);
254
- });
255
- test("lintWiki warns on unknown project rule keys on rules.md pages", async () => {
256
- const { lint, wikiFs } = await loadModules();
257
- wikiFs.ensureWikiStructure();
258
- wikiFs.writePage("pages/projects/chapterhouse/index.md", wikiPage([
259
- "title: Chapterhouse",
260
- "summary: Project overview",
261
- "updated: 2026-05-12",
262
- "tags: [engineering]",
263
- ], "# Chapterhouse\n\n## Overview\n\nProject overview body with enough content to stay out of the premature-page bucket."));
264
- wikiFs.writePage("pages/projects/chapterhouse/rules.md", wikiPage([
265
- "title: Project rules for chapterhouse",
266
- "summary: Project-specific operating rules for Chapterhouse and delegated agents.",
267
- "updated: 2026-05-12",
268
- "tags: [engineering]",
269
- "mystery_setting: true",
270
- ], "# Project rules\n\n## Soft Rules\n\n- Keep rules concise and operational."));
271
- const report = lint.lintWiki();
272
- assert.ok(report.issues.some((issue) => issue.rule === "unknown-project-rule-key" &&
273
- issue.severity === "warning" &&
274
- issue.path === "pages/projects/chapterhouse/rules.md" &&
275
- issue.message.includes("mystery_setting")));
276
- });
277
- test("renderWikiLintReport surfaces contested and low-confidence pages ahead of the general issue list", async () => {
278
- const { lint, wikiFs } = await loadModules();
279
- wikiFs.writePage("pages/shared/contested.md", wikiPage([
280
- "title: Contested page",
281
- "summary: This page is explicitly contested",
282
- "updated: 2026-05-12",
283
- "tags: [engineering]",
284
- "contested: true",
285
- ], "# Contested page\n\nThis page needs review."));
286
- wikiFs.writePage("pages/shared/low-confidence.md", wikiPage([
287
- "title: Low confidence page",
288
- "summary: This page has low confidence",
289
- "updated: 2026-05-12",
290
- "tags: [engineering]",
291
- "confidence: low",
292
- ], "# Low confidence page\n\nThis page needs review."));
293
- wikiFs.writePage("pages/shared/missing-updated.md", wikiPage([
294
- "title: Missing updated",
295
- "summary: A regular issue should come after contested pages",
296
- "tags: [engineering]",
297
- ], "# Missing updated\n\nBody."));
298
- const rendered = lint.renderWikiLintReport(lint.lintWiki());
299
- const contestedIndex = rendered.indexOf("pages/shared/contested.md");
300
- const lowConfidenceIndex = rendered.indexOf("pages/shared/low-confidence.md");
301
- const generalIssueIndex = rendered.indexOf("missing-updated");
302
- assert.ok(contestedIndex >= 0);
303
- assert.ok(lowConfidenceIndex >= 0);
304
- assert.ok(generalIssueIndex >= 0);
305
- assert.ok(contestedIndex < generalIssueIndex);
306
- assert.ok(lowConfidenceIndex < generalIssueIndex);
307
- });
308
- test("lintWiki reports a missing or unwritable action log", async () => {
309
- const { lint, wikiFs } = await loadModules();
310
- wikiFs.ensureWikiStructure();
311
- wikiFs.writePage("pages/shared/healthy.md", wikiPage([
312
- "title: Healthy page",
313
- "summary: Enough content to avoid incidental lint issues",
314
- "updated: 2026-05-12",
315
- "tags: [engineering]",
316
- ], "# Healthy page\n\n## Overview\n\nThis page exists so the action-log rule is isolated from unrelated findings in the test."));
317
- rmSync(join(wikiDir, "pages", "_meta", "log.md"), { force: true });
318
- let report = lint.lintWiki();
319
- assert.ok(report.issues.some((issue) => issue.rule === "action-log" &&
320
- issue.severity === "error" &&
321
- issue.path === "pages/_meta/log.md"));
322
- rmSync(join(wikiDir, "pages", "_meta"), { recursive: true, force: true });
323
- mkdirSync(join(wikiDir, "pages", "_meta", "log.md"), { recursive: true });
324
- report = lint.lintWiki();
325
- assert.ok(report.issues.some((issue) => issue.rule === "action-log" &&
326
- issue.severity === "error" &&
327
- issue.path === "pages/_meta/log.md"));
328
- });
329
- //# sourceMappingURL=lint.test.js.map