@next-friday/create-app 7.0.0 → 8.0.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/brand-policy/SKILL.md +9 -3
- package/templates/base/.agents/skills/compliance-policy/SKILL.md +16 -3
- package/templates/base/.agents/skills/sdlc/SKILL.md +6 -5
- package/templates/base/.agents/skills/sdlc/playbooks/design.md +37 -0
- package/templates/base/.agents/skills/sdlc/playbooks/plan.md +5 -3
- package/templates/base/.agents/skills/sdlc/playbooks/question.md +2 -0
- package/templates/base/.agents/skills/security-policy/SKILL.md +32 -3
- package/templates/base/.agents/skills/ux-policy/SKILL.md +12 -3
- package/templates/base/.claude/skills/brand-policy/SKILL.md +9 -3
- package/templates/base/.claude/skills/compliance-policy/SKILL.md +16 -3
- package/templates/base/.claude/skills/sdlc/SKILL.md +6 -5
- package/templates/base/.claude/skills/sdlc/playbooks/design.md +37 -0
- package/templates/base/.claude/skills/sdlc/playbooks/plan.md +5 -3
- package/templates/base/.claude/skills/sdlc/playbooks/question.md +2 -0
- package/templates/base/.claude/skills/security-policy/SKILL.md +32 -3
- package/templates/base/.claude/skills/ux-policy/SKILL.md +12 -3
- package/templates/base/.cursor/skills/brand-policy/SKILL.md +9 -3
- package/templates/base/.cursor/skills/compliance-policy/SKILL.md +16 -3
- package/templates/base/.cursor/skills/sdlc/SKILL.md +6 -5
- package/templates/base/.cursor/skills/sdlc/playbooks/design.md +37 -0
- package/templates/base/.cursor/skills/sdlc/playbooks/plan.md +5 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/question.md +2 -0
- package/templates/base/.cursor/skills/security-policy/SKILL.md +32 -3
- package/templates/base/.cursor/skills/ux-policy/SKILL.md +12 -3
- package/templates/base/SETUP.md +4 -4
- 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/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/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/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/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/package.json +2 -1
- package/templates/base/.agents/skills/write-spec/SKILL.md +0 -12
- package/templates/base/.claude/skills/write-spec/SKILL.md +0 -12
- package/templates/base/.cursor/skills/write-spec/SKILL.md +0 -12
|
@@ -5,8 +5,37 @@ description: Apply the organization's security policy. Use whenever writing or r
|
|
|
5
5
|
|
|
6
6
|
# Security policy
|
|
7
7
|
|
|
8
|
-
Owner:
|
|
8
|
+
Owner: the code owner of this folder in `.github/CODEOWNERS`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Standard: [OWASP Application Security Verification Standard 5.0.0](https://github.com/OWASP/ASVS/tree/v5.0.0/5.0/en), Level 1. This is the default; the owner replaces or tightens it by editing this file.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Rules
|
|
13
|
+
|
|
14
|
+
1. Find the chapters the change touches in the table below. A change that touches none meets this policy; say "Does not apply" and why.
|
|
15
|
+
2. For each chapter it touches, open that chapter's file in the standard and list every Level 1 requirement that applies to the change. Cite each as `v5.0.0-<chapter>.<section>.<requirement>`, for example `v5.0.0-8.2.1`.
|
|
16
|
+
3. Each cited requirement becomes a requirement of the spec, and a test or a review step of the plan.
|
|
17
|
+
4. When you cannot open the standard, name the chapters, cite no requirement number from memory, and report that as an area of concern.
|
|
18
|
+
5. A Level 1 requirement the change cannot meet is an area of concern. Never drop it silently.
|
|
19
|
+
6. Secrets stay out of the repository, logs, and error messages; `.env.example` holds names only.
|
|
20
|
+
|
|
21
|
+
## Chapters
|
|
22
|
+
|
|
23
|
+
| Chapter | Covers | Level 1 requirements |
|
|
24
|
+
| :------ | :---------------------------------- | :------------------- |
|
|
25
|
+
| V1 | Encoding and Sanitization | 8 |
|
|
26
|
+
| V2 | Validation and Business Logic | 4 |
|
|
27
|
+
| V3 | Web Frontend Security | 8 |
|
|
28
|
+
| V4 | API and Web Service | 2 |
|
|
29
|
+
| V5 | File Handling | 4 |
|
|
30
|
+
| V6 | Authentication | 13 |
|
|
31
|
+
| V7 | Session Management | 6 |
|
|
32
|
+
| V8 | Authorization | 4 |
|
|
33
|
+
| V9 | Self-contained Tokens | 4 |
|
|
34
|
+
| V10 | OAuth and OIDC | 5 |
|
|
35
|
+
| V11 | Cryptography | 3 |
|
|
36
|
+
| V12 | Secure Communication | 3 |
|
|
37
|
+
| V13 | Configuration | 1 |
|
|
38
|
+
| V14 | Data Protection | 2 |
|
|
39
|
+
| V15 | Secure Coding and Architecture | 3 |
|
|
40
|
+
| V16 | Security Logging and Error Handling | 0 |
|
|
41
|
+
| V17 | WebRTC | 0 |
|
|
@@ -5,8 +5,17 @@ description: Apply the organization's UX policy. Use whenever writing or reviewi
|
|
|
5
5
|
|
|
6
6
|
# UX policy
|
|
7
7
|
|
|
8
|
-
Owner:
|
|
8
|
+
Owner: the code owner of this folder in `.github/CODEOWNERS`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Standard: [Web Content Accessibility Guidelines (WCAG) 2.2](https://www.w3.org/TR/WCAG22/), Level AA: the page satisfies all the Level A and Level AA success criteria. This is the default; the owner replaces or tightens it by editing this file.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Rules
|
|
13
|
+
|
|
14
|
+
1. A change with no user interface meets this policy; say "Does not apply" and why.
|
|
15
|
+
2. For a change with a user interface, the spec lists the success criteria the change touches, by number and name, and each becomes a requirement.
|
|
16
|
+
3. Every interactive element works with the keyboard alone, shows where the focus is, and has a name a screen reader announces.
|
|
17
|
+
4. Text and meaningful graphics meet the contrast minimum, and no information is carried by colour alone.
|
|
18
|
+
5. Every form field has a visible label, and every error says in text which field is wrong and how to fix it.
|
|
19
|
+
6. Check the criteria that WCAG 2.2 added at Level A and AA: 2.4.11 Focus Not Obscured (Minimum), 2.5.7 Dragging Movements, 2.5.8 Target Size (Minimum), 3.2.6 Consistent Help, 3.3.7 Redundant Entry, and 3.3.8 Accessible Authentication (Minimum).
|
|
20
|
+
7. Every state the user can reach is designed: loading, empty, error, and success.
|
|
21
|
+
8. A criterion the change cannot meet is an area of concern. Never drop it silently.
|
package/templates/base/SETUP.md
CHANGED
|
@@ -4,8 +4,8 @@ This project follows Anthropic's [AI-native SDLC Playbook](https://claude.com/re
|
|
|
4
4
|
|
|
5
5
|
## How a change flows
|
|
6
6
|
|
|
7
|
-
1. **Plan:** the originator of an idea, a ticket, or an incident sends it with `/sdlc`, which interviews them and writes `intent/<change>/intent.md`. The product owner
|
|
8
|
-
2. **Design:** `spec.md` is written from the accepted intent with the policy skills loaded (`
|
|
7
|
+
1. **Plan:** the originator of an idea, a ticket, or an incident sends it with `/sdlc`, which interviews them and writes `intent/<change>/intent.md`. The product owner accepts it by merging it with `Status: accepted.`, or rejects it by closing its pull request.
|
|
8
|
+
2. **Design:** `spec.md` is written from the accepted intent with the policy skills loaded (send the intent's path with `/sdlc`). The product owner resolves concerns with the policy owners.
|
|
9
9
|
3. **Build:** the engineer starts in plan mode, approves the plan, and commits it as `plan.md`. A tech lead approves higher-risk plans.
|
|
10
10
|
4. **Test:** the session runs **Verifying your work** in `AGENTS.md`.
|
|
11
11
|
5. **Review:** Claude Code Review and Bugbot review by `REVIEW.md`. A code owner approves; the agent never does.
|
|
@@ -93,13 +93,13 @@ Also replace the owner placeholder in `.github/zizmor.yml`.
|
|
|
93
93
|
|
|
94
94
|
- **Sources of truth (engineering lead):** for each artifact, name one system as the source of truth: this repository, an existing tool such as Jira, or, as the minimum, a link both ways (the record id in the file and the commit SHA in the record).
|
|
95
95
|
- **Higher risk (engineering lead):** decide which changes count as higher risk. A tech lead or architect approves their specs and plans.
|
|
96
|
-
- **Policies (each policy owner, with an engineer):**
|
|
96
|
+
- **Policies (each policy owner, with an engineer):** each `.agents/skills/<area>-policy/SKILL.md` ships a default (security: OWASP ASVS 5.0.0 Level 1; UX: WCAG 2.2 Level AA; compliance: the OECD privacy principles; brand: one declared source for every brand value). Review it, replace or add the organization's own rules, then run `pnpm rules:sync`. Test that the skill triggers: ask for the relevant task in different ways and confirm the skill loads each time. The policy owner approves every later change as its code owner.
|
|
97
97
|
- **Production gate (engineering leadership, with change management and compliance):** name the release managers and add them as required reviewers on the `prod` environment, as the README's deploy section describes. `.agents/hooks/agent-production-gate.sh` blocks an agent's shell commands that dispatch a `prod` deploy, run a Railway command with `prod` as its environment, or push a release tag. It does not read MCP tools. A release manager whose own agent session must do one of these starts the agent with `RELEASE_APPROVAL` set to the approval record, for example `RELEASE_APPROVAL=CHG-123 claude`.
|
|
98
98
|
- **Review (tech lead):** replace `src/gen/` in `.agents/review.md` with this project's generated paths, then run `pnpm rules:sync`, which renders it to `REVIEW.md` for Claude Code Review. Once a month, rate the review findings and tune `.agents/review.md`.
|
|
99
99
|
- **People without Git experience:** connect GitHub to Claude (claude.ai or Cowork), so Claude commits `intent.md` for them.
|
|
100
100
|
|
|
101
101
|
## 7. Later, as the guardrails mature
|
|
102
102
|
|
|
103
|
-
-
|
|
103
|
+
- Send the intent's path with `/sdlc` by hand first, then as an organization-level slash command, then as a job that commits `spec.md` as a pull request when an `intent.md` merges.
|
|
104
104
|
- Make auto mode the default for routine work: a tight `spec.md`, a small blast radius, and code the tests already cover.
|
|
105
105
|
- Replace `bands.yaml` with this project's metric, add a unit-tested detection script that reads it, and add the trigger that runs it.
|
|
@@ -4,7 +4,7 @@ This folder is the intent home. Every change starts here as an `intent.md`, and
|
|
|
4
4
|
|
|
5
5
|
- Each change has its own folder, `intent/<change>/`, where `<change>` is a short kebab-case name. The folder holds `intent.md` (Plan), then `spec.md` (Design), then `plan.md` (Build).
|
|
6
6
|
- **Plan.** An idea, a ticket, or an incident becomes `intent.md`, written by sending the idea with `/sdlc`, which interviews the originator. The originator corrects the draft before it is committed, and the product owner reviews it in its pull request. Merging its pull request records the decision: `Status: accepted.` accepts it, and `Status: rejected.` rejects it.
|
|
7
|
-
- **Design.**
|
|
7
|
+
- **Design.** Sending the intent's path with `/sdlc` turns the accepted `intent.md` into `spec.md`, committed alongside it. The product owner reviews the spec against the idea and resolves each area of concern with its policy owner. Accepting the spec starts the build.
|
|
8
8
|
- **Build.** The engineer starts in plan mode with `intent.md` and `spec.md`, and commits the approved plan as `plan.md`. When implementation departs from the plan, `plan.md` changes in the same commit.
|
|
9
9
|
|
|
10
10
|
## Which file holds what
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -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.
|