@next-friday/create-app 9.3.1 → 9.4.1
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 +1 -1
- package/templates/base/plugins/sdlc/claude-code/skills/sdlc-mode/SKILL.md +1 -1
- package/templates/base/plugins/sdlc/claude-code/skills/sdlc-mode/playbooks/build.md +9 -8
- package/templates/base/plugins/sdlc/claude-code/skills/sdlc-mode/playbooks/design.md +3 -3
- package/templates/base/plugins/sdlc/claude-code/skills/sdlc-mode/playbooks/fix.md +9 -8
- package/templates/base/plugins/sdlc/claude-code/skills/sdlc-mode/playbooks/plan.md +3 -3
- package/templates/base/plugins/sdlc/cursor/skills/sdlc-mode/SKILL.md +1 -1
- package/templates/base/plugins/sdlc/cursor/skills/sdlc-mode/playbooks/build.md +9 -8
- package/templates/base/plugins/sdlc/cursor/skills/sdlc-mode/playbooks/design.md +3 -3
- package/templates/base/plugins/sdlc/cursor/skills/sdlc-mode/playbooks/fix.md +9 -8
- package/templates/base/plugins/sdlc/cursor/skills/sdlc-mode/playbooks/plan.md +3 -3
- package/templates/base/scripts/check-plan.ts +394 -70
- package/templates/nestjs/.github/workflows/ci.yml +5 -0
- package/templates/nestjs/AGENTS.md +1 -1
- package/templates/nextjs/.github/workflows/ci.yml +5 -0
- package/templates/nextjs/AGENTS.md +1 -1
- package/templates/nodejs/.github/workflows/ci.yml +5 -0
- package/templates/nodejs/AGENTS.md +1 -1
- package/templates/payload/.github/workflows/ci.yml +5 -0
- package/templates/payload/AGENTS.md +1 -1
- package/templates/turborepo/.github/workflows/ci.yml +3 -0
- package/templates/turborepo/AGENTS.md +1 -1
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ Do these steps in order.
|
|
|
14
14
|
| :---------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
|
|
15
15
|
| A question, or a request to explain something without changing anything | Follow [`playbooks/question.md`](playbooks/question.md). |
|
|
16
16
|
| A spec for an intent, or a path to an `intent.md` | Follow [`playbooks/design.md`](playbooks/design.md). |
|
|
17
|
-
| A build for a spec, a path to a `spec.md`, or a plan the engineer
|
|
17
|
+
| A build for a spec, a path to a `spec.md`, or a plan the engineer merged | Follow [`playbooks/build.md`](playbooks/build.md). |
|
|
18
18
|
| A review or check of a pull request or a branch, a failing check on a pull request, or a pull request to drive to green | Follow [`playbooks/review.md`](playbooks/review.md). |
|
|
19
19
|
| A deploy, a promotion to an environment, or a release to production | Follow [`playbooks/deploy.md`](playbooks/deploy.md). |
|
|
20
20
|
| A finding sent by a job, a monitor, or a trigger, or one the message says no person can answer questions about | Follow [`playbooks/maintain.md`](playbooks/maintain.md). |
|
|
@@ -5,11 +5,11 @@ This playbook turns a merged spec into a plan the engineer accepts, and then int
|
|
|
5
5
|
1. Find the change: the `intent/<change>/` folder whose `spec.md` or `plan.md` the request names. Its `spec.md` must be on the default branch beside its `intent.md`. In each case below, say only that, in two or three lines, and stop: write no file.
|
|
6
6
|
- The request names no change, or the folder has no `spec.md` on any branch: list each `intent/*/` on the default branch that has a `spec.md` and no `plan.md`, or say that there is none.
|
|
7
7
|
- The spec exists only on another branch: say that the spec is not merged yet, and name that branch.
|
|
8
|
-
- `intent/<change>/plan.md` is
|
|
8
|
+
- `intent/<change>/plan.md` is on the default branch and `spec.md` says `Status: done.`: say that the change is built, and name both files.
|
|
9
9
|
|
|
10
10
|
When the request is a bug fix with no spec, none of these cases applies: follow `playbooks/fix.md` instead.
|
|
11
11
|
|
|
12
|
-
2. When `intent/<change>/plan.md`
|
|
12
|
+
2. When `intent/<change>/plan.md` is on the default branch, the engineer accepted it by merging its pull request: go to step 7. When it exists only on a branch that is not merged, check out that branch, show that plan, and go to step 5.
|
|
13
13
|
3. Read `intent.md`, `spec.md`, the four policies, found as `playbooks/design.md` step 2 says, and the code the change touches. Start from the accepted spec, in the agent tool's plan mode when it has one, and give the plan the change's `intent.md` and `spec.md`. Then interview the engineer until they have said four things: the files the change touches, the order of the work, the tests that prove it, and each option they rule out.
|
|
14
14
|
- **When all four are already said, in the request or in the files, go to step 4 in the same turn.**
|
|
15
15
|
- **When one is missing, ask and stop.** Write no plan in that turn. Ask in rounds, as step 3 of `playbooks/plan.md` does: numbered questions, each with a recommended answer or "No recommendation". Wait for the answers, and build the next round on them.
|
|
@@ -21,13 +21,14 @@ This playbook turns a merged spec into a plan the engineer accepts, and then int
|
|
|
21
21
|
|
|
22
22
|
Show the whole plan and name the file only as `intent/<change>/plan.md`. In plan mode you cannot write the file. Show the plan as the plan.
|
|
23
23
|
|
|
24
|
-
5.
|
|
25
|
-
6.
|
|
26
|
-
7.
|
|
27
|
-
8.
|
|
28
|
-
9.
|
|
24
|
+
5. Commit `intent/<change>/plan.md` on a new branch, unless step 2 checked one out, with the `Accepted by:` line as the template has it. In that commit, set the spec's status line to `Status: accepted.`, because its pull request is merged; when an area of concern of that spec has no `Decision:` line, add it there with the decision you propose, as `playbooks/design.md` step 3 says. Push the branch and open a pull request that changes no file outside `intent/`, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests. When the branch already has a pull request, update that one. In plan mode, the engineer's approval lets you write the file and do this step; it is not the acceptance.
|
|
25
|
+
6. **Gate: the engineer accepts the plan.** The engineer merges that pull request to accept the plan, or closes it to reject it. You never merge it, and you change no file outside `intent/` before it is merged. Revise the plan as the engineer asks; each revision is a commit on that branch. When the spec classes the change as higher risk, say that a tech lead or an architect approves that pull request before it is merged. Report the pull request's URL, or the pushed branch's name when you could not open one, keep it green as `playbooks/review.md` says, and wait.
|
|
26
|
+
7. When the plan's pull request is merged, create a new branch as `playbooks/open-pr.md` says. In its first commit, set the plan's `Accepted by:` line, under its title, to the account that merged that pull request, read from the host. When that pull request has an approving review from a second person, add `Also accepted by: @<handle>.` on the next line. When you cannot read the host, confirm with `git fetch` that the plan is on the default branch, and write the handle the engineer gives; ask for it when they gave none. Never write a handle the host or the engineer did not give.
|
|
27
|
+
8. Implement the plan in its order. After each step, run the check that proves it and fix what fails. For a bug fix, follow `playbooks/fix.md` steps 8 and 9: commit the failing test on a `fix/` branch, and the gate then blocks test edits. When the work departs from the plan, change `plan.md` in the same commit. When the change meets the last requirement of `spec.md`, set `Status: done.` in `intent.md` and `spec.md`.
|
|
28
|
+
9. Run every check the project's agent instructions name and keep the output. Every check must pass: fix the code, and never skip, delete, or weaken a test. Then run the `verifier` subagent and fix what it reports. Start it on the model that `.agents/sdlc-models.md` names after `verify:`, at the effort on that line when the session can set one; when you cannot start a subagent, do what `../../../agents/verifier.md`, relative to this playbook, says yourself. For UI work, when the session has a browser tool, also close the loop with a visual check: start the app, give the browser tool the page and the mock the spec or the request names, screenshot, compare, and adjust, and repeat until it matches; two or three rounds is normal. When a check cannot run in this session, say which one and why, and never report it as passed.
|
|
29
|
+
10. Go on in the same turn, without asking: follow `playbooks/open-pr.md`, and then `playbooks/review.md`. Pushing the branch and opening the pull request are part of this task.
|
|
29
30
|
|
|
30
|
-
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step
|
|
31
|
+
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step 9 passes.
|
|
31
32
|
|
|
32
33
|
## Template
|
|
33
34
|
|
|
@@ -8,14 +8,14 @@ This playbook turns a merged intent into the requirements and design spec.
|
|
|
8
8
|
- `spec.md` already exists: say so and name the file.
|
|
9
9
|
2. Read the four policies, every time, before you write: for each of `brand`, `security`, `compliance`, and `ux`, read the project's `.agents/skills/<area>-policy/SKILL.md` when it exists, and the plugin's default at `../../<area>-policy/SKILL.md`, relative to this playbook, otherwise. Then read the code and the docs the change touches.
|
|
10
10
|
3. Write `intent/<change>/spec.md` from the template below. Use exactly the template's headings.
|
|
11
|
-
- **Request:** under the status line, quote the words of the request that started this spec.
|
|
11
|
+
- **Request:** under the status line, quote the words of the request that started this spec. End the line with `Skills at <hash>.`, the short hash of the last commit that changed the plugin's files in this repository: `git log -1 --format=%h -- plugins/sdlc`. When the plugin is not in this repository, write `Skills at unknown.`
|
|
12
12
|
- **Requirements:** numbered. Each one states a result a test or a reader can check.
|
|
13
13
|
- **Design:** how the change fits the existing code: the files that change, and what each one does afterwards.
|
|
14
14
|
- **Policies:** one line for each of the four policies. State which rules of that policy the change must meet, each as a requirement number, or write "Does not apply" and why.
|
|
15
|
-
- **Areas of concern:** a policy rule the change cannot meet, two policies that contradict, or a decision the files do not settle. Give each one a number and the policy it comes from. Write "None" only when there are none.
|
|
15
|
+
- **Areas of concern:** a policy rule the change cannot meet, two policies that contradict, or a decision the files do not settle. Give each one a number and the policy it comes from. Under each, on a line of its own, write `Decision: <the decision you propose>.` Write "None" only when there are none.
|
|
16
16
|
- **Open questions:** each open question from `intent.md`, answered with its evidence or carried forward. Write "None" only when the intent has none.
|
|
17
17
|
4. Commit `spec.md` on a new branch. In the same commit, set the intent's status line to `Status: accepted.`, because its pull request is merged. Push the branch, and open a pull request ready for review, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests. When you cannot open a pull request from this session, say so and give the pushed branch's name.
|
|
18
|
-
5. **Gate: the product owner reviews the spec.** The product owner
|
|
18
|
+
5. **Gate: the product owner reviews the spec.** The product owner corrects the `Decision:` line under each area of concern, with its policy owner, then merges the pull request to accept the spec and those decisions as written, or closes it to reject it. Change a `Decision:` line when they tell you its text. For a change the spec classes as higher risk, the product owner consults a tech lead before accepting. You do none of these. The spec stays `Status: draft.`: never write `Status: accepted.` in this pull request, because the plan's commit sets it. Report the pull request's URL, keep it green as `playbooks/review.md` says, and wait.
|
|
19
19
|
|
|
20
20
|
**Next:** `playbooks/build.md`; an engineer with an agent; when the product owner has merged the spec's pull request and the engineer sends `/sdlc:sdlc-mode build` with the spec's path.
|
|
21
21
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This playbook takes a defect in behaviour that already exists to a pull request. It has no intent and no spec: its plan is the only file it adds under `intent/`.
|
|
4
4
|
|
|
5
|
-
1. Decide that the request qualifies. It qualifies only when the request is a defect in behaviour that already exists, and the fix adds no new behaviour and no new dependency and changes no schema and no public interface. When a condition fails, say which one in two lines and follow `playbooks/plan.md` instead.
|
|
5
|
+
1. Decide that the request qualifies. It qualifies only when the request is a defect in behaviour that already exists, and the fix adds no new behaviour and no new dependency and changes no schema and no public interface. When a condition fails, say which one in two lines and follow `playbooks/plan.md` instead. When `intent/<short-description>/plan.md` for this defect is already on the default branch, the engineer accepted it by merging its pull request: go to step 7.
|
|
6
6
|
2. Reproduce the defect yourself by running the code, and keep the output. Ask the person only when you can name the specific reason you cannot reach it, such as a credential or a service this session cannot use.
|
|
7
7
|
3. Find the cause from what running the code shows. List the candidate causes, rule each out with a command or an observation, and never guess. Name the one cause the evidence leaves. When it leaves none, or more than one, say so and ask for what is missing.
|
|
8
8
|
4. Write the plan from the template in `playbooks/build.md`. The title is `# Plan: <title> (fix)`. Use exactly the template's four headings, and keep the plan short. Name the file `intent/<short-description>/plan.md`; the folder holds only this file.
|
|
@@ -13,11 +13,12 @@ This playbook takes a defect in behaviour that already exists to a pull request.
|
|
|
13
13
|
|
|
14
14
|
Show the whole plan and name the file only as `intent/<short-description>/plan.md`. In plan mode you cannot write the file. Show the plan as the plan.
|
|
15
15
|
|
|
16
|
-
5.
|
|
17
|
-
6.
|
|
18
|
-
7.
|
|
19
|
-
8.
|
|
20
|
-
9.
|
|
21
|
-
10.
|
|
16
|
+
5. Commit `intent/<short-description>/plan.md` on a new branch, with the `Accepted by:` line as the template has it, push it, and open a pull request that changes no file outside `intent/`, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests. In plan mode, the engineer's approval lets you write the file and do this step; it is not the acceptance.
|
|
17
|
+
6. **Gate: the engineer accepts the plan.** The engineer merges that pull request to accept the plan, or closes it to reject it. You never merge it, and you change no file outside `intent/` before it is merged. Revise the plan as the engineer asks; each revision is a commit on that branch. Report the pull request's URL, or the pushed branch's name when you could not open one, keep it green as `playbooks/review.md` says, and wait.
|
|
18
|
+
7. When the plan's pull request is merged, create the branch `fix/<short-description>` as `playbooks/open-pr.md` says. In its first commit, set the plan's `Accepted by:` line, under its title, to the account that merged that pull request, read from the host. When you cannot read the host, confirm with `git fetch` that the plan is on the default branch, and write the handle the engineer gives; ask for it when they gave none. Never write a handle the host or the engineer did not give.
|
|
19
|
+
8. Reproduce the defect as a test. Run it and check that it fails for the reason the cause gives. Commit the failing test before any fix.
|
|
20
|
+
9. Make the test pass without editing it.
|
|
21
|
+
10. Run every check the project's agent instructions name. Keep the output. When one fails, fix the code, and never skip, delete, or weaken a test. When a check cannot run in this session, say which one and why, and never report it as passed.
|
|
22
|
+
11. Go on in the same turn, without asking. Follow `playbooks/open-pr.md`, and then `playbooks/review.md`.
|
|
22
23
|
|
|
23
|
-
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step
|
|
24
|
+
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step 10 passes.
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
This playbook writes the intent. The build plan, `intent/<change>/plan.md`, comes later and is not written here.
|
|
4
4
|
|
|
5
5
|
1. Let the originator describe the problem in their own words: what they cannot do today, who is affected, what better looks like, and what is out of scope.
|
|
6
|
-
2. Read the code, the docs, and the existing `intent/` folders. Find the rest yourself: the
|
|
6
|
+
2. Read the code, the docs, and the existing `intent/` folders. Find the rest yourself. The author is the originator's handle on the host: read it from the host when the session shows whose account it runs as, and ask for it otherwise. Never take it from `git config user.name`, which names the machine's user.
|
|
7
7
|
3. Interview until the originator has said four things: the problem, the outcome, the constraints, and what is out of scope. The outcome is what the user would see.
|
|
8
8
|
- **When all four are already said, go to step 4 in the same turn.**
|
|
9
9
|
- **When one is missing, ask and stop.** Write no draft in that turn. Ask in rounds. Each round holds only questions that do not depend on another question in the same round. Number the questions. Each asks one thing and carries a concrete answer you recommend, worded so "yes" accepts it. A recommended answer is a fact or a choice, such as "Recommended: only the checkout page", never an instruction to the person. When you have no answer to recommend, write "No recommendation" and ask plainly. Wait for the answers, and build the next round on them.
|
|
10
10
|
4. Write `intent/<change>/intent.md` from the template below, where `<change>` is a short kebab-case name. Use exactly the template's five headings. What is out of scope goes under **Proposed outcome**. Anything else that is unknown, unverified, or not yet decided goes under **Open questions**. Write "None" only when nothing is, and never list what the originator already answered. Show the whole draft, and name the file only as `intent/<change>/intent.md`. In plan mode you cannot write the file. Show the draft as the plan, and write the file once it is approved.
|
|
11
11
|
5. **Gate: the originator corrects the draft.** Ask whether it matches what they meant, and revise until they say yes. Commit nothing, push nothing, and open no pull request before that yes. In plan mode, the originator approving the plan is that yes.
|
|
12
12
|
6. Commit `intent.md` on a new branch, push it, and open a pull request ready for review, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests.
|
|
13
|
-
7. **Gate: the product owner accepts or rejects the intent.** The product owner merges the pull request to accept the intent, or closes it to reject it. You do neither. The file stays `Status: draft.`: never write `Status: accepted.` in this pull request, because the spec's pull request sets it. Report the pull request's URL, keep it green as `playbooks/review.md` says, and wait.
|
|
13
|
+
7. **Gate: the product owner accepts or rejects the intent.** The product owner merges the pull request to accept the intent, or closes it to reject it. You do neither. When the originator is not the person who merges, the originator is the pull request's author or approves it; say so when neither holds. The file stays `Status: draft.`: never write `Status: accepted.` in this pull request, because the spec's pull request sets it. Report the pull request's URL, keep it green as `playbooks/review.md` says, and wait.
|
|
14
14
|
|
|
15
15
|
**Next:** `playbooks/design.md`; an agent; when the product owner has merged the intent's pull request and a person or a job asks for the spec.
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ This playbook writes the intent. The build plan, `intent/<change>/plan.md`, come
|
|
|
19
19
|
```markdown
|
|
20
20
|
# Intent: <title>
|
|
21
21
|
|
|
22
|
-
Author:
|
|
22
|
+
Author: @<handle> (<team>). Status: draft.
|
|
23
23
|
|
|
24
24
|
## Problem
|
|
25
25
|
|
|
@@ -14,7 +14,7 @@ Do these steps in order.
|
|
|
14
14
|
| :---------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
|
|
15
15
|
| A question, or a request to explain something without changing anything | Follow [`playbooks/question.md`](playbooks/question.md). |
|
|
16
16
|
| A spec for an intent, or a path to an `intent.md` | Follow [`playbooks/design.md`](playbooks/design.md). |
|
|
17
|
-
| A build for a spec, a path to a `spec.md`, or a plan the engineer
|
|
17
|
+
| A build for a spec, a path to a `spec.md`, or a plan the engineer merged | Follow [`playbooks/build.md`](playbooks/build.md). |
|
|
18
18
|
| A review or check of a pull request or a branch, a failing check on a pull request, or a pull request to drive to green | Follow [`playbooks/review.md`](playbooks/review.md). |
|
|
19
19
|
| A deploy, a promotion to an environment, or a release to production | Follow [`playbooks/deploy.md`](playbooks/deploy.md). |
|
|
20
20
|
| A finding sent by a job, a monitor, or a trigger, or one the message says no person can answer questions about | Follow [`playbooks/maintain.md`](playbooks/maintain.md). |
|
|
@@ -5,11 +5,11 @@ This playbook turns a merged spec into a plan the engineer accepts, and then int
|
|
|
5
5
|
1. Find the change: the `intent/<change>/` folder whose `spec.md` or `plan.md` the request names. Its `spec.md` must be on the default branch beside its `intent.md`. In each case below, say only that, in two or three lines, and stop: write no file.
|
|
6
6
|
- The request names no change, or the folder has no `spec.md` on any branch: list each `intent/*/` on the default branch that has a `spec.md` and no `plan.md`, or say that there is none.
|
|
7
7
|
- The spec exists only on another branch: say that the spec is not merged yet, and name that branch.
|
|
8
|
-
- `intent/<change>/plan.md` is
|
|
8
|
+
- `intent/<change>/plan.md` is on the default branch and `spec.md` says `Status: done.`: say that the change is built, and name both files.
|
|
9
9
|
|
|
10
10
|
When the request is a bug fix with no spec, none of these cases applies: follow `playbooks/fix.md` instead.
|
|
11
11
|
|
|
12
|
-
2. When `intent/<change>/plan.md`
|
|
12
|
+
2. When `intent/<change>/plan.md` is on the default branch, the engineer accepted it by merging its pull request: go to step 7. When it exists only on a branch that is not merged, check out that branch, show that plan, and go to step 5.
|
|
13
13
|
3. Read `intent.md`, `spec.md`, the four policies, found as `playbooks/design.md` step 2 says, and the code the change touches. Start from the accepted spec, in the agent tool's plan mode when it has one, and give the plan the change's `intent.md` and `spec.md`. Then interview the engineer until they have said four things: the files the change touches, the order of the work, the tests that prove it, and each option they rule out.
|
|
14
14
|
- **When all four are already said, in the request or in the files, go to step 4 in the same turn.**
|
|
15
15
|
- **When one is missing, ask and stop.** Write no plan in that turn. Ask in rounds, as step 3 of `playbooks/plan.md` does: numbered questions, each with a recommended answer or "No recommendation". Wait for the answers, and build the next round on them.
|
|
@@ -21,13 +21,14 @@ This playbook turns a merged spec into a plan the engineer accepts, and then int
|
|
|
21
21
|
|
|
22
22
|
Show the whole plan and name the file only as `intent/<change>/plan.md`. In plan mode you cannot write the file. Show the plan as the plan.
|
|
23
23
|
|
|
24
|
-
5.
|
|
25
|
-
6.
|
|
26
|
-
7.
|
|
27
|
-
8.
|
|
28
|
-
9.
|
|
24
|
+
5. Commit `intent/<change>/plan.md` on a new branch, unless step 2 checked one out, with the `Accepted by:` line as the template has it. In that commit, set the spec's status line to `Status: accepted.`, because its pull request is merged; when an area of concern of that spec has no `Decision:` line, add it there with the decision you propose, as `playbooks/design.md` step 3 says. Push the branch and open a pull request that changes no file outside `intent/`, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests. When the branch already has a pull request, update that one. In plan mode, the engineer's approval lets you write the file and do this step; it is not the acceptance.
|
|
25
|
+
6. **Gate: the engineer accepts the plan.** The engineer merges that pull request to accept the plan, or closes it to reject it. You never merge it, and you change no file outside `intent/` before it is merged. Revise the plan as the engineer asks; each revision is a commit on that branch. When the spec classes the change as higher risk, say that a tech lead or an architect approves that pull request before it is merged. Report the pull request's URL, or the pushed branch's name when you could not open one, keep it green as `playbooks/review.md` says, and wait.
|
|
26
|
+
7. When the plan's pull request is merged, create a new branch as `playbooks/open-pr.md` says. In its first commit, set the plan's `Accepted by:` line, under its title, to the account that merged that pull request, read from the host. When that pull request has an approving review from a second person, add `Also accepted by: @<handle>.` on the next line. When you cannot read the host, confirm with `git fetch` that the plan is on the default branch, and write the handle the engineer gives; ask for it when they gave none. Never write a handle the host or the engineer did not give.
|
|
27
|
+
8. Implement the plan in its order. After each step, run the check that proves it and fix what fails. For a bug fix, follow `playbooks/fix.md` steps 8 and 9: commit the failing test on a `fix/` branch, and the gate then blocks test edits. When the work departs from the plan, change `plan.md` in the same commit. When the change meets the last requirement of `spec.md`, set `Status: done.` in `intent.md` and `spec.md`.
|
|
28
|
+
9. Run every check the project's agent instructions name and keep the output. Every check must pass: fix the code, and never skip, delete, or weaken a test. Then run the `verifier` subagent and fix what it reports. Start it on the model that `.agents/sdlc-models.md` names after `verify:`, at the effort on that line when the session can set one; when you cannot start a subagent, do what `../../../agents/verifier.md`, relative to this playbook, says yourself. For UI work, when the session has a browser tool, also close the loop with a visual check: start the app, give the browser tool the page and the mock the spec or the request names, screenshot, compare, and adjust, and repeat until it matches; two or three rounds is normal. When a check cannot run in this session, say which one and why, and never report it as passed.
|
|
29
|
+
10. Go on in the same turn, without asking: follow `playbooks/open-pr.md`, and then `playbooks/review.md`. Pushing the branch and opening the pull request are part of this task.
|
|
29
30
|
|
|
30
|
-
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step
|
|
31
|
+
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step 9 passes.
|
|
31
32
|
|
|
32
33
|
## Template
|
|
33
34
|
|
|
@@ -8,14 +8,14 @@ This playbook turns a merged intent into the requirements and design spec.
|
|
|
8
8
|
- `spec.md` already exists: say so and name the file.
|
|
9
9
|
2. Read the four policies, every time, before you write: for each of `brand`, `security`, `compliance`, and `ux`, read the project's `.agents/skills/<area>-policy/SKILL.md` when it exists, and the plugin's default at `../../<area>-policy/SKILL.md`, relative to this playbook, otherwise. Then read the code and the docs the change touches.
|
|
10
10
|
3. Write `intent/<change>/spec.md` from the template below. Use exactly the template's headings.
|
|
11
|
-
- **Request:** under the status line, quote the words of the request that started this spec.
|
|
11
|
+
- **Request:** under the status line, quote the words of the request that started this spec. End the line with `Skills at <hash>.`, the short hash of the last commit that changed the plugin's files in this repository: `git log -1 --format=%h -- plugins/sdlc`. When the plugin is not in this repository, write `Skills at unknown.`
|
|
12
12
|
- **Requirements:** numbered. Each one states a result a test or a reader can check.
|
|
13
13
|
- **Design:** how the change fits the existing code: the files that change, and what each one does afterwards.
|
|
14
14
|
- **Policies:** one line for each of the four policies. State which rules of that policy the change must meet, each as a requirement number, or write "Does not apply" and why.
|
|
15
|
-
- **Areas of concern:** a policy rule the change cannot meet, two policies that contradict, or a decision the files do not settle. Give each one a number and the policy it comes from. Write "None" only when there are none.
|
|
15
|
+
- **Areas of concern:** a policy rule the change cannot meet, two policies that contradict, or a decision the files do not settle. Give each one a number and the policy it comes from. Under each, on a line of its own, write `Decision: <the decision you propose>.` Write "None" only when there are none.
|
|
16
16
|
- **Open questions:** each open question from `intent.md`, answered with its evidence or carried forward. Write "None" only when the intent has none.
|
|
17
17
|
4. Commit `spec.md` on a new branch. In the same commit, set the intent's status line to `Status: accepted.`, because its pull request is merged. Push the branch, and open a pull request ready for review, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests. When you cannot open a pull request from this session, say so and give the pushed branch's name.
|
|
18
|
-
5. **Gate: the product owner reviews the spec.** The product owner
|
|
18
|
+
5. **Gate: the product owner reviews the spec.** The product owner corrects the `Decision:` line under each area of concern, with its policy owner, then merges the pull request to accept the spec and those decisions as written, or closes it to reject it. Change a `Decision:` line when they tell you its text. For a change the spec classes as higher risk, the product owner consults a tech lead before accepting. You do none of these. The spec stays `Status: draft.`: never write `Status: accepted.` in this pull request, because the plan's commit sets it. Report the pull request's URL, keep it green as `playbooks/review.md` says, and wait.
|
|
19
19
|
|
|
20
20
|
**Next:** `playbooks/build.md`; an engineer with an agent; when the product owner has merged the spec's pull request and the engineer sends `/sdlc:sdlc-mode build` with the spec's path.
|
|
21
21
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This playbook takes a defect in behaviour that already exists to a pull request. It has no intent and no spec: its plan is the only file it adds under `intent/`.
|
|
4
4
|
|
|
5
|
-
1. Decide that the request qualifies. It qualifies only when the request is a defect in behaviour that already exists, and the fix adds no new behaviour and no new dependency and changes no schema and no public interface. When a condition fails, say which one in two lines and follow `playbooks/plan.md` instead.
|
|
5
|
+
1. Decide that the request qualifies. It qualifies only when the request is a defect in behaviour that already exists, and the fix adds no new behaviour and no new dependency and changes no schema and no public interface. When a condition fails, say which one in two lines and follow `playbooks/plan.md` instead. When `intent/<short-description>/plan.md` for this defect is already on the default branch, the engineer accepted it by merging its pull request: go to step 7.
|
|
6
6
|
2. Reproduce the defect yourself by running the code, and keep the output. Ask the person only when you can name the specific reason you cannot reach it, such as a credential or a service this session cannot use.
|
|
7
7
|
3. Find the cause from what running the code shows. List the candidate causes, rule each out with a command or an observation, and never guess. Name the one cause the evidence leaves. When it leaves none, or more than one, say so and ask for what is missing.
|
|
8
8
|
4. Write the plan from the template in `playbooks/build.md`. The title is `# Plan: <title> (fix)`. Use exactly the template's four headings, and keep the plan short. Name the file `intent/<short-description>/plan.md`; the folder holds only this file.
|
|
@@ -13,11 +13,12 @@ This playbook takes a defect in behaviour that already exists to a pull request.
|
|
|
13
13
|
|
|
14
14
|
Show the whole plan and name the file only as `intent/<short-description>/plan.md`. In plan mode you cannot write the file. Show the plan as the plan.
|
|
15
15
|
|
|
16
|
-
5.
|
|
17
|
-
6.
|
|
18
|
-
7.
|
|
19
|
-
8.
|
|
20
|
-
9.
|
|
21
|
-
10.
|
|
16
|
+
5. Commit `intent/<short-description>/plan.md` on a new branch, with the `Accepted by:` line as the template has it, push it, and open a pull request that changes no file outside `intent/`, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests. In plan mode, the engineer's approval lets you write the file and do this step; it is not the acceptance.
|
|
17
|
+
6. **Gate: the engineer accepts the plan.** The engineer merges that pull request to accept the plan, or closes it to reject it. You never merge it, and you change no file outside `intent/` before it is merged. Revise the plan as the engineer asks; each revision is a commit on that branch. Report the pull request's URL, or the pushed branch's name when you could not open one, keep it green as `playbooks/review.md` says, and wait.
|
|
18
|
+
7. When the plan's pull request is merged, create the branch `fix/<short-description>` as `playbooks/open-pr.md` says. In its first commit, set the plan's `Accepted by:` line, under its title, to the account that merged that pull request, read from the host. When you cannot read the host, confirm with `git fetch` that the plan is on the default branch, and write the handle the engineer gives; ask for it when they gave none. Never write a handle the host or the engineer did not give.
|
|
19
|
+
8. Reproduce the defect as a test. Run it and check that it fails for the reason the cause gives. Commit the failing test before any fix.
|
|
20
|
+
9. Make the test pass without editing it.
|
|
21
|
+
10. Run every check the project's agent instructions name. Keep the output. When one fails, fix the code, and never skip, delete, or weaken a test. When a check cannot run in this session, say which one and why, and never report it as passed.
|
|
22
|
+
11. Go on in the same turn, without asking. Follow `playbooks/open-pr.md`, and then `playbooks/review.md`.
|
|
22
23
|
|
|
23
|
-
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step
|
|
24
|
+
**Next:** `playbooks/open-pr.md`; an agent; in the same turn, as soon as step 10 passes.
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
This playbook writes the intent. The build plan, `intent/<change>/plan.md`, comes later and is not written here.
|
|
4
4
|
|
|
5
5
|
1. Let the originator describe the problem in their own words: what they cannot do today, who is affected, what better looks like, and what is out of scope.
|
|
6
|
-
2. Read the code, the docs, and the existing `intent/` folders. Find the rest yourself: the
|
|
6
|
+
2. Read the code, the docs, and the existing `intent/` folders. Find the rest yourself. The author is the originator's handle on the host: read it from the host when the session shows whose account it runs as, and ask for it otherwise. Never take it from `git config user.name`, which names the machine's user.
|
|
7
7
|
3. Interview until the originator has said four things: the problem, the outcome, the constraints, and what is out of scope. The outcome is what the user would see.
|
|
8
8
|
- **When all four are already said, go to step 4 in the same turn.**
|
|
9
9
|
- **When one is missing, ask and stop.** Write no draft in that turn. Ask in rounds. Each round holds only questions that do not depend on another question in the same round. Number the questions. Each asks one thing and carries a concrete answer you recommend, worded so "yes" accepts it. A recommended answer is a fact or a choice, such as "Recommended: only the checkout page", never an instruction to the person. When you have no answer to recommend, write "No recommendation" and ask plainly. Wait for the answers, and build the next round on them.
|
|
10
10
|
4. Write `intent/<change>/intent.md` from the template below, where `<change>` is a short kebab-case name. Use exactly the template's five headings. What is out of scope goes under **Proposed outcome**. Anything else that is unknown, unverified, or not yet decided goes under **Open questions**. Write "None" only when nothing is, and never list what the originator already answered. Show the whole draft, and name the file only as `intent/<change>/intent.md`. In plan mode you cannot write the file. Show the draft as the plan, and write the file once it is approved.
|
|
11
11
|
5. **Gate: the originator corrects the draft.** Ask whether it matches what they meant, and revise until they say yes. Commit nothing, push nothing, and open no pull request before that yes. In plan mode, the originator approving the plan is that yes.
|
|
12
12
|
6. Commit `intent.md` on a new branch, push it, and open a pull request ready for review, as the opening paragraph of `playbooks/open-pr.md` says for branches and pull requests.
|
|
13
|
-
7. **Gate: the product owner accepts or rejects the intent.** The product owner merges the pull request to accept the intent, or closes it to reject it. You do neither. The file stays `Status: draft.`: never write `Status: accepted.` in this pull request, because the spec's pull request sets it. Report the pull request's URL, keep it green as `playbooks/review.md` says, and wait.
|
|
13
|
+
7. **Gate: the product owner accepts or rejects the intent.** The product owner merges the pull request to accept the intent, or closes it to reject it. You do neither. When the originator is not the person who merges, the originator is the pull request's author or approves it; say so when neither holds. The file stays `Status: draft.`: never write `Status: accepted.` in this pull request, because the spec's pull request sets it. Report the pull request's URL, keep it green as `playbooks/review.md` says, and wait.
|
|
14
14
|
|
|
15
15
|
**Next:** `playbooks/design.md`; an agent; when the product owner has merged the intent's pull request and a person or a job asks for the spec.
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ This playbook writes the intent. The build plan, `intent/<change>/plan.md`, come
|
|
|
19
19
|
```markdown
|
|
20
20
|
# Intent: <title>
|
|
21
21
|
|
|
22
|
-
Author:
|
|
22
|
+
Author: @<handle> (<team>). Status: draft.
|
|
23
23
|
|
|
24
24
|
## Problem
|
|
25
25
|
|
|
@@ -3,30 +3,15 @@ import {existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSyn
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import {tmpdir} from "node:os";
|
|
5
5
|
|
|
6
|
-
type BranchChanges = {
|
|
7
|
-
changedPaths: string[];
|
|
8
|
-
headCommit: string;
|
|
9
|
-
mergeBase: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type SelfTestCase = {
|
|
13
|
-
expectedMessagePart?: string;
|
|
14
|
-
name: string;
|
|
15
|
-
problems: string[];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
6
|
const defaultBranch = "origin/main";
|
|
19
7
|
const intentDirectory = "intent";
|
|
20
|
-
const planFileName = "plan.md";
|
|
21
|
-
const specFileName = "spec.md";
|
|
22
|
-
const intentFileName = "intent.md";
|
|
23
|
-
const proofHeading = "## Proof";
|
|
24
|
-
const requirementsHeading = "## Requirements";
|
|
25
|
-
const acceptancePlaceholder = "@<engineer>";
|
|
26
8
|
const statusLineLimit = 5;
|
|
27
9
|
const acceptedByPrefix = "Accepted by: ";
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
|
|
11
|
+
type PlanMerge = {
|
|
12
|
+
approvers: string[];
|
|
13
|
+
mergedBy: string;
|
|
14
|
+
};
|
|
30
15
|
|
|
31
16
|
/**
|
|
32
17
|
* Run git in the current directory.
|
|
@@ -40,52 +25,6 @@ function runGit(gitArguments: string[]): string {
|
|
|
40
25
|
}).trim();
|
|
41
26
|
}
|
|
42
27
|
|
|
43
|
-
/**
|
|
44
|
-
* Find what this branch changed since it left the default branch.
|
|
45
|
-
* @returns The merge base, HEAD, and the changed paths, or undefined when the default branch is not available.
|
|
46
|
-
*/
|
|
47
|
-
function findBranchChanges(): BranchChanges | undefined {
|
|
48
|
-
try {
|
|
49
|
-
const mergeBase = runGit(["merge-base", "HEAD", defaultBranch]);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
mergeBase,
|
|
53
|
-
headCommit: runGit(["rev-parse", "HEAD"]),
|
|
54
|
-
changedPaths: runGit(["diff", "--name-only", `${mergeBase}...HEAD`])
|
|
55
|
-
.split("\n")
|
|
56
|
-
.filter(changedPath => changedPath !== ""),
|
|
57
|
-
};
|
|
58
|
-
} catch {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Tell whether a plan must name its accepting engineer: the branch changes a file outside `intent/`.
|
|
65
|
-
* @param branchChanges What the branch changed, or undefined when no merge base was found.
|
|
66
|
-
* @returns False when there is no merge base or HEAD is the default branch tip.
|
|
67
|
-
*/
|
|
68
|
-
function requiresAcceptance(branchChanges: BranchChanges | undefined): boolean {
|
|
69
|
-
return (
|
|
70
|
-
branchChanges !== undefined &&
|
|
71
|
-
branchChanges.mergeBase !== branchChanges.headCommit &&
|
|
72
|
-
branchChanges.changedPaths.some(changedPath => !changedPath.startsWith(`${intentDirectory}/`))
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Say why the acceptance rule cannot run.
|
|
78
|
-
* @param branchChanges What the branch changed, or undefined when no merge base was found.
|
|
79
|
-
* @returns A problem when the default branch is not available, otherwise undefined.
|
|
80
|
-
*/
|
|
81
|
-
function describeMissingDefaultBranch(
|
|
82
|
-
branchChanges: BranchChanges | undefined,
|
|
83
|
-
): string | undefined {
|
|
84
|
-
return branchChanges === undefined
|
|
85
|
-
? `${defaultBranch} cannot be found, so the "${acceptedByPrefix}" rule cannot be applied; run "git fetch origin main" to apply it`
|
|
86
|
-
: undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
28
|
/**
|
|
90
29
|
* Blank every line of a fenced code block, keeping line numbers.
|
|
91
30
|
* @param lines Document lines.
|
|
@@ -148,6 +87,214 @@ function readSection(maskedLines: string[], heading: string): string[] | undefin
|
|
|
148
87
|
return bounds === undefined ? undefined : maskedLines.slice(...bounds);
|
|
149
88
|
}
|
|
150
89
|
|
|
90
|
+
const concernsHeading = "## Areas of concern";
|
|
91
|
+
const decisionPrefix = "Decision: ";
|
|
92
|
+
const decisionPattern = /^ *Decision: \S/mu;
|
|
93
|
+
const alsoAcceptedByPrefix = "Also accepted by: ";
|
|
94
|
+
const hostTokenVariable = "GH_TOKEN";
|
|
95
|
+
const hostRepositoryPath = "repos/{owner}/{repo}";
|
|
96
|
+
const authorPrefix = "Author: ";
|
|
97
|
+
const authorPattern = /^Author: @[\dA-Za-z][\w-]*[ .]/;
|
|
98
|
+
const alsoAcceptedByPattern = /^Also accepted by: @([\dA-Za-z][\w-]*)\.$/;
|
|
99
|
+
|
|
100
|
+
type BranchChanges = {
|
|
101
|
+
changedPaths: string[];
|
|
102
|
+
headCommit: string;
|
|
103
|
+
mergeBase: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type SelfTestCase = {
|
|
107
|
+
expectedMessagePart?: string;
|
|
108
|
+
name: string;
|
|
109
|
+
problems: string[];
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Find a plan that this branch adds together with a change outside `intent/`.
|
|
114
|
+
* @param isMerged Whether the default branch already holds the plan.
|
|
115
|
+
* @param isAcceptanceRequired Whether the branch changes a file outside `intent/`.
|
|
116
|
+
* @returns At most one message.
|
|
117
|
+
*/
|
|
118
|
+
function findUnmergedPlanProblems(isMerged: boolean, isAcceptanceRequired: boolean): string[] {
|
|
119
|
+
return isMerged || !isAcceptanceRequired
|
|
120
|
+
? []
|
|
121
|
+
: [
|
|
122
|
+
`the plan is not on ${defaultBranch} and this branch changes a file outside ${intentDirectory}/; an engineer accepts a plan by merging the pull request that holds only it, so open that pull request and build on a new branch after it is merged`,
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Compare the plan's accepting handles with the host's record of its pull request.
|
|
128
|
+
* @param maskedLines Plan lines with fenced code blocks emptied.
|
|
129
|
+
* @param planMerge Who merged the pull request that last changed the plan, and who approved it.
|
|
130
|
+
* @returns One message per handle the host does not confirm.
|
|
131
|
+
*/
|
|
132
|
+
function findHostAcceptanceProblems(maskedLines: string[], planMerge: PlanMerge): string[] {
|
|
133
|
+
const acceptedLine = maskedLines.slice(1).find(line => line.trim() !== "") ?? "";
|
|
134
|
+
const accepter = acceptedLine.trimEnd().slice(acceptedByPrefix.length + 1, -1);
|
|
135
|
+
const secondAccepter = readSecondAccepter(maskedLines)?.toLowerCase();
|
|
136
|
+
const merger = planMerge.mergedBy.toLowerCase();
|
|
137
|
+
const approvers = planMerge.approvers.map(approver => approver.toLowerCase());
|
|
138
|
+
|
|
139
|
+
const mergerProblems =
|
|
140
|
+
accepter.toLowerCase() === merger
|
|
141
|
+
? []
|
|
142
|
+
: [
|
|
143
|
+
`the "${acceptedByPrefix}" line names @${accepter}, and the host shows that @${planMerge.mergedBy} merged the plan's pull request; write the account that merged it`,
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const isSecondConfirmed =
|
|
147
|
+
secondAccepter === undefined ||
|
|
148
|
+
(secondAccepter !== merger && approvers.includes(secondAccepter));
|
|
149
|
+
|
|
150
|
+
return isSecondConfirmed
|
|
151
|
+
? mergerProblems
|
|
152
|
+
: [
|
|
153
|
+
...mergerProblems,
|
|
154
|
+
`the "${alsoAcceptedByPrefix}" line names a second person, and the host shows no approving review from that account on the plan's pull request; write "${alsoAcceptedByPrefix}@<handle>." with the account that approved it`,
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Find each numbered area of concern in a spec that carries no decision.
|
|
160
|
+
* @param specText Spec content.
|
|
161
|
+
* @returns At most one message, which names the numbers.
|
|
162
|
+
*/
|
|
163
|
+
function findDecisionProblems(specText: string): string[] {
|
|
164
|
+
const undecidedNumbers = (
|
|
165
|
+
readSection(maskFencedBlocks(splitLines(specText)), concernsHeading) ?? []
|
|
166
|
+
)
|
|
167
|
+
.join("\n")
|
|
168
|
+
.split(/^(?=\d+\. )/mu)
|
|
169
|
+
.filter(concern => /^\d+\. /u.test(concern) && !decisionPattern.test(concern))
|
|
170
|
+
.map(concern => /^\d+/u.exec(concern)?.[0]);
|
|
171
|
+
|
|
172
|
+
return undecidedNumbers.length === 0
|
|
173
|
+
? []
|
|
174
|
+
: [
|
|
175
|
+
`area of concern ${undecidedNumbers.join(", ")} has no "${decisionPrefix}<the decision>." line under it; write the proposed decision there, and the product owner corrects it before merging the spec`,
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Find an intent whose author is not a handle on the host.
|
|
181
|
+
* @param intentText Intent content.
|
|
182
|
+
* @returns At most one message.
|
|
183
|
+
*/
|
|
184
|
+
function findAuthorProblems(intentText: string): string[] {
|
|
185
|
+
const authorLine = splitLines(intentText).find(line => line.startsWith(authorPrefix));
|
|
186
|
+
|
|
187
|
+
return authorLine !== undefined && authorPattern.test(authorLine)
|
|
188
|
+
? []
|
|
189
|
+
: [
|
|
190
|
+
`the "${authorPrefix}" line holds no handle; write "${authorPrefix}@<handle> (<team>)." with the originator's account on the host`,
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Ask the host who merged the pull request that last changed a plan on the default branch, and who approved it.
|
|
196
|
+
* @param mergeBase The commit this branch left the default branch at.
|
|
197
|
+
* @param planPath Path of the plan.
|
|
198
|
+
* @returns The merging account and the approving accounts, or undefined when no host token is set.
|
|
199
|
+
* @throws {Error} When a token is set and git or the host's tool fails.
|
|
200
|
+
*/
|
|
201
|
+
function readPlanMerge(mergeBase: string, planPath: string): PlanMerge | undefined {
|
|
202
|
+
if (process.env[hostTokenVariable] === undefined || process.env[hostTokenVariable] === "") {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const commit = runGit(["log", "-1", "--format=%H", mergeBase, "--", planPath]);
|
|
207
|
+
const pullRequest = runHost(`${hostRepositoryPath}/commits/${commit}/pulls`, ".[0].number");
|
|
208
|
+
const pullRequestPath = `${hostRepositoryPath}/pulls/${pullRequest}`;
|
|
209
|
+
const mergedBy = runHost(pullRequestPath, ".merged_by.login");
|
|
210
|
+
const approvedFilter = '.[] | select(.state == "APPROVED") | .user.login';
|
|
211
|
+
const approvers = runHost(`${pullRequestPath}/reviews`, approvedFilter);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
mergedBy,
|
|
215
|
+
approvers: approvers.split("\n").filter(approver => approver !== ""),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Call the host's API through its command-line tool.
|
|
221
|
+
* @param apiPath Path after `gh api`.
|
|
222
|
+
* @param filter The jq filter applied to the answer.
|
|
223
|
+
* @returns Trimmed standard output.
|
|
224
|
+
*/
|
|
225
|
+
function runHost(apiPath: string, filter: string): string {
|
|
226
|
+
return execFileSync("gh", ["api", apiPath, "--jq", filter], {
|
|
227
|
+
encoding: "utf8",
|
|
228
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
229
|
+
}).trim();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Read the handle on the plan's `Also accepted by:` line.
|
|
234
|
+
* @param maskedLines Plan lines with fenced code blocks emptied.
|
|
235
|
+
* @returns The handle, an empty string when the line is malformed, or undefined when there is no such line.
|
|
236
|
+
*/
|
|
237
|
+
function readSecondAccepter(maskedLines: string[]): string | undefined {
|
|
238
|
+
const line = maskedLines.find(planLine => planLine.startsWith(alsoAcceptedByPrefix));
|
|
239
|
+
|
|
240
|
+
return line === undefined ? undefined : (alsoAcceptedByPattern.exec(line.trimEnd())?.[1] ?? "");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const planFileName = "plan.md";
|
|
244
|
+
const specFileName = "spec.md";
|
|
245
|
+
const intentFileName = "intent.md";
|
|
246
|
+
const proofHeading = "## Proof";
|
|
247
|
+
const requirementsHeading = "## Requirements";
|
|
248
|
+
const acceptancePlaceholder = "@<engineer>";
|
|
249
|
+
const acceptedByPattern = /^Accepted by: @[\dA-Za-z][\w-]*\.$/;
|
|
250
|
+
const requiredHeadings = ["## Files that change", "## Order of work", "## Risks", proofHeading];
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Find what this branch changed since it left the default branch.
|
|
254
|
+
* @returns The merge base, HEAD, and the changed paths, or undefined when the default branch is not available.
|
|
255
|
+
*/
|
|
256
|
+
function findBranchChanges(): BranchChanges | undefined {
|
|
257
|
+
try {
|
|
258
|
+
const mergeBase = runGit(["merge-base", "HEAD", defaultBranch]);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
mergeBase,
|
|
262
|
+
headCommit: runGit(["rev-parse", "HEAD"]),
|
|
263
|
+
changedPaths: runGit(["diff", "--name-only", `${mergeBase}...HEAD`])
|
|
264
|
+
.split("\n")
|
|
265
|
+
.filter(changedPath => changedPath !== ""),
|
|
266
|
+
};
|
|
267
|
+
} catch {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Tell whether a plan must name its accepting engineer: the branch changes a file outside `intent/`.
|
|
274
|
+
* @param branchChanges What the branch changed, or undefined when no merge base was found.
|
|
275
|
+
* @returns False when there is no merge base or HEAD is the default branch tip.
|
|
276
|
+
*/
|
|
277
|
+
function requiresAcceptance(branchChanges: BranchChanges | undefined): boolean {
|
|
278
|
+
return (
|
|
279
|
+
branchChanges !== undefined &&
|
|
280
|
+
branchChanges.mergeBase !== branchChanges.headCommit &&
|
|
281
|
+
branchChanges.changedPaths.some(changedPath => !changedPath.startsWith(`${intentDirectory}/`))
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Say why the acceptance rule cannot run.
|
|
287
|
+
* @param branchChanges What the branch changed, or undefined when no merge base was found.
|
|
288
|
+
* @returns A problem when the default branch is not available, otherwise undefined.
|
|
289
|
+
*/
|
|
290
|
+
function describeMissingDefaultBranch(
|
|
291
|
+
branchChanges: BranchChanges | undefined,
|
|
292
|
+
): string | undefined {
|
|
293
|
+
return branchChanges === undefined
|
|
294
|
+
? `${defaultBranch} cannot be found, so the "${acceptedByPrefix}" rule cannot be applied; run "git fetch origin main" to apply it`
|
|
295
|
+
: undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
151
298
|
/**
|
|
152
299
|
* Quote each heading and join them for a message.
|
|
153
300
|
* @param headings Heading texts.
|
|
@@ -520,6 +667,26 @@ function createChangeFolder(fileNames: string[]): string {
|
|
|
520
667
|
const fixPlanFolder = createChangeFolder([planFileName]);
|
|
521
668
|
const intentOnlyFolder = createChangeFolder([intentFileName, planFileName]);
|
|
522
669
|
const specOnlyFolder = createChangeFolder([specFileName, planFileName]);
|
|
670
|
+
const onePlanLines = splitLines(buildPlan({}));
|
|
671
|
+
|
|
672
|
+
const twoPlanLines = splitLines(
|
|
673
|
+
buildPlan({
|
|
674
|
+
accepted: `${engineerLine}\n${alsoAcceptedByPrefix}@lead.`,
|
|
675
|
+
}),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Build the host's record of a plan's pull request, for a self-test.
|
|
680
|
+
* @param mergedByLogin The account that merged it.
|
|
681
|
+
* @param approvers The accounts that approved it.
|
|
682
|
+
* @returns The record.
|
|
683
|
+
*/
|
|
684
|
+
function hostRecord(mergedByLogin: string, approvers: string[]): PlanMerge {
|
|
685
|
+
return {
|
|
686
|
+
approvers,
|
|
687
|
+
mergedBy: mergedByLogin,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
523
690
|
|
|
524
691
|
const selfTestCases: SelfTestCase[] = [
|
|
525
692
|
{
|
|
@@ -843,6 +1010,80 @@ const selfTestCases: SelfTestCase[] = [
|
|
|
843
1010
|
false,
|
|
844
1011
|
),
|
|
845
1012
|
},
|
|
1013
|
+
{
|
|
1014
|
+
name: "a plan added together with code",
|
|
1015
|
+
expectedMessagePart: `is not on ${defaultBranch}`,
|
|
1016
|
+
problems: findUnmergedPlanProblems(false, true),
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
name: "a plan added alone",
|
|
1020
|
+
problems: findUnmergedPlanProblems(false, false),
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
name: "a plan that is on the default branch, built with code",
|
|
1024
|
+
problems: findUnmergedPlanProblems(true, true),
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
name: "the accepting handle is the account that merged, in another letter case",
|
|
1028
|
+
problems: findHostAcceptanceProblems(onePlanLines, hostRecord("Engineer", [])),
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
name: "the accepting handle is not the account that merged",
|
|
1032
|
+
expectedMessagePart: "@someone-else merged",
|
|
1033
|
+
problems: findHostAcceptanceProblems(onePlanLines, hostRecord("someone-else", [])),
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
name: "the second accepter approved the plan's pull request",
|
|
1037
|
+
problems: findHostAcceptanceProblems(twoPlanLines, hostRecord("engineer", ["lead"])),
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
name: "the second accepter gave no approving review",
|
|
1041
|
+
expectedMessagePart: "no approving review",
|
|
1042
|
+
problems: findHostAcceptanceProblems(twoPlanLines, hostRecord("engineer", [])),
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
name: "the second accepter is the account that merged",
|
|
1046
|
+
expectedMessagePart: "no approving review",
|
|
1047
|
+
problems: findHostAcceptanceProblems(
|
|
1048
|
+
splitLines(
|
|
1049
|
+
buildPlan({
|
|
1050
|
+
accepted: `${engineerLine}\n${alsoAcceptedByPrefix}@engineer.`,
|
|
1051
|
+
}),
|
|
1052
|
+
),
|
|
1053
|
+
hostRecord("engineer", ["engineer"]),
|
|
1054
|
+
),
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
name: "every area of concern has a decision",
|
|
1058
|
+
problems: findDecisionProblems(
|
|
1059
|
+
`${concernsHeading}\n\n1. A.\n ${decisionPrefix}yes.\n2. B.\n ${decisionPrefix}no.\n\n## Open questions\n`,
|
|
1060
|
+
),
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
name: "a spec with no area of concern",
|
|
1064
|
+
problems: findDecisionProblems(`${concernsHeading}\n\nNone\n\n## Open questions\n`),
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
name: "an area of concern has no decision",
|
|
1068
|
+
expectedMessagePart: "area of concern 2 has no",
|
|
1069
|
+
problems: findDecisionProblems(
|
|
1070
|
+
`${concernsHeading}\n\n1. A.\n ${decisionPrefix}yes.\n2. B.\n\n## Open questions\n`,
|
|
1071
|
+
),
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
name: "a decision line with no text is not a decision",
|
|
1075
|
+
expectedMessagePart: "area of concern 1 has no",
|
|
1076
|
+
problems: findDecisionProblems(`${concernsHeading}\n\n1. A.\n ${decisionPrefix}\n`),
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
name: "an intent whose author is a handle",
|
|
1080
|
+
problems: findAuthorProblems("# Intent: A\n\nAuthor: @originator (Team). Status: draft.\n"),
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
name: "an intent whose author is a name",
|
|
1084
|
+
expectedMessagePart: "holds no handle",
|
|
1085
|
+
problems: findAuthorProblems("# Intent: A\n\nAuthor: A Person (Team). Status: draft.\n"),
|
|
1086
|
+
},
|
|
846
1087
|
];
|
|
847
1088
|
|
|
848
1089
|
/**
|
|
@@ -945,17 +1186,27 @@ function findPlanLines(planPaths: string[]): string[] {
|
|
|
945
1186
|
`${planPath}: the file does not exist; pass the path of an existing ${planFileName}`,
|
|
946
1187
|
);
|
|
947
1188
|
|
|
948
|
-
const
|
|
1189
|
+
const changedPlanPaths = planPaths
|
|
949
1190
|
.filter(planPath => existsSync(planPath))
|
|
950
1191
|
.filter(
|
|
951
1192
|
planPath =>
|
|
952
1193
|
branchChanges === undefined ||
|
|
953
1194
|
readDefaultBranchFile(branchChanges.mergeBase, planPath) !==
|
|
954
1195
|
readFileSync(planPath, "utf8").trim(),
|
|
955
|
-
)
|
|
956
|
-
.flatMap(planPath => findPlanFileLines(planPath, isAcceptanceRequired));
|
|
1196
|
+
);
|
|
957
1197
|
|
|
958
|
-
|
|
1198
|
+
const checkedLines = changedPlanPaths.flatMap(planPath =>
|
|
1199
|
+
findPlanFileLines(planPath, isAcceptanceRequired),
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
const acceptanceLines =
|
|
1203
|
+
branchChanges === undefined
|
|
1204
|
+
? []
|
|
1205
|
+
: changedPlanPaths.flatMap(planPath =>
|
|
1206
|
+
findPlanAcceptanceLines(planPath, branchChanges.mergeBase, isAcceptanceRequired),
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1209
|
+
return [...missingBranchLines, ...missingLines, ...checkedLines, ...acceptanceLines];
|
|
959
1210
|
}
|
|
960
1211
|
|
|
961
1212
|
/**
|
|
@@ -972,6 +1223,78 @@ function readDefaultBranchFile(mergeBase: string, filePath: string): string | un
|
|
|
972
1223
|
}
|
|
973
1224
|
}
|
|
974
1225
|
|
|
1226
|
+
/**
|
|
1227
|
+
* Check that a plan this branch builds was accepted by a merge, and by whom.
|
|
1228
|
+
* @param planPath Path of the plan.
|
|
1229
|
+
* @param mergeBase The commit this branch left the default branch at.
|
|
1230
|
+
* @param isAcceptanceRequired Whether the branch changes a file outside `intent/`.
|
|
1231
|
+
* @returns One line per problem.
|
|
1232
|
+
*/
|
|
1233
|
+
function findPlanAcceptanceLines(
|
|
1234
|
+
planPath: string,
|
|
1235
|
+
mergeBase: string,
|
|
1236
|
+
isAcceptanceRequired: boolean,
|
|
1237
|
+
): string[] {
|
|
1238
|
+
const isOnDefaultBranch = readDefaultBranchFile(mergeBase, planPath) !== undefined;
|
|
1239
|
+
const unmergedProblems = findUnmergedPlanProblems(isOnDefaultBranch, isAcceptanceRequired);
|
|
1240
|
+
|
|
1241
|
+
if (!isAcceptanceRequired || unmergedProblems.length > 0) {
|
|
1242
|
+
return unmergedProblems.map(message => `${planPath}: ${message}`);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
const planMerge = readPlanMerge(mergeBase, planPath);
|
|
1246
|
+
|
|
1247
|
+
if (planMerge === undefined) {
|
|
1248
|
+
process.stdout.write(
|
|
1249
|
+
`${planPath}: the "${acceptedByPrefix}" handle was not checked against the host; set ${hostTokenVariable} and install gh to check who merged the plan's pull request\n`,
|
|
1250
|
+
);
|
|
1251
|
+
|
|
1252
|
+
return [];
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const planLines = splitLines(readFileSync(planPath, "utf8"));
|
|
1256
|
+
|
|
1257
|
+
return findHostAcceptanceProblems(maskFencedBlocks(planLines), planMerge).map(
|
|
1258
|
+
message => `${planPath}: ${message}`,
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Report each spec this branch adds or changes that leaves an area of concern undecided, and each intent it adds whose author is not a handle.
|
|
1264
|
+
* @param changeFolders The `intent/<change>` folders.
|
|
1265
|
+
* @returns One line per problem.
|
|
1266
|
+
*/
|
|
1267
|
+
function findRecordLines(changeFolders: string[]): string[] {
|
|
1268
|
+
const branchChanges = changeFolders.length > 0 ? findBranchChanges() : undefined;
|
|
1269
|
+
|
|
1270
|
+
if (branchChanges === undefined) {
|
|
1271
|
+
return [];
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
return changeFolders.flatMap(folder => {
|
|
1275
|
+
const specPath = path.join(folder, specFileName);
|
|
1276
|
+
const intentPath = path.join(folder, intentFileName);
|
|
1277
|
+
const specText = readSpec(specPath);
|
|
1278
|
+
|
|
1279
|
+
const specProblems =
|
|
1280
|
+
specText === undefined ||
|
|
1281
|
+
readDefaultBranchFile(branchChanges.mergeBase, specPath) === specText.trim()
|
|
1282
|
+
? []
|
|
1283
|
+
: findDecisionProblems(specText);
|
|
1284
|
+
|
|
1285
|
+
const authorProblems =
|
|
1286
|
+
existsSync(intentPath) &&
|
|
1287
|
+
readDefaultBranchFile(branchChanges.mergeBase, intentPath) === undefined
|
|
1288
|
+
? findAuthorProblems(readFileSync(intentPath, "utf8"))
|
|
1289
|
+
: [];
|
|
1290
|
+
|
|
1291
|
+
return [
|
|
1292
|
+
...specProblems.map(message => `${specPath}: ${message}`),
|
|
1293
|
+
...authorProblems.map(message => `${intentPath}: ${message}`),
|
|
1294
|
+
];
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
|
|
975
1298
|
/**
|
|
976
1299
|
* Report each intent and spec that this branch marks accepted before its pull request merged.
|
|
977
1300
|
* @param changeFolders The `intent/<change>` folders.
|
|
@@ -1034,6 +1357,7 @@ if (selfTestFailures.length > 0) {
|
|
|
1034
1357
|
...findPlanLines(planPaths),
|
|
1035
1358
|
...findMissingPlanLines(changeFolders),
|
|
1036
1359
|
...findAcceptedStatusLines(changeFolders),
|
|
1360
|
+
...findRecordLines(changeFolders),
|
|
1037
1361
|
];
|
|
1038
1362
|
|
|
1039
1363
|
if (problemLines.length > 0) {
|
|
@@ -62,6 +62,9 @@ jobs:
|
|
|
62
62
|
if: needs.changes.outputs.full_verification == 'true'
|
|
63
63
|
runs-on: ubuntu-latest
|
|
64
64
|
timeout-minutes: 20
|
|
65
|
+
permissions:
|
|
66
|
+
contents: read
|
|
67
|
+
pull-requests: read # Needed by plan:check to read who merged the plan's pull request.
|
|
65
68
|
steps:
|
|
66
69
|
- name: Checkout repository
|
|
67
70
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
@@ -78,6 +81,8 @@ jobs:
|
|
|
78
81
|
- name: Check agent gates
|
|
79
82
|
run: pnpm gates:check
|
|
80
83
|
- name: Check plans
|
|
84
|
+
env:
|
|
85
|
+
GH_TOKEN: ${{ github.token }} # plan:check reads who merged the plan's pull request.
|
|
81
86
|
run: pnpm plan:check
|
|
82
87
|
- name: Check bands
|
|
83
88
|
run: pnpm bands:check
|
|
@@ -34,7 +34,7 @@ The `verifier` subagent starts the app with `pnpm start:dev`. It exercises the c
|
|
|
34
34
|
1. **Put unit tests next to the code as `src/**/*.test.ts` and end-to-end tests in `test/` as `*.e2e.test.ts`.** `vitest.config.ts` and `vitest.e2e.config.ts` collect only those patterns.
|
|
35
35
|
2. **Read configuration from environment variables and fail when one is missing**, as `parsePort` does. Document each variable in `.env.example`.
|
|
36
36
|
3. **Log through Nest's `Logger`.** It goes to the `ConsoleLogger` set in `src/main.ts`.
|
|
37
|
-
4. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`.
|
|
37
|
+
4. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`. The plan, `intent/<change>/plan.md`, gets a pull request of its own, and the engineer accepts it by merging that pull request, as `playbooks/build.md` of the `sdlc-mode` skill in `plugins/sdlc/claude-code/` describes.
|
|
38
38
|
5. **Start a bug fix with a failing test.** A bug fix has no intent and no spec; its plan, `intent/<short-description>/plan.md`, is accepted by an engineer before any code changes. Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it on a `fix/` branch. Then `.agents/hooks/agent-test-edit-gate.sh` blocks test edits by itself: make the test pass without editing it. On a branch with another name, create `.git/bug-fix-in-progress` first, and delete it once the test passes.
|
|
39
39
|
6. **Do not start work a person did not ask for.** When a message reports a problem without asking for work, answer in a few lines and end with the message that would start it: `/sdlc:sdlc-mode` followed by their own words.
|
|
40
40
|
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. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
@@ -62,6 +62,9 @@ jobs:
|
|
|
62
62
|
if: needs.changes.outputs.full_verification == 'true'
|
|
63
63
|
runs-on: ubuntu-latest
|
|
64
64
|
timeout-minutes: 20
|
|
65
|
+
permissions:
|
|
66
|
+
contents: read
|
|
67
|
+
pull-requests: read # Needed by plan:check to read who merged the plan's pull request.
|
|
65
68
|
steps:
|
|
66
69
|
- name: Checkout repository
|
|
67
70
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
@@ -78,6 +81,8 @@ jobs:
|
|
|
78
81
|
- name: Check agent gates
|
|
79
82
|
run: pnpm gates:check
|
|
80
83
|
- name: Check plans
|
|
84
|
+
env:
|
|
85
|
+
GH_TOKEN: ${{ github.token }} # plan:check reads who merged the plan's pull request.
|
|
81
86
|
run: pnpm plan:check
|
|
82
87
|
- name: Check bands
|
|
83
88
|
run: pnpm bands:check
|
|
@@ -34,7 +34,7 @@ The `verifier` subagent starts the app with `pnpm dev`. It exercises the changed
|
|
|
34
34
|
1. **Put unit tests next to the code as `app/**/*.test.{ts,tsx}` and Playwright end-to-end tests in `test/` as `*.e2e.test.ts`.** Vitest collects only the first pattern.
|
|
35
35
|
2. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
|
|
36
36
|
3. **Read configuration from environment variables and fail when one is missing.** Document each variable in `.env.example`.
|
|
37
|
-
4. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`.
|
|
37
|
+
4. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`. The plan, `intent/<change>/plan.md`, gets a pull request of its own, and the engineer accepts it by merging that pull request, as `playbooks/build.md` of the `sdlc-mode` skill in `plugins/sdlc/claude-code/` describes.
|
|
38
38
|
5. **Start a bug fix with a failing test.** A bug fix has no intent and no spec; its plan, `intent/<short-description>/plan.md`, is accepted by an engineer before any code changes. Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it on a `fix/` branch. Then `.agents/hooks/agent-test-edit-gate.sh` blocks test edits by itself: make the test pass without editing it. On a branch with another name, create `.git/bug-fix-in-progress` first, and delete it once the test passes.
|
|
39
39
|
6. **Do not start work a person did not ask for.** When a message reports a problem without asking for work, answer in a few lines and end with the message that would start it: `/sdlc:sdlc-mode` followed by their own words.
|
|
40
40
|
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. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
@@ -62,6 +62,9 @@ jobs:
|
|
|
62
62
|
if: needs.changes.outputs.full_verification == 'true'
|
|
63
63
|
runs-on: ubuntu-latest
|
|
64
64
|
timeout-minutes: 20
|
|
65
|
+
permissions:
|
|
66
|
+
contents: read
|
|
67
|
+
pull-requests: read # Needed by plan:check to read who merged the plan's pull request.
|
|
65
68
|
steps:
|
|
66
69
|
- name: Checkout repository
|
|
67
70
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
@@ -78,6 +81,8 @@ jobs:
|
|
|
78
81
|
- name: Check agent gates
|
|
79
82
|
run: pnpm gates:check
|
|
80
83
|
- name: Check plans
|
|
84
|
+
env:
|
|
85
|
+
GH_TOKEN: ${{ github.token }} # plan:check reads who merged the plan's pull request.
|
|
81
86
|
run: pnpm plan:check
|
|
82
87
|
- name: Check bands
|
|
83
88
|
run: pnpm bands:check
|
|
@@ -33,7 +33,7 @@ The `verifier` subagent starts the app with `pnpm start:dev`. It exercises the c
|
|
|
33
33
|
|
|
34
34
|
1. **Put tests next to the code as `src/**/*.test.ts`.** Vitest collects only that pattern.
|
|
35
35
|
2. **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`.
|
|
36
|
-
3. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`.
|
|
36
|
+
3. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`. The plan, `intent/<change>/plan.md`, gets a pull request of its own, and the engineer accepts it by merging that pull request, as `playbooks/build.md` of the `sdlc-mode` skill in `plugins/sdlc/claude-code/` describes.
|
|
37
37
|
4. **Start a bug fix with a failing test.** A bug fix has no intent and no spec; its plan, `intent/<short-description>/plan.md`, is accepted by an engineer before any code changes. Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it on a `fix/` branch. Then `.agents/hooks/agent-test-edit-gate.sh` blocks test edits by itself: make the test pass without editing it. On a branch with another name, create `.git/bug-fix-in-progress` first, and delete it once the test passes.
|
|
38
38
|
5. **Do not start work a person did not ask for.** When a message reports a problem without asking for work, answer in a few lines and end with the message that would start it: `/sdlc:sdlc-mode` followed by their own words.
|
|
39
39
|
6. **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. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
@@ -79,6 +79,9 @@ jobs:
|
|
|
79
79
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432/payload
|
|
80
80
|
PAYLOAD_SECRET: ci-only-secret
|
|
81
81
|
APP_URL: http://localhost:3000
|
|
82
|
+
permissions:
|
|
83
|
+
contents: read
|
|
84
|
+
pull-requests: read # Needed by plan:check to read who merged the plan's pull request.
|
|
82
85
|
steps:
|
|
83
86
|
- name: Checkout repository
|
|
84
87
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
@@ -95,6 +98,8 @@ jobs:
|
|
|
95
98
|
- name: Check agent gates
|
|
96
99
|
run: pnpm gates:check
|
|
97
100
|
- name: Check plans
|
|
101
|
+
env:
|
|
102
|
+
GH_TOKEN: ${{ github.token }} # plan:check reads who merged the plan's pull request.
|
|
98
103
|
run: pnpm plan:check
|
|
99
104
|
- name: Check bands
|
|
100
105
|
run: pnpm bands:check
|
|
@@ -37,7 +37,7 @@ The `verifier` subagent starts the app with `pnpm dev`, which needs a PostgreSQL
|
|
|
37
37
|
2. **Put unit tests next to the code as `src/**/*.test.{ts,tsx}` and Playwright end-to-end tests in `test/` as `*.e2e.test.ts`.** Vitest collects only the first pattern.
|
|
38
38
|
3. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
|
|
39
39
|
4. **Read configuration through `requireEnvironment` in `src/environment.ts`.** Document each variable in `.env.example`.
|
|
40
|
-
5. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`.
|
|
40
|
+
5. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`. The plan, `intent/<change>/plan.md`, gets a pull request of its own, and the engineer accepts it by merging that pull request, as `playbooks/build.md` of the `sdlc-mode` skill in `plugins/sdlc/claude-code/` describes.
|
|
41
41
|
6. **Start a bug fix with a failing test.** A bug fix has no intent and no spec; its plan, `intent/<short-description>/plan.md`, is accepted by an engineer before any code changes. Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it on a `fix/` branch. Then `.agents/hooks/agent-test-edit-gate.sh` blocks test edits by itself: make the test pass without editing it. On a branch with another name, create `.git/bug-fix-in-progress` first, and delete it once the test passes.
|
|
42
42
|
7. **Do not start work a person did not ask for.** When a message reports a problem without asking for work, answer in a few lines and end with the message that would start it: `/sdlc:sdlc-mode` followed by their own words.
|
|
43
43
|
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. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|
|
@@ -104,6 +104,7 @@ jobs:
|
|
|
104
104
|
permissions:
|
|
105
105
|
contents: read
|
|
106
106
|
id-token: write # Needed to exchange GitHub OIDC for a Turborepo Remote Cache token.
|
|
107
|
+
pull-requests: read # Needed by plan:check to read who merged the plan's pull request.
|
|
107
108
|
services:
|
|
108
109
|
postgres:
|
|
109
110
|
image: postgres:17.11-alpine@sha256:266d3c257594e195b309ae9d1805e54c788f1d7b375f56bf6c367aac6c917e7b
|
|
@@ -156,6 +157,8 @@ jobs:
|
|
|
156
157
|
- name: Check agent gates
|
|
157
158
|
run: pnpm gates:check
|
|
158
159
|
- name: Check plans
|
|
160
|
+
env:
|
|
161
|
+
GH_TOKEN: ${{ github.token }} # plan:check reads who merged the plan's pull request.
|
|
159
162
|
run: pnpm plan:check
|
|
160
163
|
- name: Check bands
|
|
161
164
|
run: pnpm bands:check
|
|
@@ -39,7 +39,7 @@ The `verifier` subagent starts the app with `pnpm dev`, which needs a PostgreSQL
|
|
|
39
39
|
2. **Put unit tests next to the code and Playwright end-to-end tests in `apps/app/test/` and `apps/backoffice/test/` as `*.e2e.test.ts`.** Vitest collects `apps/app/app/**/*.test.{ts,tsx}` and `apps/backoffice/src/**/*.test.{ts,tsx}`.
|
|
40
40
|
3. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
|
|
41
41
|
4. **Read backoffice configuration through `requireEnvironment` in `apps/backoffice/src/environment.ts`.** Document each variable in `.env.example`.
|
|
42
|
-
5. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`.
|
|
42
|
+
5. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`. The plan, `intent/<change>/plan.md`, gets a pull request of its own, and the engineer accepts it by merging that pull request, as `playbooks/build.md` of the `sdlc-mode` skill in `plugins/sdlc/claude-code/` describes.
|
|
43
43
|
6. **Start a bug fix with a failing test.** A bug fix has no intent and no spec; its plan, `intent/<short-description>/plan.md`, is accepted by an engineer before any code changes. Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it on a `fix/` branch. Then `.agents/hooks/agent-test-edit-gate.sh` blocks test edits by itself: make the test pass without editing it. On a branch with another name, create `.git/bug-fix-in-progress` first, and delete it once the test passes.
|
|
44
44
|
7. **Do not start work a person did not ask for.** When a message reports a problem without asking for work, answer in a few lines and end with the message that would start it: `/sdlc:sdlc-mode` followed by their own words.
|
|
45
45
|
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. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
|