create-afe-project 0.0.0-stage → 2.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +77 -2
  4. package/bin/create-afe-project.js +123 -0
  5. package/package.json +43 -4
  6. package/src/answers.js +147 -0
  7. package/src/config.js +151 -0
  8. package/src/project.js +217 -0
  9. package/src/render.js +51 -0
  10. package/template/.claude/harness.conf +50 -0
  11. package/template/.claude/hooks/post-edit-check.sh +41 -0
  12. package/template/.claude/hooks/protect-docs.sh +315 -0
  13. package/template/.claude/hooks/session-start.sh +81 -0
  14. package/template/.claude/hooks/stop-check-decisions.sh +25 -0
  15. package/template/.claude/hooks/stop-check-docs.sh +29 -0
  16. package/template/.claude/hooks/stop-check-fast.sh +33 -0
  17. package/template/.claude/hooks/stop-check-template.sh +23 -0
  18. package/template/.claude/hooks/stop-run-project-hooks.sh +27 -0
  19. package/template/.claude/path-classes +102 -0
  20. package/template/.claude/settings.project.json +5 -0
  21. package/template/.claude/settings.template.json +92 -0
  22. package/template/.claude/skills/afe-init/SKILL.md +104 -0
  23. package/template/.claude/skills/create-pr/SKILL.md.jinja +110 -0
  24. package/template/.claude/skills/update-template/SKILL.md +40 -0
  25. package/template/.claude/skills/{% if code_review %}review-code{% endif %}/SKILL.md.jinja +50 -0
  26. package/template/.claude/skills/{% if docs_review %}docs-review{% endif %}/SKILL.md +60 -0
  27. package/template/.claude/skills/{% if dual_review %}dual-review{% endif %}/SKILL.md +58 -0
  28. package/template/.github/workflows/decisions.yml.jinja +57 -0
  29. package/template/.github/workflows/docs.yml.jinja +40 -0
  30. package/template/.github/workflows/{% if code_review %}code-review.yml{% endif %}.jinja +54 -0
  31. package/template/.github/workflows/{% if docs_review %}docs-review.yml{% endif %}.jinja +52 -0
  32. package/template/.github/workflows/{% if fail_without_change %}fail-without-change.yml{% endif %}.jinja +42 -0
  33. package/template/.github/workflows/{% if secret_scan %}secret-scan.yml{% endif %}.jinja +47 -0
  34. package/template/.markdownlint-cli2.jsonc +27 -0
  35. package/template/.template/owned.txt.jinja +65 -0
  36. package/template/AGENTS.md.jinja +91 -0
  37. package/template/CLAUDE.md +3 -0
  38. package/template/README.md.jinja +53 -0
  39. package/template/_gitignore +14 -0
  40. package/template/docs/README.md.jinja +44 -0
  41. package/template/docs/contributing.md.jinja +206 -0
  42. package/template/docs/decisions/adr-template.md +38 -0
  43. package/template/docs/documentation-rules.md.jinja +200 -0
  44. package/template/docs/testing-rules.md +80 -0
  45. package/template/scripts/check-decisions.sh +382 -0
  46. package/template/scripts/check-docs.sh +20 -0
  47. package/template/scripts/check-links.mjs +142 -0
  48. package/template/scripts/check-project.sh +27 -0
  49. package/template/scripts/check-template.sh +88 -0
  50. package/template/scripts/check.sh +21 -0
  51. package/template/scripts/env.sh +29 -0
  52. package/template/scripts/gen-decisions-index.sh +57 -0
  53. package/template/scripts/gen-settings.sh +14 -0
  54. package/template/scripts/lib/classes.sh +89 -0
  55. package/template/scripts/lib/git-base.sh +25 -0
  56. package/template/scripts/lib/harness-conf.sh +48 -0
  57. package/template/scripts/lib/json.mjs +160 -0
  58. package/template/scripts/pr-metrics.sh +80 -0
  59. package/template/scripts/protect-branch.sh +101 -0
  60. package/template/scripts/{% if acceptance_check %}check-acceptance.sh{% endif %} +47 -0
  61. package/template/scripts/{% if fail_without_change %}fail-without-change.sh{% endif %} +88 -0
@@ -0,0 +1,206 @@
1
+ # Contribution rules
2
+
3
+ Branches, commits, pull requests, when a change is done, template updates, and review.
4
+ `AGENTS.md` takes precedence; this file comes from the template, so report a disagreement there.
5
+ Everything written into git history and pull requests is in English, without exception.
6
+
7
+ ## 1. Branches
8
+
9
+ - `main` on the remote `origin` (`git remote set-head origin -a`) is the only long-lived branch
10
+ and the base of every pull request; nothing but the template's initial commit goes to it directly.
11
+ - A working branch is named `<type>/<slug>`: `docs` for documentation, decisions, requirements,
12
+ and plans; `ci` for checks, hooks, workflows, and skills; `feat` for new behaviour; `fix` for a
13
+ defect; `chore` for tooling and housekeeping. The slug names the change in two to five
14
+ lowercase words joined by `-`, such as `ci/check-script-timeout`; no ticket numbers.
15
+ - One branch carries one logical change and ends in one pull request: one mechanism with its
16
+ tests and the owner documents it changes (one check, one hook, one rule, one behaviour). A rule
17
+ change and the first code that follows it are two changes (`AGENTS.md`, section 5); readiness
18
+ warns above `DIFF_WARN_LINES` changed lines. An exception needs the owner's instruction, named
19
+ under "Notes for the owner".
20
+ - Each parallel session works in its own git worktree (`git worktree add`) on its own branch. A
21
+ session that stops unfinished leaves a checkpoint: its commits pushed, and its progress and
22
+ blockers in the plan or the pull request description, where the next session starts.
23
+ - A branch that a tool named otherwise (`claude/<name>`) is renamed before its first push; a
24
+ pushed one keeps its name, noted under "Notes for the owner". After the merge GitHub deletes it, a repository
25
+ setting that `scripts/protect-branch.sh` applies.
26
+
27
+ ## 2. Commits
28
+
29
+ - The subject line is `<Scope>: <summary>`, at most 72 characters.
30
+ - `Scope` names what the commit changes, as it is written elsewhere: an owner document or its
31
+ area (`Requirements`, `Rules`, `Map`, `Plan`, `Contributing`), a decision (`ADR-0001`), a
32
+ workflow or skill (`create-pr`). Several scopes are separated by commas.
33
+ - `summary` states what the commit does or what is now true, in the present tense, lowercase,
34
+ without a trailing period: `apply review fixes`, `requirements section now exists`.
35
+ - The body, after a blank line and wrapped at 72 characters, says why when the subject does not,
36
+ and names the documents affected, the statuses changed and who set them, and related pull
37
+ requests (`PR #2`). It never calls something implemented without a test.
38
+ - One commit per logical change. Work-in-progress and fix-up commits are squashed before the
39
+ branch is pushed. A follow-up that answers review is its own commit.
40
+ - A commit authored by an agent ends with the co-author trailer its tool prescribes; for Claude
41
+ Code, a `Co-Authored-By:` line naming the model.
42
+
43
+ ## 3. Pull requests and done
44
+
45
+ - One pull request per branch, against `main`; a draft while the work is unfinished or a check
46
+ other than merge readiness fails. The title is the branch's commit subject, or one subject in
47
+ the same form for several commits.
48
+ - The description is the report of `AGENTS.md`, section 6: the evidence that the change is done.
49
+ It has these sections in this order; "Owner decisions" and "Gate files" appear when they have
50
+ an item, "Notes for the owner" is optional, and an empty section reads "None".
51
+
52
+ ```markdown
53
+ ## Summary
54
+
55
+ - What changed and why, one bullet per owner document or area, naming the files; each new
56
+ statement with its status (`proposed`, `accepted by the owner`, `implemented` with its test).
57
+
58
+ ## Acceptance and test plan
59
+
60
+ - One bullet per identifier the change serves (`REQ-NNN`, `AC-NN`, or an identifier in the owner
61
+ document of a check): its cases, the test files in backticks and the tests that carry it, and
62
+ the evidence: failed on the base and why, passes now. "None: <reason>" for no behaviour change.
63
+
64
+ ## Checks
65
+
66
+ - `scripts/check.sh` passes; or, for each failing part, the command and the lines of its output
67
+ that show the failure, so that the next session can resume from them.
68
+ - Semantic self-review (`docs/documentation-rules.md`, section 7): what was checked, the result.
69
+ - The review before the pull request (section 6): its reviewers and their models, or why none.
70
+
71
+ ## Assumptions and open questions
72
+
73
+ - None, or one bullet each, labelled `blocking:`, `recorded:`, `assumption:`, or `limit:`.
74
+
75
+ ## Owner decisions
76
+
77
+ - Each `Decided by the owner` marker the branch adds: the file, the statement, and the owner's
78
+ instruction, quoted with its date.
79
+
80
+ ## Gate files
81
+
82
+ - Each file of class gate the branch touches, as `scripts/check-decisions.sh --ready` lists them.
83
+
84
+ ## Metrics
85
+
86
+ | Sensor | Class | Found | Fixed | Limit | Declined |
87
+ | --- | --- | --- | --- | --- | --- |
88
+ | check.sh | blocker | 0 | 0 | 0 | 0 |
89
+
90
+ | Measure | Value |
91
+ | --- | --- |
92
+ | First check.sh | pass |
93
+ | Ralph iterations | 1 |
94
+ | Santa rounds | 0 |
95
+ | Owner interventions | 0 |
96
+ | Files outside the scope | 0 |
97
+ | Regression of | none |
98
+
99
+ ## Notes for the owner
100
+
101
+ - Optional: protected files touched and the instruction that allowed it, alternatives not taken.
102
+ ```
103
+
104
+ - An agent's pull request ends with the attribution line its tool prescribes; for Claude Code,
105
+ `🤖 Generated with [Claude Code](https://claude.com/claude-code)`.
106
+ - The labels of "Assumptions and open questions":
107
+ - `blocking:` a question about the pull request itself (its content, a status, a decision it
108
+ needs), resolved before the merge. A review finding is one only in a Santa loop (section 6).
109
+ - `recorded:` a question about the subject matter, kept with an identifier in its owner
110
+ document, which the item names (`recorded: OQ-1 in docs/requirements/<document>.md`).
111
+ - `assumption:` an assumption the change rests on, also labelled where it is used.
112
+ - `limit:` a suggestion of a review that the branch does not fix, with its sensor and reason;
113
+ for a check, its owner document now names the forms it catches. The merge accepts it.
114
+ - A change is done when each identifier it serves has a test that carries it, each new or changed
115
+ test failed on the base and passes now, `scripts/check.sh` passes and no measure the project
116
+ records falls below the base, no `blocker` is open, and the readiness check passes. The agent
117
+ derives the test plan from the identifiers (`testing-rules.md`, section 3); it is a report,
118
+ never approved per pull request and not maintained afterwards.{% if acceptance_check %} `scripts/check-acceptance.sh`
119
+ checks the first condition by identifier.{% endif %}{% if fail_without_change %} `scripts/fail-without-change.sh` checks per file that each
120
+ new or changed test passes with the change and fails on the base; `tests-pass-on-base` allows a pass.{% endif %}
121
+ - Ralph loop: the author runs `scripts/check.sh`, fixes, and runs it again until it passes, at
122
+ most `RALPH_MAX_ITERATIONS` runs (`.claude/harness.conf`, default 5). At the cap the author
123
+ stops and reports the failing command and its output. Metrics count the runs.
124
+ - An agent writes a `Decided by the owner` marker only on the owner's instruction and lists each
125
+ one it adds under "Owner decisions"; the owner's merge confirms them.
126
+ - Metrics measure the sensors, so that a rule or check that never fires is removed;
127
+ `scripts/pr-metrics.sh` aggregates merged pull requests, with the time to merge. A sensor is a
128
+ check or a reviewer; an owner intervention is anything the owner answered, approved, or
129
+ corrected besides the merge; "Regression of" names the pull request whose behaviour this repairs.
130
+ - Ready to merge: `scripts/check-decisions.sh --ready` and every other check pass, no `blocker`
131
+ is open, every finding is answered. Readiness fails on a missing "Acceptance and test plan" or
132
+ "Metrics" section, a missing or repeated measure; an added record still `proposed`; an
133
+ open `blocking:` item or one without a label; an Acceptance item that is neither "None" with a
134
+ reason nor names an existing path in backticks; an added marker in a Markdown file or a touched
135
+ gate file that its section does not name; a Metrics table outside its forms, whose answers do
136
+ not add up, or with a blocker as a limit.{% if acceptance_check %} It runs `scripts/check-acceptance.sh` too.{% endif %} Whether a test proves
137
+ its identifier is left to the review. The `decisions` workflow runs it on every change, on the records
138
+ only for a pull request that `github-actions[bot]` opened, and does not block; one waiting only on the owner stays open as "not ready to merge".
139
+ - The owner merges with a merge commit; `scripts/protect-branch.sh` allows no other merge method.
140
+
141
+ ## 4. Before opening a pull request
142
+
143
+ Sections 1 to 3 hold, the checks pass or their failures are stated, and every affected owner
144
+ document is updated in the same branch. The `create-pr` skill runs this and opens it.
145
+
146
+ ## 5. Template updates
147
+
148
+ The template-owned files (`documentation-rules.md`, section 9) change only through an update to a
149
+ template version, which `.afe-answers.json` records; the `update-template` skill runs it:
150
+
151
+ 1. From an up-to-date `main`, on the branch `chore/template-update`: `npx create-afe-project@latest
152
+ update --defaults`, or `@<version>`. It replaces the template-owned files, keeps the project
153
+ block of `AGENTS.md`, never changes an existing seeded file, and regenerates generated files.
154
+ 2. Review its report: a replaced local edit of a template-owned file is lost unless it moves to
155
+ the template; project files are untouched. An update changes seeded files only by a migration
156
+ the changelog names.
157
+ 3. `scripts/check.sh`, then a pull request `Template: update to <version>` with the changelog
158
+ entries since the previous version under "Summary". A change the project needs in a
159
+ template-owned file is made in the template first.
160
+
161
+ ## 6. Review of pull requests
162
+
163
+ - AI review is a sensor, not a gate: the checks decide by themselves, and a reviewer judges what
164
+ they cannot. Reviewers propose; the owner decides. The review follows the highest class of the
165
+ changed paths (`documentation-rules.md`, section 8), as `scripts/check-decisions.sh --classes`
166
+ prints it. Before the pull request the {% if dual_review %}`dual-review`{% else %}`create-pr`{% endif %} skill runs it:
167
+ - low (documents, generated files, a template update): no reviewer;
168
+ - normal and gate: one pass by one reviewer, {% if dual_review %}the Codex CLI when it is installed, otherwise {% endif %}a fresh
169
+ Claude subagent; only the fix of a blocker is checked again;
170
+ {%- if dual_review %}
171
+ - high: the Santa loop: a Claude subagent and the Codex CLI (without Codex, a second fresh
172
+ Claude subagent with another lens) review independently; the author fixes the blockers and both
173
+ review again, until neither finds one or after `REVIEW_MAX_ROUNDS` rounds (`.claude/harness.conf`,
174
+ 3 to 5): the open blockers go to the owner as `blocking:` items.
175
+ {%- else %}
176
+ - high: one pass as for normal; the Santa loop is an optional module of the template.
177
+ {%- endif %}
178
+ {%- if docs_review or code_review %}
179
+ - In the pull request, {% if docs_review %}`docs-review` reviews every pull request{% endif %}{% if docs_review and code_review %} and {% endif %}{% if code_review %}`code-review` every one that changes code, contracts, checks, or workflows{% endif %}, once, when it
180
+ opens or leaves draft, except one that `github-actions[bot]` opened; a push does not run {% if docs_review and code_review %}them{% else %}it{% endif %} again.
181
+ {%- endif %}
182
+ - A finding names a file, a line, a class, and the rule it rests on; zero findings is valid, and
183
+ style belongs to the linters. A `blocker`: merged as it is, the change breaks behaviour, a
184
+ contract, security, or a product invariant (`docs/invariants.md`), loses data, fails an item of
185
+ its Acceptance section, or contradicts a higher source (`AGENTS.md`, section 1). Which bypass of
186
+ a gate is a blocker follows `testing-rules.md`, section 4. Any other finding is a `suggestion`.
187
+ - Each finding is answered once. A blocker is fixed, and the fix checked once more by a fresh
188
+ reviewer given the blocker and the fix, or declined with a reason. A suggestion never drives
189
+ a loop: it is fixed at once, recorded as `limit:` (section 3), or declined with a reason.
190
+ - The project adds its own criteria in one owner document that the map names under "Review
191
+ criteria"; every reviewer applies both.
192
+
193
+ | Criterion | Passes when |
194
+ | --- | --- |
195
+ | Authority | every behaviour serves a named requirement or criterion, or is an open question; nothing is invented (`AGENTS.md`, section 4) |
196
+ | Co-change | contract, code, tests, and owner document change together; a breaking contract change is versioned; generated files are regenerated |
197
+ | Correctness | the logic does what the requirement says, with the edge cases that apply |
198
+ | Tests | the tests follow `docs/testing-rules.md`, and every identifier of "Acceptance and test plan" has a test that carries it and fails without the change |
199
+ | Errors | no error is swallowed, no failure hides behind a default |
200
+ | Security | external input is validated where it enters; secrets never reach code, logs, or output |
201
+ | Dependencies | a new dependency exists in its registry under the name used, is maintained, has a license the project allows (an open question while no document says which), and does no job an existing dependency already does; its version is pinned exactly, through the lock file where the stack has one |
202
+ | Statuses | nothing is called implemented without a test (`documentation-rules.md`, section 3) |
203
+
204
+ - A defect found after the merge is fixed after a regression test; its pull request gives the
205
+ evidence pack (the reproduction, the root cause, the regression test failing then passing) and
206
+ names what keeps the next one out: a criterion, a check, a rule, or why none fits.
@@ -0,0 +1,38 @@
1
+ ---
2
+ status: proposed
3
+ date: YYYY-MM-DD
4
+ decision-makers: {who decides}
5
+ scope: {stack | architecture | data | security | operations | process}
6
+ summary: "In the context of {situation}, facing {concern}, we decided {chosen option} and rejected {alternatives}, to achieve {quality}, accepting {downside}."
7
+ supersedes: []
8
+ ---
9
+
10
+ # {the decision as a short statement}
11
+
12
+ ## Context and Problem Statement
13
+
14
+ {Two to five sentences, or a question: the problem, the forces, and the scope of the decision.
15
+ Link the requirements and documents involved instead of copying them. Only for a decision that
16
+ meets `docs/documentation-rules.md`, section 5; keep the whole record to about one page.}
17
+
18
+ ## Considered Options
19
+
20
+ * {chosen option}
21
+ * {real alternative}
22
+
23
+ ## Decision Outcome
24
+
25
+ Chosen option: "{chosen option}", because {justification}. {Rules, parameters, and design details
26
+ belong in their owner document; link it here instead of listing them.}
27
+
28
+ Rejected: {each alternative, with the reason in one clause}.
29
+
30
+ ### Consequences
31
+
32
+ * Good, because {positive consequence}
33
+ * Bad, because {negative consequence}
34
+
35
+ ### Confirmation
36
+
37
+ {The check that verifies the decision: a script, a test, a contract check, or a review step. Or
38
+ "Not checked yet:" followed by what will check it and when.}
@@ -0,0 +1,200 @@
1
+ # Documentation rules
2
+
3
+ Reference for creating, changing, and retiring documentation. `AGENTS.md` holds the short rules
4
+ and takes precedence; this file comes from the template, so report a disagreement to the
5
+ template (section 8). It has at most 200 lines (checked): an addition pays with a removal.
6
+
7
+ ## 1. Principles
8
+
9
+ - Docs as code: documentation lives here, in Markdown, is reviewed like code, and is checked
10
+ automatically (section 7).
11
+ - Progressive disclosure: entry files (`AGENTS.md`, `docs/README.md`) stay short and link out.
12
+ - One owner per fact: `docs/README.md` maps kinds of facts to owner documents; others link, never
13
+ copy. Generated artefacts come from a script, are checked in CI, and are never hand-edited.
14
+ - Content before structure: a section exists only with real content; future ones are listed in
15
+ the map, not created as placeholders. Diátaxis is a lens for content type, not a folder tree.
16
+
17
+ ## 2. Document kinds and formats
18
+
19
+ | Kind | Location | Format |
20
+ | --- | --- | --- |
21
+ | Agent rules | `AGENTS.md` | at most 90 template lines plus a project block of at most 10 lines (checked); numbered sections; `CLAUDE.md` imports it with `@AGENTS.md` |
22
+ | Documentation map | `docs/README.md` | tables of existing and planned sections |
23
+ | Testing rules | `docs/testing-rules.md` | at most 80 lines (checked); numbered sections: what a test is for, how it proves behaviour, tests and the change, gates; the project's test levels, tools, and edge cases live in its own documents |
24
+ | Contribution rules | `docs/contributing.md` | at most 200 lines (checked); numbered sections: branches, commits, pull requests and when a change is done, template updates, review |
25
+ | Decision (ADR) | `docs/decisions/NNNN-title-with-dashes.md` | only for a decision that meets section 5; short MADR 4.0 from `adr-template.md`, about one page, details linked to their owner document; front matter `status`, `date`, `decision-makers`, `scope`, `summary`, optional `supersedes` |
26
+ | Decision index | `docs/decisions/README.md` | generated by `scripts/gen-decisions-index.sh` from the front matter of the records; never hand-edited |
27
+ | Plan | `docs/plans/<slug>.md` | front matter `status`, `owner`, `created`, `review-by`; sections Goal, Steps, Open questions |
28
+ | Requirement | `docs/requirements/` | one `REQ-NNN` per statement; RFC 2119 keywords; fields `status`, `source`, `verification`; its acceptance criteria `AC-NN` as Given, When, Then, with the check that establishes each |
29
+ | Invariants register | `docs/invariants.md` | one `INV-NN` per invariant that must hold everywhere (`testing-rules.md`, section 4); fields `statement`, `source` (the requirement or owner decision), `enforced-by` (the test or project check that fails when it breaks, or `none`, which is an open question) |
30
+ | Architecture | `docs/architecture/` | the system map: components, domain ownership, public interfaces and the dependency rules a project check enforces; explanation and reference; front matter `status`, `date`, `verified-against` (commit, or `none` while no code exists) |
31
+ | Contract | `contracts/` | machine-readable, for example OpenAPI or JSON Schema; the file is the contract; prose links to it |
32
+ | Operations | `docs/operations/` | how-to; every procedure names the command or file it relies on; front matter as for architecture |
33
+ | Conventions and recipes | `docs/conventions/<area>.md`, `docs/recipes/<task>.md` | conventions: reference, the rules of one area that the code does not make obvious, each naming the lint rule or check that enforces it, or `none` when only the review does; component boundaries and dependency rules stay in architecture; recipes: how-to, the steps of one repeated development task, each naming the command or file it relies on, while running the system stays in operations; a tool-specific rule file or skill links to them and never holds its own copy; front matter as for architecture |
34
+
35
+ This is the fixed set of document kinds the template defines; another project document, such as
36
+ a changelog, exists only when the owner approves it and the map lists it. Front matter is YAML
37
+ between `---` lines at the top of the file.
38
+
39
+ ## 3. Status vocabulary
40
+
41
+ | Document | Allowed `status` values and transitions | Transition set by |
42
+ | --- | --- | --- |
43
+ | ADR | `proposed` to `accepted` or `rejected`; `accepted` to `deprecated` or `superseded by ADR-NNNN` | owner; agents create `proposed` |
44
+ | Requirement | `draft` to `approved` to `implemented` or `retired` | owner; `implemented` needs a linked test |
45
+ | Plan | `active`, then the file is deleted when done or abandoned | the change that completes or abandons it |
46
+ | Architecture, operations, conventions, recipes | `draft` to `current`; `current` with a stale `verified-against` is suspect | the reviewer who verified it against code |
47
+ | Owner decision in an owner document | a statement carries its document's status until the owner decides it; then it ends with, or its section opens with, `Decided by the owner, YYYY-MM-DD.` | owner; an agent adds the marker only on the owner's explicit instruction and lists it in the pull request (`contributing.md`, section 3); the owner's merge confirms it |
48
+
49
+ Wording rules for behaviour:
50
+
51
+ - "X does Y" only for implemented behaviour, merged code with a test; link the test or module
52
+ when the claim matters. Requirements use RFC 2119 keywords and never claim to be implemented.
53
+ Proposals say "proposed", accepted decisions "accepted" or "decided", unknown things "not
54
+ decided" or "unknown", never guessed.
55
+ - A statement about a check (a lint rule, a test check, a hook, a workflow) names the forms it
56
+ catches and what is left to the review; an absolute claim about a check needs a test for each
57
+ form it covers.
58
+
59
+ ## 4. Currency
60
+
61
+ A document is current when its content agrees with its owner source (code, contract, decision).
62
+ `date` records the last content change; `verified-against: <commit>`, or `none` while no code
63
+ exists, the last review against code: architecture, operations, conventions, and recipes carry it.
64
+ Touching a file or bumping a date is not verification.
65
+
66
+ ## 5. Lifecycle
67
+
68
+ - Create a document when its first real content exists. A new section or plan needs the owner's
69
+ approval, and the map moves the section from planned to existing in the same change. An agent
70
+ may create an ADR with `status: proposed`; the owner's acceptance is the approval.
71
+ - A new ADR only for a decision that is costly to reverse once code or data depend on it and had
72
+ a real alternative: language, runtime, or framework; storage and persistent data formats;
73
+ public protocols and contract formats; the security, isolation, or authentication model; an
74
+ external service or infrastructure. Copy `adr-template.md`, take the highest number plus one,
75
+ set `status: proposed`, record one decision in about one page, link the owner documents, and
76
+ regenerate the index: `scripts/gen-decisions-index.sh > docs/decisions/README.md`.
77
+ - Other owner decisions are edits of the owner document with the marker of section 3; a decision
78
+ about a template-owned file goes to the template. Process rules live in the rule documents and
79
+ their checks; implementation choices stay in the pull request and the commit messages.
80
+ - A plan only for work that spans sessions or needs owner follow-up; the change that completes or
81
+ abandons it deletes the file. A plan past `review-by` is updated or deleted in the next change
82
+ to `docs/`, and agents flag it.
83
+ - Update the owner document in the same change that makes it wrong; refactoring that leaves
84
+ behaviour, contracts, and operations unchanged needs none. Update links and the map.
85
+ - An ADR on `main` changes only its `status` line along section 3. A `proposed` ADR that needs
86
+ other wording is `rejected` and replaced; an ADR is never deleted or renamed. `superseded by
87
+ ADR-NNNN` names an ADR the same change adds, which lists the records it replaces in
88
+ `supersedes`. Requirements are retired with a reason and a link to their replacement.
89
+ - There is no archive directory: a retired document stays with its retired status. Temporary
90
+ material (research notes, logs, transcripts, agent reports) never enters `docs/`; a person
91
+ extracts durable facts into the owner document.
92
+
93
+ ## 6. Alignment of documentation, contracts, and code
94
+
95
+ These rules apply once code exists; they are fixed now so the first code follows them.
96
+
97
+ - Contracts are machine-readable files in `contracts/`. The file is the source of truth for the
98
+ interface. The implementation conforms to it, and prose documentation links to it. Contract
99
+ linting and breaking-change detection are added to CI with the first contract, as decided in
100
+ an ADR together with the contract format.
101
+ - Implemented behaviour is proven by tests that follow `docs/testing-rules.md`. Documentation
102
+ that describes behaviour links to the test or module. A described behaviour without a test is
103
+ `proposed`, not `implemented`.
104
+ - CI runs the documentation checks, the contract checks, and the tests on every pull request.
105
+ - The documents of section 4 carry `verified-against`; a review against code updates it.
106
+ - With the first code: the project adds its code checks to `scripts/checks.d/` (section 9), and
107
+ `docs/README.md` moves "implemented behaviour" to existing.
108
+
109
+ ## 7. Automated checks and semantic review
110
+
111
+ | Concern | Automated (`scripts/check.sh`, CI) | Semantic review, per change |
112
+ | --- | --- | --- |
113
+ | Markdown syntax and structure | markdownlint-cli2 | none |
114
+ | Broken internal links and anchors | `scripts/check-links.mjs`, offline, with anchors | none |
115
+ | Secrets in a change | {% if secret_scan %}gitleaks in the `secret-scan` workflow; {% endif %}the protect hook asks when an edit contains a known token format | none |
116
+ | External links | not checked automatically | author verifies when adding; spot check on review |
117
+ | Status correctness | none | status matches the evidence (section 3) |
118
+ | Invented facts | none | every new fact has a source or is marked unknown |
119
+ | Duplication | none | new content links instead of copying |
120
+ | Currency | the SessionStart hook flags each plan past its `review-by` (section 5), with the branch, its base, and the uncommitted files | `verified-against` and the map are updated |
121
+ | Documentation updated together with code | {% if docs_review %}`docs-review` workflow on pull requests, one pass, a sensor{% else %}none{% endif %} | reviewer confirms the owner document changed |
122
+ | ADRs on `main` change only their status line (section 5); new ADRs carry `scope` and `summary`; the decision index matches the records | `scripts/check-decisions.sh`: Stop hook and `decisions` workflow | none |
123
+ | A new ADR meets the criteria of section 5 | none | {% if docs_review %}`docs-review` reports it; {% endif %}the owner decides |
124
+ | Template-owned files unchanged; one project block in `AGENTS.md`; the line budgets of the rule documents; `.claude/settings.json` generated, and every hook command it names exists (sections 2 and 9) | `scripts/check-template.sh`: Stop hook and `docs` workflow | none |
125
+ | Each invariant names its check (section 2) | none | the review checks `docs/invariants.md` against the checks it names |
126
+ | Project checks (section 9) | `scripts/check-project.sh` runs `scripts/checks.d/*.sh`: `scripts/check.sh` and `docs` workflow; in Claude Code, `CHECK_FAST` of `.claude/harness.conf` at a Stop when a file other than Markdown differs from the base, skipped when empty or the default `scripts/check-docs.sh` | as the project defines; what `CHECK_FAST` leaves out waits for `scripts/check.sh` |
127
+ | Code and tests meet the review rubric and the testing rules (`contributing.md`, section 6; `testing-rules.md`) | the project's test checks in `scripts/checks.d/`{% if fail_without_change %}; `scripts/fail-without-change.sh`{% endif %}{% if code_review %}; `code-review` workflow, a sensor{% endif %} | reviewer applies the rubric, by the class of the paths |
128
+ | Merge readiness (`contributing.md`, section 3) | `scripts/check-decisions.sh --ready`, in the forms that section names{% if acceptance_check %}, with `scripts/check-acceptance.sh`{% endif %}; `decisions` workflow; signals, does not block | whether the tests prove their identifiers; no `blocker` open; the owner's merge confirms the listed markers |
129
+ | Measurement of the sensors (`contributing.md`, section 3) | `scripts/pr-metrics.sh` aggregates the Metrics of merged pull requests | the owner removes a rule or check that never fires |
130
+ | Finishing with failing checks | Stop hooks in Claude Code: the documentation, decision, and template checks, `CHECK_FAST`, and the project's Stop hooks; a failing check blocks up to the Ralph cap (`contributing.md`, section 3) | none; the full `scripts/check.sh` runs before the pull request and in CI |
131
+ | Class approval and irreversible actions (section 8) | protect hook: owner prompt in Claude Code, denied headless; deny rules of `.claude/settings.json` | explicit, separate, owner-approved |
132
+
133
+ Semantic review checklist for any documentation change:
134
+
135
+ 1. Each new statement has a status, and the status matches the evidence; a statement about a
136
+ check names the forms it catches and what is left to the review, and an absolute claim about
137
+ a check has a test for each form it covers (section 3).
138
+ 2. No requirement, default, or decision was invented; gaps are listed as open questions.
139
+ 3. No content duplicates another document; links point to the owner.
140
+ 4. The map (`docs/README.md`) and all links are updated; `scripts/check.sh` passes.
141
+ 5. ADRs that exist on `main` (apart from their status line), template-owned files, the
142
+ documentation map, and the structure are unchanged unless the change is explicitly about them.
143
+ 6. A new ADR meets the criteria of section 5 and links details instead of listing them; every
144
+ new `Decided by the owner` marker is listed in the pull request with the owner's instruction,
145
+ quoted with its date (`contributing.md`, section 3).
146
+
147
+ ## 8. Path classes, access, and changing these rules
148
+
149
+ Governance follows risk and reversibility: every path a change touches takes the highest class
150
+ that applies, from the template's built-in rules and the project's `.claude/path-classes`
151
+ (section 9); `scripts/check-decisions.sh --classes` prints them and the review they need.
152
+
153
+ | Class | Paths (defaults; the project adds its own) | Before the edit | Review before the pull request (`contributing.md`, section 6) |
154
+ | --- | --- | --- | --- |
155
+ | low | Markdown outside the rules, the decision index, a template update | nothing | none |
156
+ | normal | code, tests, scripts: every path no other class takes | nothing | one reviewer |
157
+ | gate | workflows, project checks and Stop hooks, `.claude/harness.conf`, dependency manifests and lock files, linter, test, coverage, and mutation configuration | nothing; listed under "Gate files", reviewed by the owner after the merge | one reviewer |
158
+ | high | security, secrets, access and authentication, data migrations, money and billing, public contracts (`contracts/`) | nothing | {% if dual_review %}Santa loop{% else %}one reviewer; the Santa loop comes with the review module{% endif %} |
159
+ | approval | `AGENTS.md`, the template-owned files, the generated manifest, answers file, and `.claude/settings.json`, the map, `.claude/path-classes`, `.claude/settings.project.json`, `.mcp.json`, credentials, ADRs on `main` or not `proposed` | the protect hook asks the owner; a headless run is denied | by the class of the content |
160
+
161
+ The protect hook (`.claude/hooks/protect-docs.sh`) asks on an Edit, Write, or MultiEdit call and
162
+ on a shell command of a form its comments name (a redirect, `cp`, `mv`, `tee`, `rm`, `sed -i`,
163
+ and the like), and on the irreversible commands it names: `git push` that forces, deletes,
164
+ mirrors, prunes, or pushes tags (a release); `gh pr merge`; `gh release create` and `delete`;
165
+ `gh repo delete`. A side effect of a tool, a path or command named only inside a script or
166
+ behind a variable, an alias, or a directory a script changes is not caught and is left to the
167
+ review of the diff.
168
+
169
+ Access control comes before prompts: an agent gets no credentials or tools for irreversible or
170
+ production actions outside its task. `.claude/settings.json` denies force pushes and deletions
171
+ of remote branches in the forms its rules spell out, and reading `.env`, `.env.*`, `*.pem`, and
172
+ `*.key`; in `.claude/settings.project.json` the project adds deny rules, for example for the
173
+ destructive tools of an MCP server (`mcp__<server>__<tool>`), and takes a template entry out
174
+ with `"remove"`. For scripted, owner-driven changes the owner starts Claude Code
175
+ with `DOCS_GUARD=off`, which also lifts the Stop hooks for decisions and template integrity.
176
+ Other agents are bound by `AGENTS.md` and the review only.
177
+
178
+ Template-owned files (section 9), including this file and `AGENTS.md` outside its project block,
179
+ change only in the template and arrive through an update (`contributing.md`, section 5). A
180
+ change to the map or the structure is a dedicated change approved by the owner.
181
+
182
+ ## 9. Ownership of files
183
+
184
+ Every file belongs to exactly one class:
185
+
186
+ | Class | Files | Changed by |
187
+ | --- | --- | --- |
188
+ | Template-owned | the files listed with their hashes in `.template/owned.sha256` (rules, ADR template, checks, hooks, skills, workflows, linter configuration), and `AGENTS.md` outside its project block | a release of the template, applied with `npx create-afe-project@latest update` (`contributing.md`, section 5); never edited in this repository |
189
+ | Seeded | `README.md`, `CLAUDE.md`, `.gitignore`, `docs/README.md`, `.claude/path-classes`, `.claude/harness.conf`, `.claude/settings.project.json`, and the project block of `AGENTS.md`: created once by the template | the project |
190
+ | Generated | `docs/decisions/README.md`, `.template/owned.sha256`, `.afe-answers.json`, `.claude/settings.json` (by `scripts/gen-settings.sh` from `.claude/settings.template.json` and `.claude/settings.project.json`) | their generator only |
191
+ | Project | everything else, including the documentation sections of section 2 and the extension points below | the project |
192
+
193
+ The project extends the template only at its extension points: checks in `scripts/checks.d/*.sh`,
194
+ run in name order by `scripts/check.sh` and the `docs` workflow, which provision any tool they
195
+ need beyond Git, bash, and Node.js; Stop hooks in `.claude/hooks/project/stop/*.sh`, which
196
+ receive the Stop hook input and block the agent by exiting with 2; its own workflows in
197
+ `.github/workflows/`; its path classes in `.claude/path-classes`; its harness settings in
198
+ `.claude/harness.conf` (the Ralph cap, the identifier pattern, test globs, test commands); and
199
+ its Claude Code settings in `.claude/settings.project.json`. Checks and Stop hooks keep their
200
+ executable bit. Personal settings go into `.claude/settings.local.json`, never committed.
@@ -0,0 +1,80 @@
1
+ # Testing rules
2
+
3
+ Rules that every test in this repository follows, whatever its language or tools. `AGENTS.md`
4
+ holds the short rules and takes precedence if the two ever disagree. This file comes from the
5
+ project template: report such a disagreement to the template instead of editing it. The project
6
+ adds the levels of its verification ladder (static checks, unit, integration, user journeys),
7
+ tools, commands, thresholds, and the edge cases of its domain in documents the map names, and
8
+ its test checks in `scripts/checks.d/` (`documentation-rules.md`, section 9).
9
+
10
+ ## 1. What a test is for
11
+
12
+ - A test protects a behaviour that a requirement, an acceptance criterion, a contract, or a fixed
13
+ defect names. It carries that identifier (`REQ-NNN`, `AC-NN`) in its name or a tag when one
14
+ applies, so that an `implemented` status and the "Acceptance and test plan" of a pull request can
15
+ point at it (`documentation-rules.md`, section 3; `contributing.md`, section 3). The project's
16
+ checks in `scripts/checks.d/` map each identifier to the tests that carry it.
17
+ - Test count and coverage are not goals. Coverage shows which lines ran, not what a test checked:
18
+ it is a floor the project sets, never evidence of quality. Where the project runs mutation
19
+ testing, the mutation score is the measure.
20
+ - What the type checker, schema validation, or the contract check already proves (a shape, a
21
+ required field, a closed set of values) gets no test of its own.
22
+ - One behaviour has one test, or one parametrised test over its table of inputs. A test that
23
+ catches nothing the other tests miss is removed, and removing it is a valid change.
24
+
25
+ ## 2. How a test proves behaviour
26
+
27
+ - A test asserts an observable outcome: the returned value or result, the resulting state, or the
28
+ emitted events, records, and output. An assertion that a mock was called, that a value is
29
+ defined or truthy, or that nothing threw is not enough on its own.
30
+ - Fakes stand in for the boundaries of the unit under test (external services, storage, the
31
+ clock, randomness), never for code inside it.
32
+ - Expected values are literals or fixtures. A test never computes its expectation with the logic
33
+ it tests, and a snapshot is never the only assertion of a rule.
34
+ - Failure paths are tested as deliberately as success: every distinct outcome or error a unit can
35
+ return has a test that produces it; every invalid contract example is rejected where input is validated.
36
+ - The edge cases that apply are covered: boundaries, empty and missing values, unknown against
37
+ zero, duplicates and retries, concurrent use, time zones and period boundaries, and the largest
38
+ input the requirement allows. The project lists the edge cases of its domain.
39
+ - Tests are deterministic: no sleeps, no real clock, network, or external service, no dependence on
40
+ test order. A flaky test teaches that red can be ignored: it is fixed, never retried or rerun
41
+ until green; skipping it is a blocker of section 4 unless the owner, asked, decides otherwise.
42
+
43
+ ## 3. Tests and the change they cover
44
+
45
+ - Tests come first: they are written before the implementation and run once to see them fail for
46
+ the expected reason. A test that passes before the implementation exists tests nothing.
47
+ - The acceptance tests of a requirement's criteria are written or reviewed by someone other than
48
+ the author of the implementation, before it exists; the author changes them only with the
49
+ owner's agreement.
50
+ - The agent derives the test plan from the identifiers the change serves: their cases, failure
51
+ paths, and edge cases (section 2). It is a report under "Acceptance and test plan"
52
+ (`contributing.md`, section 3), never approved per pull request and not maintained afterwards.
53
+ Each new or changed test fails on the base and passes with the change.
54
+ - A defect is fixed only after a regression test that reproduces it fails.
55
+ - A refactoring keeps the tests as they are. A change of behaviour first changes the requirement
56
+ or the acceptance criterion, then the test that carries the same identifier; the pull request
57
+ names both.
58
+
59
+ ## 4. Gates
60
+
61
+ - No test is skipped, focused, or deleted, no assertion is weakened, no code is excluded from
62
+ coverage, and no threshold is lowered to make a check pass. Such a need is an open question for
63
+ the owner.
64
+ - The quality measures the project records (coverage, mutation score) never fall below those of
65
+ the base branch.
66
+ - The configuration of the test runner, coverage, mutation testing, and linters is class gate in
67
+ `.claude/path-classes` (`documentation-rules.md`, section 8): it changes without a prompt, the
68
+ pull request lists it under "Gate files", which the readiness check enforces, and the owner
69
+ reviews it after the merge.
70
+ - An invariant that must hold everywhere (for example, a secret never appears in output) is an
71
+ executable check, a test or a project check, and the register `docs/invariants.md` names it
72
+ with that check (`documentation-rules.md`, section 2); its invariants are criteria of the
73
+ review of class high (`contributing.md`, section 6).
74
+ - Threat model of development gates: a gate (a lint rule, a test check, a coverage or mutation
75
+ setting, an agent hook) catches honest mistakes and shortcuts; it is not a security boundary.
76
+ It decides which bypass is a `blocker` (`contributing.md`, section 6): a diff that itself
77
+ loosens or evades a gate (a test skipped, an assertion weakened, a threshold lowered, a test
78
+ behind an aliased import), and any bypass of a product invariant (security, secrets, policy
79
+ enforcement, limits), whatever it takes. A gap that only deliberate evasion exploits, such as
80
+ a computed property or tampering with a gate's own state, is a `suggestion`.