@next-friday/create-app 6.0.2 → 7.1.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.
- package/package.json +3 -2
- package/templates/base/.agents/rules/constitution-changes.md +1 -1
- package/templates/base/.agents/skills/sdlc/SKILL.md +45 -0
- package/templates/base/.agents/skills/sdlc/playbooks/plan.md +33 -0
- package/templates/base/.agents/skills/sdlc/playbooks/question.md +8 -0
- package/templates/base/.agents/skills/sdlc-help/SKILL.md +22 -0
- package/templates/base/.claude/skills/sdlc/SKILL.md +45 -0
- package/templates/base/.claude/skills/sdlc/playbooks/plan.md +33 -0
- package/templates/base/.claude/skills/sdlc/playbooks/question.md +8 -0
- package/templates/base/.claude/skills/sdlc-help/SKILL.md +22 -0
- package/templates/base/.cursor/BUGBOT.md +0 -2
- package/templates/base/.cursor/skills/sdlc/SKILL.md +45 -0
- package/templates/base/.cursor/skills/sdlc/playbooks/plan.md +33 -0
- package/templates/base/.cursor/skills/sdlc/playbooks/question.md +8 -0
- package/templates/base/.cursor/skills/sdlc-help/SKILL.md +22 -0
- package/templates/base/SETUP.md +2 -2
- package/templates/base/intent/README.md +1 -1
- package/templates/base/scripts/check-human-gates.ts +383 -0
- package/templates/nestjs/.agents/rules.json +1 -1
- package/templates/nestjs/.claude/rules/constitution-changes.md +1 -1
- package/templates/nestjs/.cursor/rules/constitution-changes.mdc +2 -2
- package/templates/nestjs/.github/workflows/ci.yml +2 -0
- package/templates/nestjs/AGENTS.md +3 -1
- package/templates/nestjs/package.json +2 -1
- package/templates/nextjs/.agents/rules.json +1 -1
- package/templates/nextjs/.claude/rules/constitution-changes.md +1 -1
- package/templates/nextjs/.cursor/rules/constitution-changes.mdc +2 -2
- package/templates/nextjs/.github/workflows/ci.yml +2 -0
- package/templates/nextjs/AGENTS.md +3 -1
- package/templates/nextjs/package.json +2 -1
- package/templates/nodejs/.agents/rules.json +1 -1
- package/templates/nodejs/.claude/rules/constitution-changes.md +1 -1
- package/templates/nodejs/.cursor/rules/constitution-changes.mdc +2 -2
- package/templates/nodejs/.github/workflows/ci.yml +2 -0
- package/templates/nodejs/AGENTS.md +3 -1
- package/templates/nodejs/package.json +2 -1
- package/templates/payload/.agents/rules.json +1 -1
- package/templates/payload/.claude/rules/constitution-changes.md +1 -1
- package/templates/payload/.cursor/rules/constitution-changes.mdc +2 -2
- package/templates/payload/.github/workflows/ci.yml +2 -0
- package/templates/payload/AGENTS.md +3 -1
- package/templates/payload/package.json +2 -1
- package/templates/turborepo/.agents/rules.json +1 -1
- package/templates/turborepo/.claude/rules/constitution-changes.md +1 -1
- package/templates/turborepo/.cursor/rules/constitution-changes.mdc +2 -2
- package/templates/turborepo/.github/workflows/ci.yml +2 -0
- package/templates/turborepo/AGENTS.md +3 -1
- package/templates/turborepo/package.json +2 -1
- package/templates/base/.agents/skills/git-workflow/SKILL.md +0 -15
- package/templates/base/.agents/skills/script-driven-changes/SKILL.md +0 -12
- package/templates/base/.agents/skills/write-intent/SKILL.md +0 -31
- package/templates/base/.claude/skills/git-workflow/SKILL.md +0 -15
- package/templates/base/.claude/skills/script-driven-changes/SKILL.md +0 -12
- package/templates/base/.claude/skills/write-intent/SKILL.md +0 -31
- package/templates/base/.cursor/skills/git-workflow/SKILL.md +0 -15
- package/templates/base/.cursor/skills/script-driven-changes/SKILL.md +0 -12
- package/templates/base/.cursor/skills/write-intent/SKILL.md +0 -31
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import {existsSync, readdirSync, readFileSync, statSync} from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
type Problem = {
|
|
5
|
+
line: number;
|
|
6
|
+
message: string;
|
|
7
|
+
rule: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type Step = {
|
|
11
|
+
line: number;
|
|
12
|
+
text: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type SelfTestCase = {
|
|
16
|
+
expectedRule?: string;
|
|
17
|
+
name: string;
|
|
18
|
+
text: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const playbookDirectory = ".agents/skills/sdlc/playbooks";
|
|
22
|
+
const agentOwnedWorkRule = ".agents/rules/agent-owned-work.md";
|
|
23
|
+
const nextMarker = "**Next:**";
|
|
24
|
+
|
|
25
|
+
const humanGateRoles = [
|
|
26
|
+
"originator",
|
|
27
|
+
"product owner",
|
|
28
|
+
"engineer",
|
|
29
|
+
"code owner",
|
|
30
|
+
"release manager",
|
|
31
|
+
"service owner",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const roleWords = new RegExp(String.raw`\b(?:${humanGateRoles.join("|")})s?\b`, "i");
|
|
35
|
+
|
|
36
|
+
const waitingWordList = [
|
|
37
|
+
"wait",
|
|
38
|
+
"waits",
|
|
39
|
+
"approve",
|
|
40
|
+
"approves",
|
|
41
|
+
"approval",
|
|
42
|
+
"approved",
|
|
43
|
+
"approving",
|
|
44
|
+
"confirm",
|
|
45
|
+
"confirms",
|
|
46
|
+
"confirmation",
|
|
47
|
+
"permission",
|
|
48
|
+
"sign off",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const waitingWords = new RegExp(String.raw`\b(?:${waitingWordList.join("|")})\b`, "i");
|
|
52
|
+
const roleList = humanGateRoles.join(", ");
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Blank every line of a fenced code block, keeping line numbers.
|
|
56
|
+
* @param text Playbook content with LF line endings.
|
|
57
|
+
* @returns The content with fenced lines emptied.
|
|
58
|
+
*/
|
|
59
|
+
function maskFencedBlocks(text: string): string {
|
|
60
|
+
let isFenced = false;
|
|
61
|
+
|
|
62
|
+
return text
|
|
63
|
+
.split("\n")
|
|
64
|
+
.map(line => {
|
|
65
|
+
const isFence = /^\s*(?:```|~~~)/.test(line);
|
|
66
|
+
const isMasked = isFenced || isFence;
|
|
67
|
+
|
|
68
|
+
isFenced = isFence ? !isFenced : isFenced;
|
|
69
|
+
|
|
70
|
+
return isMasked ? "" : line;
|
|
71
|
+
})
|
|
72
|
+
.join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Split a playbook into its numbered steps, each with its indented continuation lines.
|
|
77
|
+
* @param text Playbook content.
|
|
78
|
+
* @returns The steps with the line each starts on.
|
|
79
|
+
*/
|
|
80
|
+
function splitSteps(text: string): Step[] {
|
|
81
|
+
return Array.from(text.matchAll(/^\d+\. .*(?:\n[\t ]+\S.*)*/gm), match => ({
|
|
82
|
+
line: text.slice(0, match.index).split("\n").length,
|
|
83
|
+
text: match[0],
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Find gate lines whose bold sentence names no human gate role.
|
|
89
|
+
* @param lines Playbook lines.
|
|
90
|
+
* @returns One problem per such line.
|
|
91
|
+
*/
|
|
92
|
+
function findGateProblems(lines: string[]): Problem[] {
|
|
93
|
+
return lines
|
|
94
|
+
.map((text, index) => ({
|
|
95
|
+
text,
|
|
96
|
+
line: index + 1,
|
|
97
|
+
}))
|
|
98
|
+
.filter(({text}) => text.includes("**Gate:"))
|
|
99
|
+
.filter(({text}) => !roleWords.test(/\*\*Gate:(.*?)\*\*/.exec(text)?.[1] ?? ""))
|
|
100
|
+
.map(({line}) => ({
|
|
101
|
+
line,
|
|
102
|
+
rule: "A",
|
|
103
|
+
message: `a gate must name one of ${roleList} inside its bold sentence; write "**Gate: the <role> <does what>.**"`,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Find steps that wait, approve, or confirm without naming a human gate role.
|
|
109
|
+
* @param text Playbook content.
|
|
110
|
+
* @returns One problem per such step.
|
|
111
|
+
*/
|
|
112
|
+
function findWaitingStepProblems(text: string): Problem[] {
|
|
113
|
+
return splitSteps(text)
|
|
114
|
+
.filter(step => waitingWords.test(step.text) && !roleWords.test(step.text))
|
|
115
|
+
.map(step => ({
|
|
116
|
+
rule: "B",
|
|
117
|
+
line: step.line,
|
|
118
|
+
message: `a step that waits, approves, or confirms must name who does it: one of ${roleList}`,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Tell whether a path is a relative path without `..` to an existing regular file.
|
|
124
|
+
* @param targetPath Path from a `**Next:**` target.
|
|
125
|
+
* @returns True when the path is usable.
|
|
126
|
+
*/
|
|
127
|
+
function isProjectFile(targetPath: string): boolean {
|
|
128
|
+
const segments = targetPath.split(/[\\/]/);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
!path.isAbsolute(targetPath) &&
|
|
132
|
+
!segments.includes("..") &&
|
|
133
|
+
statSync(targetPath, {
|
|
134
|
+
throwIfNoEntry: false,
|
|
135
|
+
})?.isFile() === true
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Describe what is wrong with the text after a `**Next:**` marker.
|
|
141
|
+
* @param text The text after the marker.
|
|
142
|
+
* @returns The problem, or an empty string when the line is valid.
|
|
143
|
+
*/
|
|
144
|
+
function describeNextProblem(text: string): string {
|
|
145
|
+
const parts = text.replace(/\.\s*$/, "").split(";");
|
|
146
|
+
|
|
147
|
+
if (parts.length !== 3 || parts.some(part => part.trim() === "")) {
|
|
148
|
+
return `"${nextMarker}" needs exactly three non-empty parts separated by ";" and no other ";": the target, who runs it, and what starts it`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const target = parts.at(0)?.trim() ?? "";
|
|
152
|
+
const targetPath = /^`([^`\s]+)`$/.exec(target)?.[1];
|
|
153
|
+
|
|
154
|
+
return target !== "none" && (targetPath === undefined || !isProjectFile(targetPath))
|
|
155
|
+
? `the "${nextMarker}" target must be "none" or one backticked relative path to an existing file in the project, with no ".." segment, found ${target}`
|
|
156
|
+
: "";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Find a missing, repeated, or malformed `**Next:**` line.
|
|
161
|
+
* @param lines Playbook lines.
|
|
162
|
+
* @returns The problems with the next-step line.
|
|
163
|
+
*/
|
|
164
|
+
function findNextProblems(lines: string[]): Problem[] {
|
|
165
|
+
const nextLines = lines
|
|
166
|
+
.map((text, index) => ({
|
|
167
|
+
text,
|
|
168
|
+
line: index + 1,
|
|
169
|
+
}))
|
|
170
|
+
.filter(({text}) => text.startsWith(nextMarker));
|
|
171
|
+
|
|
172
|
+
const next = nextLines.at(0);
|
|
173
|
+
|
|
174
|
+
if (next === undefined || nextLines.length !== 1) {
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
rule: "C",
|
|
178
|
+
line: next?.line ?? 1,
|
|
179
|
+
message: `a playbook needs exactly one line starting with "${nextMarker}", found ${nextLines.length}; write "${nextMarker} <target>; <who runs it>; <what starts it>."`,
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const message = describeNextProblem(next.text.slice(nextMarker.length));
|
|
185
|
+
|
|
186
|
+
return message === ""
|
|
187
|
+
? []
|
|
188
|
+
: [
|
|
189
|
+
{
|
|
190
|
+
message,
|
|
191
|
+
rule: "C",
|
|
192
|
+
line: next.line,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check one playbook against rules A, B, and C.
|
|
199
|
+
* @param text Playbook content.
|
|
200
|
+
* @returns Every problem found.
|
|
201
|
+
*/
|
|
202
|
+
function checkPlaybook(text: string): Problem[] {
|
|
203
|
+
const unfenced = maskFencedBlocks(text.replaceAll("\r\n", "\n"));
|
|
204
|
+
const lines = unfenced.split("\n");
|
|
205
|
+
|
|
206
|
+
return [
|
|
207
|
+
...findGateProblems(lines),
|
|
208
|
+
...findWaitingStepProblems(unfenced),
|
|
209
|
+
...findNextProblems(lines),
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* List the human gate roles a rule text does not mention.
|
|
215
|
+
* @param ruleText Content of the agent-owned work rule.
|
|
216
|
+
* @returns The missing roles.
|
|
217
|
+
*/
|
|
218
|
+
function findMissingRoles(ruleText: string): string[] {
|
|
219
|
+
return humanGateRoles.filter(role => !ruleText.toLowerCase().includes(role));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const validNext = "**Next:** none; the person; when they write.";
|
|
223
|
+
|
|
224
|
+
const selfTestCases: SelfTestCase[] = [
|
|
225
|
+
{
|
|
226
|
+
name: "gate names a role",
|
|
227
|
+
text: `**Gate: the originator corrects the draft.**\n\n${validNext}`,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
expectedRule: "A",
|
|
231
|
+
name: "gate names no role",
|
|
232
|
+
text: `**Gate: the manager corrects the draft.**\n\n${validNext}`,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
expectedRule: "A",
|
|
236
|
+
name: "role outside the bold gate sentence",
|
|
237
|
+
text: `**Gate: the draft is corrected.** The originator does it.\n\n${validNext}`,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "waiting step names a role",
|
|
241
|
+
text: `1. Wait for the engineer.\n\n${validNext}`,
|
|
242
|
+
},
|
|
243
|
+
...waitingWordList.map(word => ({
|
|
244
|
+
expectedRule: "B",
|
|
245
|
+
name: `waiting word "${word}" names no role`,
|
|
246
|
+
text: `1. Ask, then ${word} for the answer.\n\n${validNext}`,
|
|
247
|
+
})),
|
|
248
|
+
{
|
|
249
|
+
name: "plural role",
|
|
250
|
+
text: `1. Wait for the engineers and the code owners.\n\n${validNext}`,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
expectedRule: "B",
|
|
254
|
+
name: "longer word that starts with a role",
|
|
255
|
+
text: `1. Wait for the engineering team.\n\n${validNext}`,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "CRLF line endings",
|
|
259
|
+
text: `1. Ask, then wait.\r\n - The code owner answers.\r\n\r\n${validNext}\r\n`,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "gate inside a fenced block",
|
|
263
|
+
text: `\`\`\`\n**Gate: the manager corrects the draft.**\n\`\`\`\n\n${validNext}`,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "next inside a fenced block",
|
|
267
|
+
text: `\`\`\`\n**Next:** none; ; x\n\`\`\`\n\n${validNext}`,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
expectedRule: "B",
|
|
271
|
+
name: "waiting word on a continuation line",
|
|
272
|
+
text: `1. Ask.\n - Wait for the answer.\n\n${validNext}`,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "role on a continuation line covers the step",
|
|
276
|
+
text: `1. Ask, then wait.\n - The code owner answers.\n\n${validNext}`,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "target is an existing path",
|
|
280
|
+
text: "**Next:** `package.json`; an agent; when asked.",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
expectedRule: "C",
|
|
284
|
+
name: "target path does not exist",
|
|
285
|
+
text: "**Next:** `missing/file.md`; an agent; when asked.",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
expectedRule: "C",
|
|
289
|
+
name: "target is an absolute path",
|
|
290
|
+
text: "**Next:** `/etc/hostname`; an agent; when asked.",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
expectedRule: "C",
|
|
294
|
+
name: "target has a .. segment",
|
|
295
|
+
text: "**Next:** `.agents/../package.json`; an agent; when asked.",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
expectedRule: "C",
|
|
299
|
+
name: "target is a directory",
|
|
300
|
+
text: "**Next:** `.agents`; an agent; when asked.",
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
expectedRule: "C",
|
|
304
|
+
name: "target is not backticked",
|
|
305
|
+
text: "**Next:** package.json; an agent; when asked.",
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
expectedRule: "C",
|
|
309
|
+
name: "next has two parts",
|
|
310
|
+
text: "**Next:** none; the person.",
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
expectedRule: "C",
|
|
314
|
+
name: "next has an empty part",
|
|
315
|
+
text: "**Next:** none; ; when they write.",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
expectedRule: "C",
|
|
319
|
+
name: "next is missing",
|
|
320
|
+
text: "1. Answer.",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
expectedRule: "C",
|
|
324
|
+
name: "next appears twice",
|
|
325
|
+
text: `${validNext}\n${validNext}`,
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
const selfTestFailures = selfTestCases
|
|
330
|
+
.filter(({expectedRule, text}) => {
|
|
331
|
+
const rules = checkPlaybook(text).map(problem => problem.rule);
|
|
332
|
+
|
|
333
|
+
return expectedRule === undefined ? rules.length > 0 : rules.join(",") !== expectedRule;
|
|
334
|
+
})
|
|
335
|
+
.map(({name}) => name);
|
|
336
|
+
|
|
337
|
+
if (findMissingRoles(humanGateRoles.join(" ")).length > 0) {
|
|
338
|
+
selfTestFailures.push("a rule naming every role has no missing role");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (findMissingRoles("no role").length !== humanGateRoles.length) {
|
|
342
|
+
selfTestFailures.push("a rule naming no role is missing every role");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (existsSync(playbookDirectory) && existsSync(agentOwnedWorkRule)) {
|
|
346
|
+
if (selfTestFailures.length > 0) {
|
|
347
|
+
const failureLines = selfTestFailures.map(name => `- ${name}`);
|
|
348
|
+
|
|
349
|
+
process.stderr.write(`Human gate self-tests failed:\n${failureLines.join("\n")}\n`);
|
|
350
|
+
process.exitCode = 1;
|
|
351
|
+
} else {
|
|
352
|
+
const playbooks = readdirSync(playbookDirectory)
|
|
353
|
+
.filter(name => name.endsWith(".md"))
|
|
354
|
+
.map(name => `${playbookDirectory}/${name}`);
|
|
355
|
+
|
|
356
|
+
const problemLines = [
|
|
357
|
+
...playbooks.flatMap(playbook =>
|
|
358
|
+
checkPlaybook(readFileSync(playbook, "utf8")).map(
|
|
359
|
+
({line, message}) => `${playbook}:${line}: ${message}`,
|
|
360
|
+
),
|
|
361
|
+
),
|
|
362
|
+
...findMissingRoles(readFileSync(agentOwnedWorkRule, "utf8")).map(
|
|
363
|
+
role =>
|
|
364
|
+
`${agentOwnedWorkRule}:1: the human gate role "${role}" is missing; name it in the rule or remove it from scripts/check-human-gates.ts`,
|
|
365
|
+
),
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
if (problemLines.length > 0) {
|
|
369
|
+
process.stderr.write(`${problemLines.join("\n")}\n`);
|
|
370
|
+
process.exitCode = 1;
|
|
371
|
+
} else {
|
|
372
|
+
process.stdout.write(
|
|
373
|
+
`Checked ${playbooks.length} playbooks against ${humanGateRoles.length} human gate roles.\n`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
process.stderr.write(
|
|
379
|
+
`Run from the project root: ${playbookDirectory} and ${agentOwnedWorkRule} must exist.\n`,
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"description": "Codebase hygiene: modify existing code instead of accumulating new code"
|
|
13
13
|
},
|
|
14
14
|
"constitution-changes": {
|
|
15
|
-
"description": "Constitution changes: change rules, hooks, and agent settings
|
|
15
|
+
"description": "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves",
|
|
16
16
|
"globs": [
|
|
17
17
|
".agents/rules/**",
|
|
18
18
|
".agents/rules.json",
|
|
@@ -22,4 +22,4 @@ paths:
|
|
|
22
22
|
|
|
23
23
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Constitution changes: change rules, hooks, and agent settings
|
|
2
|
+
description: "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves"
|
|
3
3
|
globs: ".agents/rules/**,.agents/rules.json,.agents/hooks/**,.claude/settings.json,.claude/rules/**,.cursor/rules/**,.cursor/hooks/**,.cursor/hooks.json,scripts/sync-agent-rules.ts,scripts/check-agent-gates.ts,.husky/**,.commitlintrc.json,.prettierrc.json,eslint.config.ts,.lintstagedrc.json,.gitleaks.toml"
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -8,4 +8,4 @@ alwaysApply: false
|
|
|
8
8
|
|
|
9
9
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -36,7 +36,9 @@ If a test fails, fix the code, not the test.
|
|
|
36
36
|
4. **Log through Nest's `Logger`.** It goes to the `ConsoleLogger` set in `src/main.ts`.
|
|
37
37
|
5. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
|
|
38
38
|
6. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
|
|
39
|
-
7. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
|
|
39
|
+
7. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. When a message reports a problem without asking for work, answer in a few lines and end with the message that would start the work: `/sdlc` followed by their own words. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
|
|
40
|
+
8. **Branch from the latest `origin/main`, and open the pull request ready for review.** Name the branch `<type>/<short-description>` and fill the body from `.github/pull_request_template.md`. `.github/workflows/ci.yml` runs full verification only on a pull request that is not a draft. Drive it until it is green and waits only on code owner approval, as `babysit-pr` describes. Rebase and `git push --force-with-lease` only branches you own. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
41
|
+
9. **Write replies a person can check.** Reply in the language the person wrote in. Write paths from the project root, such as `README.md:12`, never from the machine's root. State as a fact only what you opened or ran in this turn, and say what you ran; mark anything else as inferred or not checked. Answer what was asked and stop: leave out tools, services, and steps the request does not need, and never hand the person a check you could run. Send nothing outside the session, such as a message to another session or a service, unless a task step says to.
|
|
40
42
|
|
|
41
43
|
## Things Claude gets wrong
|
|
42
44
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"format": "prettier --write --cache .",
|
|
23
23
|
"format:check": "prettier --check --cache . && editorconfig-checker",
|
|
24
24
|
"gates:check": "node scripts/check-agent-gates.ts",
|
|
25
|
+
"human-gates:check": "node scripts/check-human-gates.ts",
|
|
25
26
|
"knip": "knip",
|
|
26
27
|
"lint": "eslint . --fix --cache --max-warnings=0 --no-inline-config",
|
|
27
28
|
"lint:check": "eslint . --cache --max-warnings=0 --no-inline-config",
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"test:watch": "vitest",
|
|
44
45
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
45
46
|
"verify": "pnpm verify:static && pnpm test:coverage && pnpm build",
|
|
46
|
-
"verify:static": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm peers:check && pnpm typecheck && pnpm knip && pnpm depcruise"
|
|
47
|
+
"verify:static": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm human-gates:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm peers:check && pnpm typecheck && pnpm knip && pnpm depcruise"
|
|
47
48
|
},
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@nestjs/common": "12.3.2",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"description": "Codebase hygiene: modify existing code instead of accumulating new code"
|
|
13
13
|
},
|
|
14
14
|
"constitution-changes": {
|
|
15
|
-
"description": "Constitution changes: change rules, hooks, and agent settings
|
|
15
|
+
"description": "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves",
|
|
16
16
|
"globs": [
|
|
17
17
|
".agents/rules/**",
|
|
18
18
|
".agents/rules.json",
|
|
@@ -22,4 +22,4 @@ paths:
|
|
|
22
22
|
|
|
23
23
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Constitution changes: change rules, hooks, and agent settings
|
|
2
|
+
description: "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves"
|
|
3
3
|
globs: ".agents/rules/**,.agents/rules.json,.agents/hooks/**,.claude/settings.json,.claude/rules/**,.cursor/rules/**,.cursor/hooks/**,.cursor/hooks.json,scripts/sync-agent-rules.ts,scripts/check-agent-gates.ts,.husky/**,.commitlintrc.json,prettier.config.ts,eslint.config.ts,.lintstagedrc.json,.gitleaks.toml"
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -8,4 +8,4 @@ alwaysApply: false
|
|
|
8
8
|
|
|
9
9
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -36,7 +36,9 @@ If a test fails, fix the code, not the test.
|
|
|
36
36
|
4. **Read configuration from environment variables and fail when one is missing.** Document each variable in `.env.example`.
|
|
37
37
|
5. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
|
|
38
38
|
6. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
|
|
39
|
-
7. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
|
|
39
|
+
7. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. When a message reports a problem without asking for work, answer in a few lines and end with the message that would start the work: `/sdlc` followed by their own words. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
|
|
40
|
+
8. **Branch from the latest `origin/main`, and open the pull request ready for review.** Name the branch `<type>/<short-description>` and fill the body from `.github/pull_request_template.md`. `.github/workflows/ci.yml` runs full verification only on a pull request that is not a draft. Drive it until it is green and waits only on code owner approval, as `babysit-pr` describes. Rebase and `git push --force-with-lease` only branches you own. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
41
|
+
9. **Write replies a person can check.** Reply in the language the person wrote in. Write paths from the project root, such as `README.md:12`, never from the machine's root. State as a fact only what you opened or ran in this turn, and say what you ran; mark anything else as inferred or not checked. Answer what was asked and stop: leave out tools, services, and steps the request does not need, and never hand the person a check you could run. Send nothing outside the session, such as a message to another session or a service, unless a task step says to.
|
|
40
42
|
|
|
41
43
|
## Things Claude gets wrong
|
|
42
44
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"format": "prettier --write --cache .",
|
|
20
20
|
"format:check": "prettier --check --cache . && editorconfig-checker",
|
|
21
21
|
"gates:check": "node scripts/check-agent-gates.ts",
|
|
22
|
+
"human-gates:check": "node scripts/check-human-gates.ts",
|
|
22
23
|
"knip": "knip",
|
|
23
24
|
"lint": "eslint . --fix --cache --max-warnings=0 --no-inline-config",
|
|
24
25
|
"lint:check": "eslint . --cache --max-warnings=0 --no-inline-config",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"test:watch": "vitest",
|
|
38
39
|
"typecheck": "next typegen && tsc --noEmit -p tsconfig.json",
|
|
39
40
|
"verify": "pnpm verify:static && pnpm test:coverage && pnpm build",
|
|
40
|
-
"verify:static": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm peers:check && pnpm typecheck && pnpm knip && pnpm depcruise"
|
|
41
|
+
"verify:static": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm human-gates:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm peers:check && pnpm typecheck && pnpm knip && pnpm depcruise"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"next": "16.4.3",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"description": "Codebase hygiene: modify existing code instead of accumulating new code"
|
|
13
13
|
},
|
|
14
14
|
"constitution-changes": {
|
|
15
|
-
"description": "Constitution changes: change rules, hooks, and agent settings
|
|
15
|
+
"description": "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves",
|
|
16
16
|
"globs": [
|
|
17
17
|
".agents/rules/**",
|
|
18
18
|
".agents/rules.json",
|
|
@@ -22,4 +22,4 @@ paths:
|
|
|
22
22
|
|
|
23
23
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Constitution changes: change rules, hooks, and agent settings
|
|
2
|
+
description: "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves"
|
|
3
3
|
globs: ".agents/rules/**,.agents/rules.json,.agents/hooks/**,.claude/settings.json,.claude/rules/**,.cursor/rules/**,.cursor/hooks/**,.cursor/hooks.json,scripts/sync-agent-rules.ts,scripts/check-agent-gates.ts,.husky/**,.commitlintrc.json,.prettierrc.json,eslint.config.ts,.lintstagedrc.json,.gitleaks.toml"
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -8,4 +8,4 @@ alwaysApply: false
|
|
|
8
8
|
|
|
9
9
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -34,7 +34,9 @@ If a test fails, fix the code, not the test.
|
|
|
34
34
|
3. **Read configuration from environment variables and fail when one is missing**, as `parsePort` does. Document each variable in `.env.example`; `pnpm start` and `pnpm start:dev` load `.env`.
|
|
35
35
|
4. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
|
|
36
36
|
5. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
|
|
37
|
-
6. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
|
|
37
|
+
6. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. When a message reports a problem without asking for work, answer in a few lines and end with the message that would start the work: `/sdlc` followed by their own words. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
|
|
38
|
+
7. **Branch from the latest `origin/main`, and open the pull request ready for review.** Name the branch `<type>/<short-description>` and fill the body from `.github/pull_request_template.md`. `.github/workflows/ci.yml` runs full verification only on a pull request that is not a draft. Drive it until it is green and waits only on code owner approval, as `babysit-pr` describes. Rebase and `git push --force-with-lease` only branches you own. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
39
|
+
8. **Write replies a person can check.** Reply in the language the person wrote in. Write paths from the project root, such as `README.md:12`, never from the machine's root. State as a fact only what you opened or ran in this turn, and say what you ran; mark anything else as inferred or not checked. Answer what was asked and stop: leave out tools, services, and steps the request does not need, and never hand the person a check you could run. Send nothing outside the session, such as a message to another session or a service, unless a task step says to.
|
|
38
40
|
|
|
39
41
|
## Things Claude gets wrong
|
|
40
42
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"format": "prettier --write --cache .",
|
|
19
19
|
"format:check": "prettier --check --cache . && editorconfig-checker",
|
|
20
20
|
"gates:check": "node scripts/check-agent-gates.ts",
|
|
21
|
+
"human-gates:check": "node scripts/check-human-gates.ts",
|
|
21
22
|
"knip": "knip",
|
|
22
23
|
"lint": "eslint . --fix --cache --max-warnings=0 --no-inline-config",
|
|
23
24
|
"lint:check": "eslint . --cache --max-warnings=0 --no-inline-config",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"test:watch": "vitest",
|
|
38
39
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
39
40
|
"verify": "pnpm verify:static && pnpm test:coverage && pnpm build",
|
|
40
|
-
"verify:static": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm peers:check && pnpm typecheck && pnpm knip && pnpm depcruise"
|
|
41
|
+
"verify:static": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm human-gates:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm peers:check && pnpm typecheck && pnpm knip && pnpm depcruise"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@commitlint/cli": "21.2.4",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"description": "Codebase hygiene: modify existing code instead of accumulating new code"
|
|
13
13
|
},
|
|
14
14
|
"constitution-changes": {
|
|
15
|
-
"description": "Constitution changes: change rules, hooks, and agent settings
|
|
15
|
+
"description": "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves",
|
|
16
16
|
"globs": [
|
|
17
17
|
".agents/rules/**",
|
|
18
18
|
".agents/rules.json",
|
|
@@ -22,4 +22,4 @@ paths:
|
|
|
22
22
|
|
|
23
23
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Constitution changes: change rules, hooks, and agent settings
|
|
2
|
+
description: "Constitution changes: change rules, hooks, and agent settings in a pull request a code owner approves"
|
|
3
3
|
globs: ".agents/rules/**,.agents/rules.json,.agents/hooks/**,.claude/settings.json,.claude/rules/**,.cursor/rules/**,.cursor/hooks/**,.cursor/hooks.json,scripts/sync-agent-rules.ts,scripts/check-agent-gates.ts,.husky/**,.commitlintrc.json,.prettierrc.json,eslint.config.ts,.lintstagedrc.json,.gitleaks.toml"
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -8,4 +8,4 @@ alwaysApply: false
|
|
|
8
8
|
|
|
9
9
|
The constitution is the set of files that agent settings deny to agents: rules, hooks, lint, format, and commit configuration, and the agent settings themselves. It is company policy.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The constitution changes in a pull request like any other change, and a code owner approves it in review. Agent settings stop an agent from editing these files. When a task needs a constitution change, write the exact new text and the files it goes in on the pull request; a person commits it to that branch.
|