chapterhouse 0.3.20 → 0.3.21
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.js +154 -3
- package/dist/api/server.test.js +461 -14
- package/dist/copilot/orchestrator.js +70 -3
- package/dist/copilot/orchestrator.test.js +337 -1
- package/dist/copilot/project-resolution.js +73 -0
- package/dist/copilot/project-resolution.test.js +124 -0
- package/dist/copilot/project-rule-warnings.js +73 -0
- package/dist/copilot/project-rule-warnings.test.js +46 -0
- package/dist/copilot/project-rules-injection.js +71 -0
- package/dist/copilot/project-rules-injection.test.js +84 -0
- package/dist/copilot/tools.agent.test.js +214 -0
- package/dist/copilot/tools.js +14 -3
- package/dist/store/db.js +4 -0
- package/dist/store/db.test.js +30 -0
- package/dist/wiki/frontmatter.js +1 -1
- package/dist/wiki/lint.js +37 -10
- package/dist/wiki/lint.test.js +72 -0
- package/dist/wiki/project-registry.js +160 -0
- package/dist/wiki/project-registry.test.js +72 -0
- package/dist/wiki/project-rules.js +155 -0
- package/dist/wiki/project-rules.test.js +217 -0
- package/package.json +1 -1
- package/web/dist/assets/{index-9We9vWBC.js → index-B8dZzlmE.js} +66 -66
- package/web/dist/assets/index-B8dZzlmE.js.map +1 -0
- package/web/dist/assets/index-D9flFppK.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-9We9vWBC.js.map +0 -1
- package/web/dist/assets/index-DYx2idiH.css +0 -10
|
@@ -0,0 +1,217 @@
|
|
|
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", `wiki-project-rules-${process.pid}`);
|
|
7
|
+
process.env.CHAPTERHOUSE_HOME = sandboxRoot;
|
|
8
|
+
async function loadModules() {
|
|
9
|
+
const nonce = `${Date.now()}-${Math.random()}`;
|
|
10
|
+
const wikiFs = await import(new URL(`./fs.js?case=${nonce}`, import.meta.url).href);
|
|
11
|
+
let projectRules;
|
|
12
|
+
let loadError;
|
|
13
|
+
try {
|
|
14
|
+
projectRules = await import(new URL(`./project-rules.js?case=${nonce}`, import.meta.url).href);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
loadError = error;
|
|
18
|
+
}
|
|
19
|
+
assert.equal(loadError, undefined, `Expected project-rules module to load without errors: ${String(loadError)}`);
|
|
20
|
+
return { projectRules: projectRules, wikiFs };
|
|
21
|
+
}
|
|
22
|
+
function resetSandbox() {
|
|
23
|
+
mkdirSync(join(repoRoot, ".test-work"), { recursive: true });
|
|
24
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
test.beforeEach(() => {
|
|
27
|
+
resetSandbox();
|
|
28
|
+
});
|
|
29
|
+
test.after(() => {
|
|
30
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
test("loadProjectRules returns an explicit empty result when rules.md is absent", async () => {
|
|
33
|
+
const { projectRules } = await loadModules();
|
|
34
|
+
assert.deepEqual(projectRules.loadProjectRules("chapterhouse"), {
|
|
35
|
+
found: false,
|
|
36
|
+
path: "pages/projects/chapterhouse/rules.md",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
test("renderInitialProjectRulesPage returns the default scaffold for a new project", async () => {
|
|
40
|
+
const { projectRules } = await loadModules();
|
|
41
|
+
assert.equal(projectRules.getProjectRulesPath("chapterhouse"), "pages/projects/chapterhouse/rules.md");
|
|
42
|
+
assert.equal(projectRules.renderInitialProjectRulesPage("chapterhouse", new Date("2026-05-13T12:34:56.000Z")), "---\n"
|
|
43
|
+
+ "title: Project rules for chapterhouse\n"
|
|
44
|
+
+ "summary: Project-specific operating rules for Chapterhouse and delegated agents.\n"
|
|
45
|
+
+ "updated: 2026-05-13\n"
|
|
46
|
+
+ "tags: [engineering, workflow]\n"
|
|
47
|
+
+ "related: []\n"
|
|
48
|
+
+ "---\n\n"
|
|
49
|
+
+ "## Soft Rules");
|
|
50
|
+
});
|
|
51
|
+
test("loadProjectRules returns typed hard rules, raw soft rules, and warnings for a valid rules page", async () => {
|
|
52
|
+
const { projectRules, wikiFs } = await loadModules();
|
|
53
|
+
wikiFs.writePage("pages/projects/chapterhouse/rules.md", "---\n"
|
|
54
|
+
+ "title: Project rules for chapterhouse\n"
|
|
55
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
56
|
+
+ "auto_pr: false\n"
|
|
57
|
+
+ "test_command: npm test\n"
|
|
58
|
+
+ "custom_rule: preserve-me\n"
|
|
59
|
+
+ "---\n\n"
|
|
60
|
+
+ "## Soft Rules\n\n"
|
|
61
|
+
+ "- Keep tests green.\n");
|
|
62
|
+
assert.deepEqual(projectRules.loadProjectRules("chapterhouse"), {
|
|
63
|
+
found: true,
|
|
64
|
+
path: "pages/projects/chapterhouse/rules.md",
|
|
65
|
+
hard: {
|
|
66
|
+
auto_pr: false,
|
|
67
|
+
require_worktree: false,
|
|
68
|
+
pr_draft_default: false,
|
|
69
|
+
default_branch: "main",
|
|
70
|
+
commit_co_author: "Copilot <223556219+Copilot@users.noreply.github.com>",
|
|
71
|
+
test_command: "npm test",
|
|
72
|
+
build_command: "",
|
|
73
|
+
lint_command: "",
|
|
74
|
+
require_clean_worktree: false,
|
|
75
|
+
},
|
|
76
|
+
soft: "## Soft Rules\n\n- Keep tests green.\n",
|
|
77
|
+
warnings: [
|
|
78
|
+
{
|
|
79
|
+
rule: "unknown-project-rule-key",
|
|
80
|
+
field: "custom_rule",
|
|
81
|
+
message: "Project rules frontmatter includes unknown key 'custom_rule'.",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
metadata: {
|
|
85
|
+
custom_rule: "preserve-me",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
test("loadProjectRules throws when rules.md contains malformed known hard-rule fields", async () => {
|
|
90
|
+
const { projectRules, wikiFs } = await loadModules();
|
|
91
|
+
wikiFs.writePage("pages/projects/chapterhouse/rules.md", "---\n"
|
|
92
|
+
+ "title: Project rules for chapterhouse\n"
|
|
93
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
94
|
+
+ "auto_pr: nope\n"
|
|
95
|
+
+ "test_command: [npm test]\n"
|
|
96
|
+
+ "---\n\n"
|
|
97
|
+
+ "## Soft Rules\n");
|
|
98
|
+
assert.throws(() => projectRules.loadProjectRules("chapterhouse"), /invalid 'auto_pr' type|invalid 'test_command' type/);
|
|
99
|
+
});
|
|
100
|
+
test("saveProjectRulesHardFields rewrites only hard-rule frontmatter fields", async () => {
|
|
101
|
+
const { projectRules, wikiFs } = await loadModules();
|
|
102
|
+
wikiFs.writePage("pages/projects/chapterhouse/rules.md", "---\n"
|
|
103
|
+
+ "title: Project rules for chapterhouse\n"
|
|
104
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
105
|
+
+ "updated: 2026-05-13\n"
|
|
106
|
+
+ "tags: [engineering, workflow]\n"
|
|
107
|
+
+ "related: []\n"
|
|
108
|
+
+ "auto_pr: false\n"
|
|
109
|
+
+ "test_command: npm test\n"
|
|
110
|
+
+ "custom_rule: preserve-me\n"
|
|
111
|
+
+ "---\n\n"
|
|
112
|
+
+ "## Soft Rules\n\n"
|
|
113
|
+
+ "- Keep tests green.\n");
|
|
114
|
+
projectRules.saveProjectRulesHardFields("chapterhouse", {
|
|
115
|
+
auto_pr: true,
|
|
116
|
+
require_worktree: true,
|
|
117
|
+
pr_draft_default: true,
|
|
118
|
+
default_branch: "release",
|
|
119
|
+
commit_co_author: "Jane Doe <jane@example.com>",
|
|
120
|
+
test_command: "pnpm test",
|
|
121
|
+
build_command: "pnpm build",
|
|
122
|
+
lint_command: "pnpm lint",
|
|
123
|
+
require_clean_worktree: true,
|
|
124
|
+
});
|
|
125
|
+
assert.equal(wikiFs.readPage("pages/projects/chapterhouse/rules.md"), "---\n"
|
|
126
|
+
+ "title: Project rules for chapterhouse\n"
|
|
127
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
128
|
+
+ "updated: 2026-05-13\n"
|
|
129
|
+
+ "tags: [engineering, workflow]\n"
|
|
130
|
+
+ "related: []\n"
|
|
131
|
+
+ "auto_pr: true\n"
|
|
132
|
+
+ "require_worktree: true\n"
|
|
133
|
+
+ "pr_draft_default: true\n"
|
|
134
|
+
+ "default_branch: release\n"
|
|
135
|
+
+ "commit_co_author: Jane Doe <jane@example.com>\n"
|
|
136
|
+
+ "test_command: pnpm test\n"
|
|
137
|
+
+ "build_command: pnpm build\n"
|
|
138
|
+
+ "lint_command: pnpm lint\n"
|
|
139
|
+
+ "require_clean_worktree: true\n"
|
|
140
|
+
+ "custom_rule: preserve-me\n"
|
|
141
|
+
+ "---\n\n"
|
|
142
|
+
+ "## Soft Rules\n\n"
|
|
143
|
+
+ "- Keep tests green.\n");
|
|
144
|
+
});
|
|
145
|
+
test("saveProjectRulesSoftRules rewrites the body as a stable soft-rule list while preserving frontmatter", async () => {
|
|
146
|
+
const { projectRules, wikiFs } = await loadModules();
|
|
147
|
+
wikiFs.writePage("pages/projects/chapterhouse/rules.md", "---\n"
|
|
148
|
+
+ "title: Project rules for chapterhouse\n"
|
|
149
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
150
|
+
+ "updated: 2026-05-13\n"
|
|
151
|
+
+ "tags: [engineering, workflow]\n"
|
|
152
|
+
+ "related: []\n"
|
|
153
|
+
+ "auto_pr: false\n"
|
|
154
|
+
+ "test_command: npm test\n"
|
|
155
|
+
+ "custom_rule: preserve-me\n"
|
|
156
|
+
+ "---\n\n"
|
|
157
|
+
+ "## Soft Rules\n\n"
|
|
158
|
+
+ "Intro text that should be replaced.\n\n"
|
|
159
|
+
+ "- Keep tests green.\n"
|
|
160
|
+
+ "* Prefer worktrees for PR work.\n");
|
|
161
|
+
projectRules.saveProjectRulesSoftRules("chapterhouse", [
|
|
162
|
+
"Ship the smallest safe slice.",
|
|
163
|
+
"Document deferred work in the PR body.",
|
|
164
|
+
]);
|
|
165
|
+
assert.equal(wikiFs.readPage("pages/projects/chapterhouse/rules.md"), "---\n"
|
|
166
|
+
+ "title: Project rules for chapterhouse\n"
|
|
167
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
168
|
+
+ "updated: 2026-05-13\n"
|
|
169
|
+
+ "tags: [engineering, workflow]\n"
|
|
170
|
+
+ "related: []\n"
|
|
171
|
+
+ "auto_pr: false\n"
|
|
172
|
+
+ "require_worktree: false\n"
|
|
173
|
+
+ "pr_draft_default: false\n"
|
|
174
|
+
+ "default_branch: main\n"
|
|
175
|
+
+ "commit_co_author: Copilot <223556219+Copilot@users.noreply.github.com>\n"
|
|
176
|
+
+ "test_command: npm test\n"
|
|
177
|
+
+ "build_command: \n"
|
|
178
|
+
+ "lint_command: \n"
|
|
179
|
+
+ "require_clean_worktree: false\n"
|
|
180
|
+
+ "custom_rule: preserve-me\n"
|
|
181
|
+
+ "---\n\n"
|
|
182
|
+
+ "## Soft Rules\n\n"
|
|
183
|
+
+ "- Ship the smallest safe slice.\n"
|
|
184
|
+
+ "- Document deferred work in the PR body.\n");
|
|
185
|
+
});
|
|
186
|
+
test("loadProjectRuleSummary returns null counts when rules.md is absent", async () => {
|
|
187
|
+
const { projectRules } = await loadModules();
|
|
188
|
+
assert.deepEqual(projectRules.loadProjectRuleSummary("chapterhouse"), {
|
|
189
|
+
slug: "chapterhouse",
|
|
190
|
+
path: "pages/projects/chapterhouse/rules.md",
|
|
191
|
+
hardRuleCount: null,
|
|
192
|
+
softRuleCount: null,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
test("loadProjectRuleSummary counts explicit hard-rule keys and top-level soft-rule bullets", async () => {
|
|
196
|
+
const { projectRules, wikiFs } = await loadModules();
|
|
197
|
+
wikiFs.writePage("pages/projects/chapterhouse/rules.md", "---\n"
|
|
198
|
+
+ "title: Project rules for chapterhouse\n"
|
|
199
|
+
+ "summary: Project-specific operating rules for Chapterhouse itself.\n"
|
|
200
|
+
+ "auto_pr: false\n"
|
|
201
|
+
+ "test_command: npm test\n"
|
|
202
|
+
+ "custom_rule: preserve-me\n"
|
|
203
|
+
+ "---\n\n"
|
|
204
|
+
+ "## Soft Rules\n\n"
|
|
205
|
+
+ "Intro paragraph.\n\n"
|
|
206
|
+
+ "- Keep tests green.\n"
|
|
207
|
+
+ " - Nested bullets do not count yet.\n"
|
|
208
|
+
+ "* Prefer worktrees for PR work.\n"
|
|
209
|
+
+ "+ Keep docs in sync.\n");
|
|
210
|
+
assert.deepEqual(projectRules.loadProjectRuleSummary("chapterhouse"), {
|
|
211
|
+
slug: "chapterhouse",
|
|
212
|
+
path: "pages/projects/chapterhouse/rules.md",
|
|
213
|
+
hardRuleCount: 2,
|
|
214
|
+
softRuleCount: 3,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=project-rules.test.js.map
|
package/package.json
CHANGED