chapterhouse 0.3.17 → 0.3.19
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/server-runtime.js +0 -16
- package/dist/api/server.js +21 -14
- package/dist/api/server.test.js +57 -26
- package/dist/copilot/skills.test.js +4 -0
- package/dist/copilot/tools.js +32 -0
- package/dist/copilot/tools.wiki.test.js +46 -0
- package/dist/wiki/fix.js +335 -0
- package/dist/wiki/fix.test.js +350 -0
- package/dist/wiki/frontmatter.js +105 -0
- package/dist/wiki/frontmatter.test.js +120 -0
- package/dist/wiki/fs.js +16 -0
- package/dist/wiki/fs.test.js +19 -1
- package/dist/wiki/index-manager.test.js +13 -1
- package/dist/wiki/lint.test.js +17 -20
- package/dist/wiki/topic-structure.js +3 -1
- package/dist/wiki/topic-structure.test.js +11 -0
- package/package.json +1 -1
- package/web/dist/assets/{index-BYuMgJ36.js → index-9We9vWBC.js} +63 -63
- package/web/dist/assets/index-9We9vWBC.js.map +1 -0
- package/web/dist/assets/{index-lvHFM_ut.css → index-DYx2idiH.css} +1 -1
- package/web/dist/index.html +2 -2
- package/skills/squad/SKILL.md +0 -76
- package/web/dist/assets/index-BYuMgJ36.js.map +0 -1
|
@@ -58,10 +58,19 @@ test("buildIndexEntryForPage treats frontmatter summary as the canonical index s
|
|
|
58
58
|
});
|
|
59
59
|
test("parseIndex self-heals an empty index from on-disk pages", async () => {
|
|
60
60
|
const { indexManager, wikiFs } = await loadModules();
|
|
61
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
61
62
|
wikiFs.writePage("pages/team/vision.md", "# Vision\n\nShared direction for the team.\n");
|
|
62
63
|
wikiFs.writeIndexFile("# Wiki Index\n\n");
|
|
63
64
|
const entries = indexManager.parseIndex();
|
|
64
65
|
assert.deepEqual(entries, [
|
|
66
|
+
{
|
|
67
|
+
path: "pages/index.md",
|
|
68
|
+
title: "Wiki",
|
|
69
|
+
summary: "Index of all wiki pages.",
|
|
70
|
+
section: "Knowledge",
|
|
71
|
+
tags: undefined,
|
|
72
|
+
updated: today,
|
|
73
|
+
},
|
|
65
74
|
{
|
|
66
75
|
path: "pages/team/vision.md",
|
|
67
76
|
title: "Vision",
|
|
@@ -119,6 +128,7 @@ test("searchIndex ranks strong metadata matches and falls back to page bodies",
|
|
|
119
128
|
});
|
|
120
129
|
test("addToIndex, removeFromIndex, and getIndexSummary keep the catalog in sync", async () => {
|
|
121
130
|
const { indexManager } = await loadModules();
|
|
131
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
122
132
|
indexManager.addToIndex({
|
|
123
133
|
path: "pages/people/ada.md",
|
|
124
134
|
title: "Ada Lovelace",
|
|
@@ -143,6 +153,8 @@ test("addToIndex, removeFromIndex, and getIndexSummary keep the catalog in sync"
|
|
|
143
153
|
});
|
|
144
154
|
assert.equal(indexManager.removeFromIndex("pages/projects/launch.md"), true);
|
|
145
155
|
assert.equal(indexManager.removeFromIndex("pages/projects/missing.md"), false);
|
|
146
|
-
|
|
156
|
+
const summary = indexManager.getIndexSummary();
|
|
157
|
+
assert.match(summary, /\*\*People\*\*: Ada Lovelace: Owns regression coverage \[qa, testing\] \(2026-05-06\)/);
|
|
158
|
+
assert.match(summary, new RegExp(`\\*\\*Index\\*\\*: Wiki: Index of all wiki pages\\. \\(${today}\\)`));
|
|
147
159
|
});
|
|
148
160
|
//# sourceMappingURL=index-manager.test.js.map
|
package/dist/wiki/lint.test.js
CHANGED
|
@@ -25,6 +25,18 @@ test.after(() => {
|
|
|
25
25
|
function wikiPage(frontmatter, body) {
|
|
26
26
|
return `---\n${frontmatter.join("\n")}\n---\n\n${body.trim()}\n`;
|
|
27
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
|
+
}
|
|
28
40
|
function setPageAge(relativePath, ageInDays) {
|
|
29
41
|
const stamped = new Date(Date.now() - ageInDays * 24 * 60 * 60 * 1000);
|
|
30
42
|
const fullPath = join(wikiDir, relativePath);
|
|
@@ -39,14 +51,9 @@ test("lintWiki reports orphan pages and index entries pointing to missing pages"
|
|
|
39
51
|
"updated: 2026-05-12",
|
|
40
52
|
"tags: [engineering]",
|
|
41
53
|
], "# Orphan\n\nThis orphan page has enough descriptive body text to avoid the premature-page lint while still remaining absent from the index."));
|
|
42
|
-
wikiFs.writeIndexFile(
|
|
43
|
-
|
|
44
|
-
## Shared
|
|
45
|
-
|
|
46
|
-
- [Missing](pages/shared/missing.md) — Not on disk
|
|
47
|
-
`);
|
|
54
|
+
wikiFs.writeIndexFile(wikiIndexWithSharedEntries("- [Missing](pages/shared/missing.md) — Not on disk"));
|
|
48
55
|
const report = lint.lintWiki();
|
|
49
|
-
assert.equal(report.pageCount,
|
|
56
|
+
assert.equal(report.pageCount, 2);
|
|
50
57
|
assert.equal(report.sourceCount, 0);
|
|
51
58
|
assert.deepEqual(report.issues.map((issue) => `${issue.rule}:${issue.severity}:${issue.path ?? "-"}`).sort(), [
|
|
52
59
|
"missing-page:warning:pages/shared/missing.md",
|
|
@@ -62,14 +69,9 @@ test("renderWikiLintReport preserves the current wiki_lint report format", async
|
|
|
62
69
|
"updated: 2026-05-12",
|
|
63
70
|
"tags: [engineering]",
|
|
64
71
|
], "# Orphan\n\nThis orphan page has enough descriptive body text to avoid the premature-page lint while still remaining absent from the index."));
|
|
65
|
-
wikiFs.writeIndexFile(
|
|
66
|
-
|
|
67
|
-
## Shared
|
|
68
|
-
|
|
69
|
-
- [Tracked](pages/shared/tracked.md) — Present only in the index
|
|
70
|
-
`);
|
|
72
|
+
wikiFs.writeIndexFile(wikiIndexWithSharedEntries("- [Tracked](pages/shared/tracked.md) — Present only in the index"));
|
|
71
73
|
const rendered = lint.renderWikiLintReport(lint.lintWiki());
|
|
72
|
-
assert.match(rendered, /^Wiki health report \(
|
|
74
|
+
assert.match(rendered, /^Wiki health report \(2 pages, 0 sources\):/);
|
|
73
75
|
assert.match(rendered, /warning\s+\|\s+orphan-page\s+\|\s+pages\/shared\/orphan\.md/);
|
|
74
76
|
assert.match(rendered, /warning\s+\|\s+missing-page\s+\|\s+pages\/shared\/tracked\.md/);
|
|
75
77
|
assert.match(rendered, /\*\*Suggestions\*\*: Look for pages that should link to each other/);
|
|
@@ -83,12 +85,7 @@ test("renderWikiLintReport reports the healthy wiki message when no issues are f
|
|
|
83
85
|
"updated: 2026-05-12",
|
|
84
86
|
"tags: [engineering]",
|
|
85
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."));
|
|
86
|
-
wikiFs.writeIndexFile(
|
|
87
|
-
|
|
88
|
-
## Shared
|
|
89
|
-
|
|
90
|
-
- [Indexed](pages/shared/indexed.md) — Tracked page
|
|
91
|
-
`);
|
|
88
|
+
wikiFs.writeIndexFile(wikiIndexWithSharedEntries("- [Indexed](pages/shared/indexed.md) — Tracked page"));
|
|
92
89
|
const rendered = lint.renderWikiLintReport(lint.lintWiki());
|
|
93
90
|
assert.match(rendered, /✅ No issues found\. Index and pages are in sync\./);
|
|
94
91
|
});
|
|
@@ -21,6 +21,7 @@ import { normalizeWikiPath } from "./path-utils.js";
|
|
|
21
21
|
export const FLAT_CATEGORIES = ["preferences", "facts", "routines"];
|
|
22
22
|
/** Path prefixes (relative to the wiki root) that follow their own conventions. */
|
|
23
23
|
export const EXEMPT_PREFIXES = [
|
|
24
|
+
"pages/_meta/",
|
|
24
25
|
"pages/conversations/",
|
|
25
26
|
"pages/team/",
|
|
26
27
|
"pages/okrs/",
|
|
@@ -158,7 +159,8 @@ export function validateTopicPath(relativePath) {
|
|
|
158
159
|
ok: false,
|
|
159
160
|
reason: `'${topDir}' is not a recognized wiki category. ` +
|
|
160
161
|
`Use one of: ${valid} (entity categories take a topic directory: pages/<category>/<topic>/<page>.md). ` +
|
|
161
|
-
`Conversations and team pages are written by the system
|
|
162
|
+
`Conversations and team pages are written by the system. ` +
|
|
163
|
+
`(pages/_meta/ is reserved for system metadata.)`,
|
|
162
164
|
};
|
|
163
165
|
}
|
|
164
166
|
/** Convenience: returns the error message string for an invalid path, or undefined if valid. */
|
|
@@ -35,6 +35,16 @@ test("validateTopicPath accepts canonical and exempt paths", () => {
|
|
|
35
35
|
assert.deepEqual(validateTopicPath("pages/team/onboarding.md"), { ok: true });
|
|
36
36
|
assert.deepEqual(validateTopicPath("pages/okrs/2026-Q2.md"), { ok: true });
|
|
37
37
|
});
|
|
38
|
+
test("validateTopicPath accepts system-managed pages/_meta subtree paths", () => {
|
|
39
|
+
assert.deepEqual(validateTopicPath("pages/_meta/log.md"), { ok: true });
|
|
40
|
+
assert.deepEqual(validateTopicPath("pages/_meta/taxonomy.md"), { ok: true });
|
|
41
|
+
assert.deepEqual(validateTopicPath("pages/_meta/log-2026.md"), { ok: true });
|
|
42
|
+
assert.deepEqual(validateTopicPath("pages/_meta/sub/file.md"), { ok: true });
|
|
43
|
+
});
|
|
44
|
+
test("validateTopicPath rejects pages/_meta without a page file", () => {
|
|
45
|
+
const r = validateTopicPath("pages/_meta");
|
|
46
|
+
assert.equal(r.ok, false);
|
|
47
|
+
});
|
|
38
48
|
test("validateTopicPath rejects a bare entity-category file and suggests the topic index", () => {
|
|
39
49
|
const r = validateTopicPath("pages/projects/chapterhouse.md");
|
|
40
50
|
assert.equal(r.ok, false);
|
|
@@ -61,6 +71,7 @@ test("validateTopicPath rejects unknown top-level categories", () => {
|
|
|
61
71
|
const r = validateTopicPath("pages/randomstuff/foo.md");
|
|
62
72
|
assert.equal(r.ok, false);
|
|
63
73
|
assert.match(r.ok === false ? r.reason : "", /not a recognized wiki category/);
|
|
74
|
+
assert.match(r.ok === false ? r.reason : "", /pages\/_meta\/ is reserved for system metadata/);
|
|
64
75
|
});
|
|
65
76
|
test("validateTopicPath rejects non-slug topic or page names", () => {
|
|
66
77
|
const r = validateTopicPath("pages/projects/Chapter House/index.md");
|
package/package.json
CHANGED