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.
- package/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +77 -2
- package/bin/create-afe-project.js +123 -0
- package/package.json +43 -4
- package/src/answers.js +147 -0
- package/src/config.js +151 -0
- package/src/project.js +217 -0
- package/src/render.js +51 -0
- package/template/.claude/harness.conf +50 -0
- package/template/.claude/hooks/post-edit-check.sh +41 -0
- package/template/.claude/hooks/protect-docs.sh +315 -0
- package/template/.claude/hooks/session-start.sh +81 -0
- package/template/.claude/hooks/stop-check-decisions.sh +25 -0
- package/template/.claude/hooks/stop-check-docs.sh +29 -0
- package/template/.claude/hooks/stop-check-fast.sh +33 -0
- package/template/.claude/hooks/stop-check-template.sh +23 -0
- package/template/.claude/hooks/stop-run-project-hooks.sh +27 -0
- package/template/.claude/path-classes +102 -0
- package/template/.claude/settings.project.json +5 -0
- package/template/.claude/settings.template.json +92 -0
- package/template/.claude/skills/afe-init/SKILL.md +104 -0
- package/template/.claude/skills/create-pr/SKILL.md.jinja +110 -0
- package/template/.claude/skills/update-template/SKILL.md +40 -0
- package/template/.claude/skills/{% if code_review %}review-code{% endif %}/SKILL.md.jinja +50 -0
- package/template/.claude/skills/{% if docs_review %}docs-review{% endif %}/SKILL.md +60 -0
- package/template/.claude/skills/{% if dual_review %}dual-review{% endif %}/SKILL.md +58 -0
- package/template/.github/workflows/decisions.yml.jinja +57 -0
- package/template/.github/workflows/docs.yml.jinja +40 -0
- package/template/.github/workflows/{% if code_review %}code-review.yml{% endif %}.jinja +54 -0
- package/template/.github/workflows/{% if docs_review %}docs-review.yml{% endif %}.jinja +52 -0
- package/template/.github/workflows/{% if fail_without_change %}fail-without-change.yml{% endif %}.jinja +42 -0
- package/template/.github/workflows/{% if secret_scan %}secret-scan.yml{% endif %}.jinja +47 -0
- package/template/.markdownlint-cli2.jsonc +27 -0
- package/template/.template/owned.txt.jinja +65 -0
- package/template/AGENTS.md.jinja +91 -0
- package/template/CLAUDE.md +3 -0
- package/template/README.md.jinja +53 -0
- package/template/_gitignore +14 -0
- package/template/docs/README.md.jinja +44 -0
- package/template/docs/contributing.md.jinja +206 -0
- package/template/docs/decisions/adr-template.md +38 -0
- package/template/docs/documentation-rules.md.jinja +200 -0
- package/template/docs/testing-rules.md +80 -0
- package/template/scripts/check-decisions.sh +382 -0
- package/template/scripts/check-docs.sh +20 -0
- package/template/scripts/check-links.mjs +142 -0
- package/template/scripts/check-project.sh +27 -0
- package/template/scripts/check-template.sh +88 -0
- package/template/scripts/check.sh +21 -0
- package/template/scripts/env.sh +29 -0
- package/template/scripts/gen-decisions-index.sh +57 -0
- package/template/scripts/gen-settings.sh +14 -0
- package/template/scripts/lib/classes.sh +89 -0
- package/template/scripts/lib/git-base.sh +25 -0
- package/template/scripts/lib/harness-conf.sh +48 -0
- package/template/scripts/lib/json.mjs +160 -0
- package/template/scripts/pr-metrics.sh +80 -0
- package/template/scripts/protect-branch.sh +101 -0
- package/template/scripts/{% if acceptance_check %}check-acceptance.sh{% endif %} +47 -0
- 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`.
|