workflow-toolkit 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/ponytail/SKILL.md +111 -0
- package/.agents/skills/ponytail-audit/SKILL.md +37 -0
- package/.agents/skills/ponytail-debt/SKILL.md +41 -0
- package/.agents/skills/ponytail-gain/SKILL.md +47 -0
- package/.agents/skills/ponytail-help/SKILL.md +70 -0
- package/.agents/skills/ponytail-review/SKILL.md +52 -0
- package/.agents/skills/prompt-review/SKILL.md +60 -0
- package/.agents/skills/wtk/SKILL.md +64 -0
- package/.agents/skills/wtk/references/artifacts.md +82 -0
- package/.agents/skills/wtk/references/evidence.md +68 -0
- package/.agents/skills/wtk/references/git.md +75 -0
- package/.agents/skills/wtk/references/test-contract.md +76 -0
- package/.agents/skills/wtk/references/validation.md +127 -0
- package/.agents/skills/wtk-config/SKILL.md +102 -0
- package/.agents/skills/wtk-config/assets/agents/claude/deep-reviewer.md +40 -0
- package/.agents/skills/wtk-config/assets/agents/claude/designer.md +57 -0
- package/.agents/skills/wtk-config/assets/agents/claude/explorer.md +41 -0
- package/.agents/skills/wtk-config/assets/agents/claude/implementer.md +57 -0
- package/.agents/skills/wtk-config/assets/agents/claude/planner.md +68 -0
- package/.agents/skills/wtk-config/assets/agents/claude/verifier.md +80 -0
- package/.agents/skills/wtk-config/assets/agents/codex/deep-reviewer.toml +41 -0
- package/.agents/skills/wtk-config/assets/agents/codex/designer.toml +55 -0
- package/.agents/skills/wtk-config/assets/agents/codex/explorer.toml +35 -0
- package/.agents/skills/wtk-config/assets/agents/codex/implementer.toml +52 -0
- package/.agents/skills/wtk-config/assets/agents/codex/planner.toml +66 -0
- package/.agents/skills/wtk-config/assets/agents/codex/verifier.toml +78 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/deep-reviewer.md +38 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/designer.md +55 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/explorer.md +39 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/implementer.md +55 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/planner.md +66 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/verifier.md +79 -0
- package/.agents/skills/wtk-config/scripts/ad-index.py +123 -0
- package/.agents/skills/wtk-config/scripts/repository_intelligence.py +671 -0
- package/.agents/skills/wtk-config/scripts/workflow_config.py +972 -0
- package/.agents/skills/wtk-deep-review/SKILL.md +164 -0
- package/.agents/skills/wtk-deep-review/assets/PROMPT.md +56 -0
- package/.agents/skills/wtk-deep-review/assets/REVIEW_UI.html +1340 -0
- package/.agents/skills/wtk-deep-review/assets/findings.schema.json +113 -0
- package/.agents/skills/wtk-deep-review/references/context-pack.md +88 -0
- package/.agents/skills/wtk-deep-review/references/orchestration.md +164 -0
- package/.agents/skills/wtk-deep-review/references/output-contracts.md +149 -0
- package/.agents/skills/wtk-deep-review/references/publish-github.md +83 -0
- package/.agents/skills/wtk-deep-review/references/state-and-learnings.md +64 -0
- package/.agents/skills/wtk-deep-review/references/subagent-runtimes.md +33 -0
- package/.agents/skills/wtk-deep-review/references/taxonomy.md +70 -0
- package/.agents/skills/wtk-deep-review/scripts/_common.py +371 -0
- package/.agents/skills/wtk-deep-review/scripts/build_jobs.py +561 -0
- package/.agents/skills/wtk-deep-review/scripts/build_knowledge.py +349 -0
- package/.agents/skills/wtk-deep-review/scripts/build_manifest.py +479 -0
- package/.agents/skills/wtk-deep-review/scripts/graft_context.py +160 -0
- package/.agents/skills/wtk-deep-review/scripts/graphify_context.py +75 -0
- package/.agents/skills/wtk-deep-review/scripts/merge_findings.py +313 -0
- package/.agents/skills/wtk-deep-review/scripts/render_html.py +240 -0
- package/.agents/skills/wtk-deep-review/scripts/render_review.py +282 -0
- package/.agents/skills/wtk-deep-review/scripts/run_jobs.py +436 -0
- package/.agents/skills/wtk-deep-review/scripts/token_metrics.py +369 -0
- package/.agents/skills/wtk-discover/SKILL.md +270 -0
- package/.agents/skills/wtk-discover/references/document-format.md +149 -0
- package/.agents/skills/wtk-implement/SKILL.md +95 -0
- package/.agents/skills/wtk-implement/references/checklist-format.md +116 -0
- package/.agents/skills/wtk-implement/references/screens.md +129 -0
- package/.agents/skills/wtk-implement/references/test-policy.md +152 -0
- package/.agents/skills/wtk-implement/references/verify.md +297 -0
- package/.agents/skills/wtk-knowledge-check/SKILL.md +16 -0
- package/.agents/skills/wtk-knowledge-check/scripts/check.ts +502 -0
- package/.agents/skills/wtk-knowledge-check/scripts/cli.ts +32 -0
- package/.agents/skills/wtk-knowledge-check/scripts/frontmatter.ts +44 -0
- package/.agents/skills/wtk-lean/.skill-meta.json +6 -0
- package/.agents/skills/wtk-lean/NOTICE.md +18 -0
- package/.agents/skills/wtk-lean/SKILL.md +137 -0
- package/.agents/skills/wtk-lean/references/build.md +168 -0
- package/.agents/skills/wtk-lean/references/checks.md +253 -0
- package/.agents/skills/wtk-lean/references/memory.md +156 -0
- package/.agents/skills/wtk-lean/references/plan.md +514 -0
- package/.agents/skills/wtk-lean/references/verify.md +336 -0
- package/.agents/skills/wtk-lean/scripts/check_commit.py +121 -0
- package/.agents/skills/wtk-lean/scripts/fixtures/checks.md +98 -0
- package/.agents/skills/wtk-lean/scripts/fixtures/plan.md +119 -0
- package/.agents/skills/wtk-lean/scripts/fixtures/verification.md +41 -0
- package/.agents/skills/wtk-lean/scripts/lessons.py +412 -0
- package/.agents/skills/wtk-lean/scripts/selftest.py +401 -0
- package/.agents/skills/wtk-lean/scripts/validate_checks.py +417 -0
- package/.agents/skills/wtk-lean/scripts/validate_plan.py +580 -0
- package/.agents/skills/wtk-lean/scripts/validate_verification.py +357 -0
- package/.agents/skills/wtk-plan/SKILL.md +102 -0
- package/.agents/skills/wtk-plan/references/document-format.md +222 -0
- package/.agents/skills/wtk-qa/SKILL.md +14 -0
- package/.agents/skills/wtk-qa-execute/SKILL.md +115 -0
- package/.agents/skills/wtk-qa-execute/references/fix-loop.md +23 -0
- package/.agents/skills/wtk-qa-execute/references/session-protocol.md +25 -0
- package/.agents/skills/wtk-qa-plan/SKILL.md +111 -0
- package/.agents/skills/wtk-qa-plan/references/profile.md +35 -0
- package/.agents/skills/wtk-ship/SKILL.md +49 -0
- package/.agents/skills/wtk-ship/remediation.py +170 -0
- package/.agents/skills/wtk-ship/scripts/close_feature.py +83 -0
- package/.agents/skills/wtk-ship/scripts/review_convergence.py +373 -0
- package/.wtk.toml.example +93 -0
- package/AGENTS.md +129 -0
- package/NOTICE.md +12 -0
- package/README.md +440 -0
- package/bin/wtk.js +25 -0
- package/docs/toolkit/README.md +56 -0
- package/docs/toolkit/decisions.md +61 -0
- package/docs/toolkit/guidelines/CONTEXT-BUDGET.md +64 -0
- package/docs/toolkit/guidelines/DX.md +67 -0
- package/docs/toolkit/guidelines/FRONTEND.md +73 -0
- package/docs/toolkit/guidelines/KNOWLEDGE-WIKI.md +128 -0
- package/docs/toolkit/guidelines/MODELING.md +134 -0
- package/docs/toolkit/guidelines/QA-EXECUTION.md +46 -0
- package/docs/toolkit/guidelines/QA-SCENARIOS.md +165 -0
- package/docs/toolkit/guidelines/REVIEW-ROUNDS.md +160 -0
- package/docs/toolkit/guidelines/SECURITY.md +160 -0
- package/docs/toolkit/guidelines/UI-UX.md +117 -0
- package/docs/toolkit/guidelines/WORKFLOW-MEMORY.md +73 -0
- package/docs/toolkit/guidelines.md +50 -0
- package/docs/toolkit/loop.md +77 -0
- package/docs/toolkit/purpose.md +51 -0
- package/docs/toolkit/repository-intelligence.md +53 -0
- package/docs/toolkit/reviews.md +77 -0
- package/knowledge/AGENTS.md +316 -0
- package/knowledge/raw/README.md +17 -0
- package/package.json +74 -0
- package/scripts/install_security_skills.py +816 -0
- package/scripts/installer/engine.js +254 -0
- package/scripts/installer/knowledge.js +28 -0
- package/scripts/installer/packets.js +77 -0
- package/scripts/installer/terminal.js +98 -0
- package/scripts/installer/transaction.js +75 -0
- package/skills-lock.json +92 -0
- package/templates/adoption/agents/core.md +23 -0
- package/templates/adoption/agents/quality.md +3 -0
- package/templates/adoption/knowledge/wiki/architecture/index.md +3 -0
- package/templates/adoption/knowledge/wiki/decisions/index.md +3 -0
- package/templates/adoption/knowledge/wiki/design/index.md +3 -0
- package/templates/adoption/knowledge/wiki/domain/index.md +3 -0
- package/templates/adoption/knowledge/wiki/index.md +3 -0
- package/templates/adoption/knowledge/wiki/log.md +3 -0
- package/templates/adoption/knowledge/wiki/open-questions/index.md +3 -0
- package/templates/adoption/knowledge/wiki/product/index.md +3 -0
- package/templates/adoption/knowledge/wiki/research/index.md +3 -0
- package/templates/adoption/product/AGENT-CONTEXT.md +25 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Memory
|
|
2
|
+
|
|
3
|
+
Three things outlive a session: decisions that constrain future features, a handoff snapshot for
|
|
4
|
+
picking work back up, and lessons distilled from real verification failures. All three live under
|
|
5
|
+
`.specs/`.
|
|
6
|
+
|
|
7
|
+
## `.specs/STATE.md`
|
|
8
|
+
|
|
9
|
+
Two sections, written by different phases, never overwritten together.
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# Project state
|
|
13
|
+
|
|
14
|
+
## Decisions
|
|
15
|
+
|
|
16
|
+
| ID | Decision | Rationale | Status | Date |
|
|
17
|
+
| --- | --- | --- | --- | --- |
|
|
18
|
+
| AD-001 | Webhook ingest is idempotent on provider delivery id | replays are routine and cheap to dedup at the edge | active | 2026-02-14 |
|
|
19
|
+
| AD-002 | Money is stored in integer cents | superseded by AD-007 | superseded by AD-007 | 2026-03-02 |
|
|
20
|
+
|
|
21
|
+
## Handoff
|
|
22
|
+
|
|
23
|
+
**Feature**: <name>
|
|
24
|
+
**Where**: <check id> - <what is done, what is not>
|
|
25
|
+
**In progress**: `path/to/file.rb:88` - <what was being changed>
|
|
26
|
+
**Next step**: <the concrete next action>
|
|
27
|
+
**Blockers**: <none | description>
|
|
28
|
+
**Uncommitted**: <files>
|
|
29
|
+
**Branch**: <name>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Section-scoped writes are critical.** Replace only the body between a `##` header and the next
|
|
33
|
+
one. Overwriting the whole file silently destroys the decisions log, and nothing downstream
|
|
34
|
+
notices until a future feature contradicts a decision nobody can find.
|
|
35
|
+
|
|
36
|
+
**Where the repo already keeps ADRs or a decision log, use that instead.** Do not start a second
|
|
37
|
+
log; append there and keep `STATE.md` to the handoff section. Two logs means neither is
|
|
38
|
+
authoritative.
|
|
39
|
+
|
|
40
|
+
## Recording a decision
|
|
41
|
+
|
|
42
|
+
A decision belongs in `## Decisions` when a future feature has to conform to it: a convention, a
|
|
43
|
+
pattern, a constraint. It is written when the choice is made, not at the end.
|
|
44
|
+
|
|
45
|
+
This is a different bar from `Landing` in `plan.md`. `Landing` carries doors this feature closes,
|
|
46
|
+
with the literal shape someone will copy. `Decisions` carries the ones that reach past this
|
|
47
|
+
feature. A door that is both gets a row in each - the shape in `Landing`, the constraint here.
|
|
48
|
+
|
|
49
|
+
**Read the active decisions before writing checks.** Every `active` `AD-NNN` is a constraint the
|
|
50
|
+
work must conform to. Where one conflicts with what is best for this feature, you have two
|
|
51
|
+
options and both are explicit: conform, or append a new entry that supersedes the old one (setting
|
|
52
|
+
the old row's status to `superseded by AD-NNN`) and say why. Silently ignoring an active decision
|
|
53
|
+
creates inconsistency nobody can see.
|
|
54
|
+
|
|
55
|
+
## Resuming work
|
|
56
|
+
|
|
57
|
+
1. Read `.specs/STATE.md` - handoff and decisions.
|
|
58
|
+
2. Reconcile against git: `git branch --show-current`, `git status --porcelain`, recent commits,
|
|
59
|
+
and the completion marks in `checks.md`. **Evidence wins over a stale snapshot** - a handoff
|
|
60
|
+
written before a crash describes intent, the commits describe fact.
|
|
61
|
+
3. Propose the reconciled next step before writing code.
|
|
62
|
+
|
|
63
|
+
A snapshot that disagrees with git is not a conflict to resolve carefully; it is simply out of
|
|
64
|
+
date. Say what you found and move on.
|
|
65
|
+
|
|
66
|
+
## Lessons
|
|
67
|
+
|
|
68
|
+
Verification failures become reusable guidance, or they happen again. The split that keeps this
|
|
69
|
+
alive: **you supply judgment** - read the failure, phrase the lesson, cite its grounding - and
|
|
70
|
+
`scripts/lessons.py` owns everything mechanical: IDs, recurrence counting across distinct
|
|
71
|
+
features, candidate→confirmed promotion, pruning, demotion, rendering. Hand-kept bookkeeping is
|
|
72
|
+
exactly what rots, so it is not your job.
|
|
73
|
+
|
|
74
|
+
| File | Owner |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `.specs/lessons.json` | script - canonical state, never hand-edit |
|
|
77
|
+
| `.specs/LESSONS.md` | script - rendered playbook, read it, never write it |
|
|
78
|
+
|
|
79
|
+
`confirmed` lessons are the playbook. `candidate` lessons are tracked but not trusted until
|
|
80
|
+
corroborated across two distinct features. `quarantined` ones failed when applied and are ignored.
|
|
81
|
+
|
|
82
|
+
### Write - at the end of verification
|
|
83
|
+
|
|
84
|
+
Walk the just-written `verification.md`. For each **grounded** signal, record one lesson:
|
|
85
|
+
|
|
86
|
+
| Signal in the report | `--signal` |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| A check unproven, or with no located evidence | `ac_gap` |
|
|
89
|
+
| A mutant survived fault injection | `surviving_mutant` |
|
|
90
|
+
| A check left a value imprecise (precision gap) | `spec_precision_gap` |
|
|
91
|
+
| A check contradicted a binding source, or the build diverged from an approved shape | `spec_deviation` |
|
|
92
|
+
| The gate failed | `gate_fail` |
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python3 <skill-dir>/scripts/lessons.py add \
|
|
96
|
+
--feature "<feature folder>" \
|
|
97
|
+
--signal "surviving_mutant" \
|
|
98
|
+
--source "<file:line | check id | mutation id from verification.md>" \
|
|
99
|
+
--text "<one general, actionable sentence>" \
|
|
100
|
+
--scope "<optional: billing, routes, repo-layer>"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--source` is mandatory and the script exits non-zero without it. That is the grounding gate
|
|
104
|
+
working, not an error to route around: a lesson with no grounding in a real verification outcome
|
|
105
|
+
is an opinion.
|
|
106
|
+
|
|
107
|
+
**Phrasing rules** - deduplication is exact-after-normalization, not semantic, so two lessons that
|
|
108
|
+
mean the same thing must read the same way or neither ever gets promoted:
|
|
109
|
+
|
|
110
|
+
- Write the general rule, not the incident. Good: `"Assert the exact persisted status value, not
|
|
111
|
+
just that a status field exists"`. Bad: `"The subscription test on line 88 was too weak"`.
|
|
112
|
+
- Be canonical and terse. One lesson per signal; do not bundle.
|
|
113
|
+
|
|
114
|
+
**Scope discipline.** This captures *execution* lessons about this codebase. It does **not**
|
|
115
|
+
capture opinions about the workflow itself ("we should write checks earlier") - those are
|
|
116
|
+
maintainer decisions that ship in a version bump, never auto-written. If a candidate lesson is
|
|
117
|
+
about how to run the skill rather than about this code, do not record it.
|
|
118
|
+
|
|
119
|
+
A clean PASS with no surviving mutant, no precision gap and no unproven member records **nothing**.
|
|
120
|
+
That is correct. But if the report had signal and you recorded zero lessons, say so plainly in
|
|
121
|
+
chat - silent skipping is how the file dies.
|
|
122
|
+
|
|
123
|
+
### Read - at Plan and before writing checks
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python3 <skill-dir>/scripts/lessons.py --root <project> list --status confirmed
|
|
127
|
+
python3 <skill-dir>/scripts/lessons.py --root <project> list --status confirmed --scope billing
|
|
128
|
+
python3 <skill-dir>/scripts/lessons.py --root <project> list --status confirmed --query idempotency
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Load `confirmed` only. A lessons file nobody reads is dead by definition, so this is mandatory at
|
|
132
|
+
Plan - but keep the loaded set small and filtered to the area this feature touches.
|
|
133
|
+
|
|
134
|
+
### Demotion
|
|
135
|
+
|
|
136
|
+
If a `confirmed` lesson was loaded for this feature and the same failure recurred anyway, the
|
|
137
|
+
guidance is not working: `lessons.py penalize --id L-NNN`. Two penalties quarantine it. Use only
|
|
138
|
+
on real repeats.
|
|
139
|
+
|
|
140
|
+
### Turning it off
|
|
141
|
+
|
|
142
|
+
The layer is additive and self-gating - no signal, no write. To disable for a project, delete
|
|
143
|
+
`.specs/lessons.json` and `.specs/LESSONS.md` and skip these steps. The Plan → Checks → Build →
|
|
144
|
+
Verify flow is unaffected.
|
|
145
|
+
|
|
146
|
+
### Known limitation
|
|
147
|
+
|
|
148
|
+
Deduplication has no embeddings (stdlib only, zero dependencies), so near-duplicates phrased
|
|
149
|
+
differently sit as separate candidates that never promote. The phrasing rules above are the
|
|
150
|
+
mitigation.
|
|
151
|
+
|
|
152
|
+
## No code-execution tool
|
|
153
|
+
|
|
154
|
+
Maintain `.specs/LESSONS.md` by hand under the same rules - grounded entries only,
|
|
155
|
+
candidate→confirmed after two distinct features, prune stale candidates - and say once in chat
|
|
156
|
+
that you are in the degraded path so the user knows the accounting is best-effort.
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
**Goal:** the one artifact a human reads to understand the work and object to it, **before** any
|
|
4
|
+
claim carries a test selector. Two halves: what must be true, then what is being built.
|
|
5
|
+
|
|
6
|
+
They are one file because they are one activity and one review. Reading forty checks to
|
|
7
|
+
reconstruct what is being built is not planning, and deriving the obligations in the same pass
|
|
8
|
+
that decides the shape produces checks that ratify whatever was already assumed. So this gets
|
|
9
|
+
confirmed, and only then does [checks.md](checks.md) turn it into obligations with proofs.
|
|
10
|
+
|
|
11
|
+
Skip this file only when the change is under roughly three files with no one-way door - then
|
|
12
|
+
write the intent paragraph straight into `checks.md`.
|
|
13
|
+
|
|
14
|
+
## Before asking anything
|
|
15
|
+
|
|
16
|
+
**Load confirmed lessons.** `--root` is a parent flag and has to come before the
|
|
17
|
+
subcommand: `python3 <skill-dir>/scripts/lessons.py --root <project> list --status confirmed`
|
|
18
|
+
(add `--scope` or `--query` for the area this feature touches). Confirmed only - never
|
|
19
|
+
`candidate` or `quarantined`. No store yet, or no code tool: skip silently.
|
|
20
|
+
|
|
21
|
+
**Read `.specs/STATE.md` `## Decisions`.** Every `active` `AD-NNN` is a project-level constraint
|
|
22
|
+
the shape must conform to. Where one conflicts with what is best for this feature, there are two
|
|
23
|
+
options and both are explicit: conform, or append a new entry that supersedes the old one
|
|
24
|
+
(setting the old row's status to `superseded by AD-NNN`) and say why. Silently ignoring an active
|
|
25
|
+
decision creates an inconsistency nobody can find later.
|
|
26
|
+
|
|
27
|
+
**Scan the code this feature touches.** Neighbouring features, the existing conventions, the
|
|
28
|
+
terms already in use. This grounds the questions in reality, it is where you find that the source
|
|
29
|
+
names things the system does not have, and it is what the shape gets built from.
|
|
30
|
+
|
|
31
|
+
**Facts you look up; decisions you ask.** Anything the environment answers - a convention, an
|
|
32
|
+
existing field, how the current endpoint behaves, what the schema allows - you resolve yourself
|
|
33
|
+
through the knowledge chain: existing code and conventions, project docs, library documentation,
|
|
34
|
+
web search, then flag as uncertain. Never invent an API, a flag or a behaviour; a fabrication
|
|
35
|
+
here propagates into the checks and then into a green test that proves nothing. A question you
|
|
36
|
+
could have answered by reading the code spends the user's turn and their patience, and enough of
|
|
37
|
+
them turn this into an interview. Ask only what is genuinely theirs: scope, priority, product
|
|
38
|
+
behaviour, which trade-off they want.
|
|
39
|
+
|
|
40
|
+
**A bet that is still open does not get placed here.** Whether this is a job or a request cycle,
|
|
41
|
+
whether the state machine belongs in the model or a service, which of two architectures to commit
|
|
42
|
+
to - when that is genuinely undecided it needs each option costed against this repository and the
|
|
43
|
+
condition that would make the other one win, and the only shape this file has for a decision is a
|
|
44
|
+
`Landing` row. Squeezing one in produces a bet nobody reviewed, recorded where nobody looks for
|
|
45
|
+
one. Settle it first with whatever the project uses - an ADR, an RFC, a spike - and link it. This
|
|
46
|
+
file records the shape that won and makes it reviewable.
|
|
47
|
+
|
|
48
|
+
# Part one: what must be true
|
|
49
|
+
|
|
50
|
+
## Recover the problem
|
|
51
|
+
|
|
52
|
+
The source usually arrives as a solution. Write the problem in the present tense with no
|
|
53
|
+
solution inside it: what is true today that should not be, who pays for it, and what it costs
|
|
54
|
+
them. "We have no Stripe integration" is not a problem - it is the absence of this feature's
|
|
55
|
+
answer, and phrased that way it can only justify the thing already chosen. "Anyone evaluating
|
|
56
|
+
the product has to enter a card first" is a sentence someone can disagree with, and being
|
|
57
|
+
disagreeable is the test.
|
|
58
|
+
|
|
59
|
+
Copy the evidence the source gives, literally - a conversion figure, a support volume, a date
|
|
60
|
+
somebody else set. Where it gives none, say so rather than manufacturing urgency.
|
|
61
|
+
|
|
62
|
+
**Challenge vagueness.** "Good" means what? "Users" means who? "Simple" means how? Make the
|
|
63
|
+
abstract concrete: walk me through using this.
|
|
64
|
+
|
|
65
|
+
## How to ask, when you have to ask
|
|
66
|
+
|
|
67
|
+
Most of what looks like a question is not one. **A gray area is a decision that is genuinely the
|
|
68
|
+
user's, has more than one defensible answer, and is not settled by the code.** Fail any of the
|
|
69
|
+
three and it is not a gray area: the repo's conventions answer it, or one option is clearly right
|
|
70
|
+
and you state it as an assumption and move on. Do not go looking for a quota of them - a quota
|
|
71
|
+
manufactures questions the same way a checklist with no `n/a` escape manufactures requirements.
|
|
72
|
+
|
|
73
|
+
**What you do not know is findable, and the next two sections are the two lists that find it.**
|
|
74
|
+
`## Observable` below walks the surfaces this feature exposes, because every surface carries the
|
|
75
|
+
same decisions every time; the nine dimensions after it walk the system properties. Neither is a
|
|
76
|
+
prompt to think harder - both are enumerations, for the same reason: "consider the edge cases"
|
|
77
|
+
finds nothing, and a fixed list with a mandatory `n/a` escape finds the item nobody mentioned.
|
|
78
|
+
|
|
79
|
+
Where you do ask, these are the rules, and they are about turn cost rather than politeness. Every
|
|
80
|
+
badly shaped question spends a turn and buys less than a stated default would have.
|
|
81
|
+
|
|
82
|
+
- **Concrete options, never an open prompt.** "Card layout or table layout" is answerable; "how
|
|
83
|
+
should this look?" hands the work back.
|
|
84
|
+
- **Lead with your recommendation and one line of why.** You have read the code; accepting or
|
|
85
|
+
overriding should cost one word.
|
|
86
|
+
- **Assume first when it is safe.** State the default and invite correction instead of blocking.
|
|
87
|
+
A question you would have answered the same way regardless of the reply is not worth asking.
|
|
88
|
+
- **At most two independent questions per turn, exactly one when they are dependent** - a
|
|
89
|
+
dependent answer prunes the questions after it, so asking them together wastes most of them.
|
|
90
|
+
Three or more in a turn is an interrogation, and it reads as one.
|
|
91
|
+
- **"You decide" is an answer.** Record it as an assumption with `Confirmed? y` and the rationale
|
|
92
|
+
`user delegated`, so discretion is on the record rather than inferred from silence later.
|
|
93
|
+
- **The boundary is fixed.** Asking clarifies *how*, never whether to add a capability. A new
|
|
94
|
+
capability that surfaces goes in `Out of scope` with its reason and stays there.
|
|
95
|
+
|
|
96
|
+
Anything asked and not answered, or that you chose not to raise, lands in `## Assumptions` with
|
|
97
|
+
your chosen default and rationale and `Confirmed? n`. That column is the whole record of who
|
|
98
|
+
decided what: `y` means a human said so. Never mark `y` for a default nobody saw.
|
|
99
|
+
|
|
100
|
+
## Walk the surfaces
|
|
101
|
+
|
|
102
|
+
A surface is anything outside the system that meets it, and each kind carries the same decisions
|
|
103
|
+
every time it appears. That is what makes them findable rather than a matter of remembering: you
|
|
104
|
+
do not ask "what did I forget about this screen", you walk the row.
|
|
105
|
+
|
|
106
|
+
| Surface | The decisions it always has |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| a screen or view | empty, loading, error and unauthorised states; density and ordering; what a destructive action confirms before doing it |
|
|
109
|
+
| an API or webhook someone calls | response shape, error shape with its codes, who may call it, versioning, what happens at the rate limit |
|
|
110
|
+
| a command or scheduled task | output format and verbosity, every flag and its default, exit codes, what it prints when it fails halfway |
|
|
111
|
+
| a document or copy someone reads | structure, tone, depth, and what the reader is meant to do next |
|
|
112
|
+
| a collection being organised | the grouping criterion, naming, ordering, what happens to duplicates, and the exception that does not fit |
|
|
113
|
+
|
|
114
|
+
Nothing about state, persistence or contracts is here - that is the nine dimensions below, and
|
|
115
|
+
duplicating it in both places produces two answers that disagree.
|
|
116
|
+
|
|
117
|
+
**Each item resolves to a criterion, to something that already behaves that way, or to an explicit
|
|
118
|
+
`n/a - <reason>`.** The `n/a` escape is mandatory and it is what stops the list from inventing
|
|
119
|
+
scope: a webhook has no empty state, and saying so costs a line. `None - no user-facing surface`
|
|
120
|
+
is a complete answer for a feature that exposes none.
|
|
121
|
+
|
|
122
|
+
Two of these hide better than the rest. An **error shape** is decided by whoever writes the first
|
|
123
|
+
handler, so it gets decided by accident and then copied. An **empty state** is invisible until the
|
|
124
|
+
feature ships to someone whose account is new, which is every user on their first day.
|
|
125
|
+
|
|
126
|
+
**Where the record lives:** `## Observable` in this file, one row per item, because each lands on
|
|
127
|
+
a criterion rather than on a check.
|
|
128
|
+
|
|
129
|
+
## Write the criteria in EARS
|
|
130
|
+
|
|
131
|
+
Every acceptance criterion resolves to exactly one pattern. Pick the one that fits instead of
|
|
132
|
+
forcing everything into WHEN/THEN.
|
|
133
|
+
|
|
134
|
+
| Pattern | Keyword | Template | Use for |
|
|
135
|
+
| --- | --- | --- | --- |
|
|
136
|
+
| Ubiquitous | (none) | The [system] SHALL [response] | always-on invariants |
|
|
137
|
+
| Event-driven | WHEN | WHEN [trigger] THEN the [system] SHALL [response] | a response to a discrete trigger |
|
|
138
|
+
| State-driven | WHILE | WHILE [state] the [system] SHALL [response] | behaviour that holds during a state |
|
|
139
|
+
| Optional-feature | WHERE | WHERE [feature is present] the [system] SHALL [response] | behaviour behind a flag or optional capability |
|
|
140
|
+
| Unwanted-behaviour | IF / THEN | IF [undesired condition] THEN the [system] SHALL [response] | errors, failures, invalid input, timeouts |
|
|
141
|
+
| Complex | combination | WHILE [state], WHEN [trigger] the [system] SHALL [response] | the above combined |
|
|
142
|
+
|
|
143
|
+
The patterns exist so that failure states, state transitions and optional behaviour become
|
|
144
|
+
first-class criteria instead of footnotes squeezed into a subordinate clause of some other
|
|
145
|
+
line. That is the whole reason not to use one shape.
|
|
146
|
+
|
|
147
|
+
**Rules.** One requirement per criterion, never two behaviours bundled. Concrete values - a
|
|
148
|
+
status code, a field, a bound - never "quickly", "gracefully" or "properly". Every criterion
|
|
149
|
+
contains a SHALL and is measurable. Edge cases are criteria, usually IF/THEN, not a separate
|
|
150
|
+
list.
|
|
151
|
+
|
|
152
|
+
**One run must settle it.** A percentile, an average, an uptime or an error rate is a service
|
|
153
|
+
target, not a criterion: no single execution can satisfy or fail it. Split the line - the
|
|
154
|
+
behaviour becomes the criterion, and the target lands on the observability dimension of the
|
|
155
|
+
sweep. Fused, the provable half hides behind the unprovable one and a test that never touched
|
|
156
|
+
the number marks the whole line green.
|
|
157
|
+
|
|
158
|
+
**A guarantee that something will not happen needs a mechanism.** Nothing prevents a duplicate
|
|
159
|
+
or a double charge by default, so "a retry does not create a second subscription" is a claim
|
|
160
|
+
about machinery. Point at the code that enforces it, or make it a one-way door in `Landing`
|
|
161
|
+
below. Walk the failure that would produce the forbidden thing - the remote call succeeded and
|
|
162
|
+
the local write did not - because that is the path nobody pictures. Where no mechanism exists it
|
|
163
|
+
is a door or a question, never a criterion standing alone.
|
|
164
|
+
|
|
165
|
+
## Sweep the nine dimensions
|
|
166
|
+
|
|
167
|
+
The source covers what somebody thought of. This is the list of what nobody writes down, and
|
|
168
|
+
it is fixed so a blank cannot look like nothing to answer:
|
|
169
|
+
|
|
170
|
+
| Dimension | What to cover |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| Validation and bounds | limits, formats, sanitization |
|
|
173
|
+
| Failure and partial failure | timeouts, partial saves, rollbacks |
|
|
174
|
+
| Idempotency, retry, duplicates | safe retries, dedup keys |
|
|
175
|
+
| Authorization and rate limits | who can call what, throttling |
|
|
176
|
+
| Concurrency and ordering | races, ordering guarantees |
|
|
177
|
+
| Data lifecycle | TTL, archival, deletion, backfill |
|
|
178
|
+
| External-dependency failure | circuit breakers, fallbacks |
|
|
179
|
+
| State transitions | valid transitions, guards |
|
|
180
|
+
| Observability | logging, metrics, tracing |
|
|
181
|
+
|
|
182
|
+
Walk all nine. Each resolves to a criterion, to something the code already handles, or to an
|
|
183
|
+
explicit `n/a because [reason]`. The `n/a` escape is mandatory - it is what stops the list from
|
|
184
|
+
manufacturing requirements. Where a dimension needs a product answer it becomes an open
|
|
185
|
+
question, never a criterion you invented.
|
|
186
|
+
|
|
187
|
+
Concurrency and observability hide better than the other seven, because neither is visible to
|
|
188
|
+
a user until it fails. Those two are why this list exists.
|
|
189
|
+
|
|
190
|
+
A landing must observe **that** dimension. Reaching for a number already used on another line
|
|
191
|
+
is the tell that the dimension is uncovered: a duplicate rejected because a row already exists
|
|
192
|
+
says nothing about two requests arriving at once. When you catch yourself borrowing, the honest
|
|
193
|
+
answers are `n/a` with the reason, or a question.
|
|
194
|
+
|
|
195
|
+
**Where the record lives:** the landing for each dimension is written in `checks.md` under
|
|
196
|
+
`## Swept`, because each one has to cite a check number. Do not duplicate it here.
|
|
197
|
+
|
|
198
|
+
# Part two: what is being built
|
|
199
|
+
|
|
200
|
+
Five sections, each bounded by a rule about what stays out. That boundary is the whole point:
|
|
201
|
+
the design half of most spec-driven flows fails not because designing is wrong but because the
|
|
202
|
+
document accumulates a component catalogue - `Purpose`, `Location`, `Interfaces`,
|
|
203
|
+
`Dependencies` per class - which is reversible detail, goes stale within weeks, and then
|
|
204
|
+
misleads the next reader with the authority of a written document. None of those fields exists
|
|
205
|
+
here.
|
|
206
|
+
|
|
207
|
+
## Flow - the path, not the catalogue
|
|
208
|
+
|
|
209
|
+
Open with one or two sentences on what this reuses instead of duplicating - the verifier that
|
|
210
|
+
already exists, the job that already prunes, the policy that already decides. That sentence is a
|
|
211
|
+
decision, and it is the one that keeps a second implementation of an existing thing from landing
|
|
212
|
+
in the diff. It replaces a code-reuse table: each hop below already marks whether its module
|
|
213
|
+
exists, so the inventory is distributed through the path and the sentence carries only the choice.
|
|
214
|
+
|
|
215
|
+
Then one line per hop, in order: what enters, which module it crosses, what it hands to the next,
|
|
216
|
+
and what persists or goes out at the end. This is the map a reviewer reads the `Landing` rows
|
|
217
|
+
against, and what a second builder needs so it does not rebuild a hop that exists.
|
|
218
|
+
|
|
219
|
+
**Name only modules that exist today, or that `Landing` creates as a door.** A component that is
|
|
220
|
+
neither is placement - reversible, answered by the repo's conventions, settled in the diff. That
|
|
221
|
+
single rule is what keeps this from becoming the catalogue.
|
|
222
|
+
|
|
223
|
+
`single module - <name>` is a complete answer, and most features are that. The section is
|
|
224
|
+
required anyway: a feature crossing four modules and one crossing one have to be distinguishable
|
|
225
|
+
without reading the whole artifact.
|
|
226
|
+
|
|
227
|
+
**When the path is not linear, draw it instead.** A numbered list cannot express one event
|
|
228
|
+
fanning out to three independent handlers, a fork that takes one of two paths, or a hop that
|
|
229
|
+
enqueues and returns while the work happens later - and flattening any of those into a sequence
|
|
230
|
+
describes a system that does not exist. Use a mermaid `flowchart` for exactly those cases, with
|
|
231
|
+
the same rule: every module node carries `(exists)` or the door that creates it, or it is
|
|
232
|
+
placement and does not belong here.
|
|
233
|
+
|
|
234
|
+
The diagram is the exception and the list is the default, for a reason that is not aesthetic:
|
|
235
|
+
`Flow` is **kept true** through the build, and a list is cheap to correct in the commit that
|
|
236
|
+
changes the path while a diagram quietly rots. Reach for it when the shape genuinely branches,
|
|
237
|
+
not to make a three-hop line look considered.
|
|
238
|
+
|
|
239
|
+
## Relations - the stored shape
|
|
240
|
+
|
|
241
|
+
Only when this changes the shape of stored data, and only what `Landing` settled: entities,
|
|
242
|
+
cardinality, and the constraints that are one-way. **No columns, no types.** Those are
|
|
243
|
+
reversible, they come from the repo's conventions, and putting them here produces the diagram
|
|
244
|
+
that disagrees with the schema in three months.
|
|
245
|
+
|
|
246
|
+
`None - no stored-data shape change` is complete.
|
|
247
|
+
|
|
248
|
+
## Surface - the signature
|
|
249
|
+
|
|
250
|
+
Only when this adds or changes an interface something outside it consumes. The **signature, not
|
|
251
|
+
a specification**: route, what goes in, what comes out, which statuses. A payload key stops
|
|
252
|
+
being yours to rename the moment something outside this codebase reads it, which is why this is
|
|
253
|
+
a review and not documentation.
|
|
254
|
+
|
|
255
|
+
Do **not** put check numbers here - checks do not exist yet. Each route's statuses become a set
|
|
256
|
+
row in the `Coverage` join in `checks.md`, which is what proves none of them went unclaimed.
|
|
257
|
+
|
|
258
|
+
`None - nothing consumed outside` is complete.
|
|
259
|
+
|
|
260
|
+
## Landing - the one-way doors
|
|
261
|
+
|
|
262
|
+
A door is one-way when reversing it costs more than a refactor: a persisted schema, a contract
|
|
263
|
+
someone else consumes, a new dependency, a data backfill, and a pattern the codebase does not
|
|
264
|
+
have yet - precedent stops being reversible once the next features have copied it.
|
|
265
|
+
|
|
266
|
+
Each row shows the **literal shape** the next person will copy and what you rejected, named with
|
|
267
|
+
the property that disqualified it: "cleaner" cannot be argued with, "cannot express the next
|
|
268
|
+
state" can. Where the choice was forced rather than compared, name the constraint that forced
|
|
269
|
+
it. Keep the row short - `Relations` and `Surface` carry the diagram and the signature.
|
|
270
|
+
|
|
271
|
+
`None - <why nothing here is one-way>` is a complete answer, and stating it is what makes the
|
|
272
|
+
omission contestable.
|
|
273
|
+
|
|
274
|
+
Two things look like doors and are not. Scope ("V1 does not charge") reverses by doing the next
|
|
275
|
+
slice and already lives in `Out of scope` above. A rule with no mechanism ("one trial per user")
|
|
276
|
+
reverses by changing a condition; it becomes a door only once something persisted enforces it,
|
|
277
|
+
and then the row is the unique index with its literal definition, not the rule.
|
|
278
|
+
|
|
279
|
+
A column or a type belongs here whenever it is a door, and the tell is never the word "column".
|
|
280
|
+
Identifier width is a type and irreversibly a door. Uniqueness decides product behaviour.
|
|
281
|
+
Nullability over a populated table is a data-dependent migration. A field a consumer binds to
|
|
282
|
+
stops being yours to rename. Everything else about the schema - names, indexes, ordinary types -
|
|
283
|
+
is settled while building.
|
|
284
|
+
|
|
285
|
+
Weigh the rejection against what comes after this feature. A constraint that settles the problem
|
|
286
|
+
in front of you can forbid something a later slice needs, and when a door reaches past the
|
|
287
|
+
boundary, say what it closes. When a decision needs a paragraph to justify itself, it is an ADR
|
|
288
|
+
or an RFC and it comes **before** this file: write it, link it, keep the row literal.
|
|
289
|
+
|
|
290
|
+
**A door that reaches past this feature also belongs in `.specs/STATE.md` `## Decisions`** - the
|
|
291
|
+
shape here, the constraint there. See [memory.md](memory.md).
|
|
292
|
+
|
|
293
|
+
## Impact - what gets disturbed
|
|
294
|
+
|
|
295
|
+
What already exists and changes underneath. "Nothing" is a valid answer; a missing row is not.
|
|
296
|
+
|
|
297
|
+
The domain rows matter more than they look. A name leaks - it becomes a class, a column, a
|
|
298
|
+
payload key, a route - so naming is a one-way door that does not look like one. For a term that
|
|
299
|
+
changes meaning, name **who branches on it today**: those callers never appear in the feature's
|
|
300
|
+
diff. Past two terms, give each its own row; six terms in one cell cannot show a gap.
|
|
301
|
+
|
|
302
|
+
The stored-data row is about what runs against existing data, not only about rows you move. A
|
|
303
|
+
unique index over a column that was never unique fails on the first pair that already exists, in
|
|
304
|
+
production, against real rows.
|
|
305
|
+
|
|
306
|
+
# Closure gate
|
|
307
|
+
|
|
308
|
+
Before presenting the plan, five checks. Nothing proceeds unresolved **and** unmarked.
|
|
309
|
+
|
|
310
|
+
1. **Unambiguity and precision.** Every criterion has a single interpretation and a precise
|
|
311
|
+
expected outcome. One that fails either gets resolved with the user, split, or logged as an
|
|
312
|
+
assumption with the chosen interpretation and its rationale.
|
|
313
|
+
2. **Assumptions closure.** Every unresolved decision that surfaced is either resolved or
|
|
314
|
+
recorded in `## Assumptions` with a chosen default and a rationale - including every gray area
|
|
315
|
+
you chose not to raise and every one the user declined. Silently dropped is the failure this
|
|
316
|
+
check exists for; a default nobody saw is fine, a default nobody can find is not.
|
|
317
|
+
3. **Blocking questions are marked as such.** A question that stops a criterion from ever being
|
|
318
|
+
satisfiable is different in kind from one that leaves an error payload undecided. So is one
|
|
319
|
+
that blocks nothing technical and still stops the feature reaching real users - a catalog
|
|
320
|
+
nobody populated, a credential nobody issued. Three kinds: `blocks`, `blocks go-live`,
|
|
321
|
+
`open`.
|
|
322
|
+
4. **Both enumerations are walked, not skimmed.** Every item of every surface present has a
|
|
323
|
+
landing in `## Observable`, and all nine dimensions have one in `checks.md`. A blank in either
|
|
324
|
+
is the failure both lists exist to make visible.
|
|
325
|
+
5. **Every criterion has somewhere to land in the shape.** A criterion whose behaviour crosses
|
|
326
|
+
no hop in `Flow`, touches nothing in `Relations` and appears on no route in `Surface` is
|
|
327
|
+
either out of scope or a gap in the shape. This is the check the two halves being one file
|
|
328
|
+
buys, and it is the reason to write them in one sitting.
|
|
329
|
+
|
|
330
|
+
This gate clarifies existing requirements; it never invents new ones. `Out of scope` and the
|
|
331
|
+
sweep's `n/a` escape are the counterweights.
|
|
332
|
+
|
|
333
|
+
**Run it, do not eyeball it:**
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
python3 <skill-dir>/scripts/validate_plan.py <feature>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
It fails a missing or empty section, a criterion that is not EARS-shaped, an assumption row with
|
|
340
|
+
an empty default or rationale, a malformed requirement ID, an `Observable` row whose landing is
|
|
341
|
+
blank or whose `n/a` carries no reason, a `Landing` row with no literal shape or no rejected
|
|
342
|
+
alternative, a `Relations` block that names columns or types, a `Surface` row
|
|
343
|
+
missing its statuses, and check numbers written into `Surface` before checks exist. It warns on a
|
|
344
|
+
`Flow` hop naming a module marked neither as existing nor as a door. Judgment calls stay yours.
|
|
345
|
+
|
|
346
|
+
## Template: `.specs/features/<feature>/plan.md`
|
|
347
|
+
|
|
348
|
+
`````markdown
|
|
349
|
+
# <Feature>
|
|
350
|
+
|
|
351
|
+
Sources:
|
|
352
|
+
|
|
353
|
+
- <ticket URL / document path / "conversation"> - <what it settles>
|
|
354
|
+
- <design> - **binding for the interface**: screens <ids>, and where the copy lives
|
|
355
|
+
|
|
356
|
+
## Problem
|
|
357
|
+
|
|
358
|
+
<Present tense, no solution inside it: what is true today that should not be, who pays for it,
|
|
359
|
+
what it costs them. The evidence the source gives, copied literally - or a note that it gives
|
|
360
|
+
none.>
|
|
361
|
+
|
|
362
|
+
<What is different for a user when this ships.>
|
|
363
|
+
|
|
364
|
+
## Out of scope
|
|
365
|
+
|
|
366
|
+
| Excluded | Why |
|
|
367
|
+
| --- | --- |
|
|
368
|
+
| <capability> | <reason> |
|
|
369
|
+
|
|
370
|
+
## Assumptions
|
|
371
|
+
|
|
372
|
+
| Assumption | Chosen default | Rationale | Confirmed? |
|
|
373
|
+
| --- | --- | --- | --- |
|
|
374
|
+
| <ambiguity> | <what we will do> | <why> | y/n |
|
|
375
|
+
|
|
376
|
+
**Open questions:** none - all resolved or logged above.
|
|
377
|
+
|
|
378
|
+
<Or, when something genuinely stayed open:>
|
|
379
|
+
|
|
380
|
+
| # | Kind | Question | Until answered |
|
|
381
|
+
| --- | --- | --- | --- |
|
|
382
|
+
| 1 | blocks | <question> | <which criterion cannot be satisfied> |
|
|
383
|
+
| 2 | blocks go-live | <question> | <what cannot be switched on for real users> |
|
|
384
|
+
| 3 | open | <question> | <what stays imprecise, and what was written meanwhile> |
|
|
385
|
+
|
|
386
|
+
## Criteria
|
|
387
|
+
|
|
388
|
+
Grouped by slice - one observable outcome each, never a layer. Numbering runs across the whole
|
|
389
|
+
plan. A screen names its screen and gives every state that matters its own line: empty, loading,
|
|
390
|
+
error, unauthorised.
|
|
391
|
+
|
|
392
|
+
### S1: <slice - the outcome someone can watch> (P1)
|
|
393
|
+
|
|
394
|
+
**Acceptance Criteria**
|
|
395
|
+
|
|
396
|
+
1. WHEN <trigger> THEN the system SHALL <observable outcome with the concrete value>
|
|
397
|
+
2. IF <undesired condition> THEN the system SHALL <response>
|
|
398
|
+
3. WHILE <state holds> the system SHALL <behaviour>
|
|
399
|
+
4. The system SHALL <always-on invariant with its concrete value>
|
|
400
|
+
|
|
401
|
+
**Independent test:** <how to demo this slice alone>
|
|
402
|
+
|
|
403
|
+
### S2: <slice> (P2)
|
|
404
|
+
|
|
405
|
+
**Acceptance Criteria**
|
|
406
|
+
|
|
407
|
+
5. WHEN <trigger> THEN the system SHALL <outcome>
|
|
408
|
+
|
|
409
|
+
**Independent test:** <how to demo>
|
|
410
|
+
|
|
411
|
+
## Traceability
|
|
412
|
+
|
|
413
|
+
| ID | Slice | Criteria | Status |
|
|
414
|
+
| --- | --- | --- | --- |
|
|
415
|
+
| FEAT-01 | S1 | 1, 2, 3 | Pending |
|
|
416
|
+
| FEAT-02 | S2 | 5 | Pending |
|
|
417
|
+
|
|
418
|
+
**ID format:** `CATEGORY-NUMBER`, e.g. `AUTH-01`. **Status:** Pending → In checks →
|
|
419
|
+
Implementing → Verified.
|
|
420
|
+
|
|
421
|
+
## Observable
|
|
422
|
+
|
|
423
|
+
Every item of every surface this feature exposes. `n/a` needs its reason.
|
|
424
|
+
|
|
425
|
+
| Surface | Decision | Landing |
|
|
426
|
+
| --- | --- | --- |
|
|
427
|
+
| screen `<name>` | empty state | AC <n> |
|
|
428
|
+
| screen `<name>` | error state | AC <n> |
|
|
429
|
+
| screen `<name>` | destructive action confirms | existing - <the pattern already in use> |
|
|
430
|
+
| API `<METHOD> /<path>` | error shape and codes | AC <n> |
|
|
431
|
+
| API `<METHOD> /<path>` | versioning | n/a - <why it does not apply> |
|
|
432
|
+
|
|
433
|
+
<Or:> `None - no user-facing surface`
|
|
434
|
+
|
|
435
|
+
## Flow
|
|
436
|
+
|
|
437
|
+
<One or two sentences: what this reuses instead of duplicating.>
|
|
438
|
+
|
|
439
|
+
1. <what enters> -> `<Module>` (exists) - <what it does, what it hands on>
|
|
440
|
+
2. `<Module>` (exists) - <what it does>, persists `<Entity>` (door <n>)
|
|
441
|
+
3. `<Module>` (new, no door - placement per conventions) - <what it decides>
|
|
442
|
+
4. out: `<response>`, and `<Module>` (exists) reads `<field>` on the next request
|
|
443
|
+
|
|
444
|
+
<Or, for most features:> `single module - <Name>`
|
|
445
|
+
|
|
446
|
+
<Or, only when the path branches, fans out, or hands off asynchronously:>
|
|
447
|
+
|
|
448
|
+
```mermaid
|
|
449
|
+
flowchart TD
|
|
450
|
+
IN[what enters] --> V["Module (exists)"]
|
|
451
|
+
V --> A["Module (exists)"]
|
|
452
|
+
V --> B["Module (door 2)"]
|
|
453
|
+
A --> OUT[response]
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Relations
|
|
457
|
+
|
|
458
|
+
```mermaid
|
|
459
|
+
erDiagram
|
|
460
|
+
<Entity> ||--o{ <Entity> : "<verb>"
|
|
461
|
+
<Entity> ||--|| <Entity> : "<field> - unique, door <n>"
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
One-way constraints: <field> unique (door <n>), <field> not null with <value> in the enum
|
|
465
|
+
(door <n>). No columns and no types here.
|
|
466
|
+
|
|
467
|
+
<Or:> `None - no stored-data shape change`
|
|
468
|
+
|
|
469
|
+
## Surface
|
|
470
|
+
|
|
471
|
+
| Route | In | Out | Status |
|
|
472
|
+
| --- | --- | --- | --- |
|
|
473
|
+
| `<METHOD> /<path>` | `<field>`, `<field>` | `<field>` · `<field>` | `<code>`, `<code>`, `<code>` |
|
|
474
|
+
|
|
475
|
+
<Or:> `None - nothing consumed outside`
|
|
476
|
+
|
|
477
|
+
## Landing
|
|
478
|
+
|
|
479
|
+
| One-way door | Literal shape | Alternative rejected |
|
|
480
|
+
| --- | --- | --- |
|
|
481
|
+
| <what> | <enum value, unique index, dependency - the literal a reader copies> | <the option, and the property that disqualified it> |
|
|
482
|
+
|
|
483
|
+
- Nothing else in this change is hard to reverse
|
|
484
|
+
|
|
485
|
+
## Impact
|
|
486
|
+
|
|
487
|
+
| Front | What changes |
|
|
488
|
+
| --- | --- |
|
|
489
|
+
| domain | new term: `<Name>` - <one-line definition>, lives in <module> |
|
|
490
|
+
| domain | existing term: `<Name>` meant <x>, now means <y> - <who branches on it today> |
|
|
491
|
+
| stored data | <backfill now / migrate on read / dual write / nothing to migrate> |
|
|
492
|
+
`````
|
|
493
|
+
|
|
494
|
+
## Then confirm, and only then write checks
|
|
495
|
+
|
|
496
|
+
**This is the one place worth stopping for a human by default.** Present the plan and stop: the
|
|
497
|
+
criteria are what everything downstream traces to, and the doors are the rows that cost the most
|
|
498
|
+
to raise late. Where a `Landing` door has a live alternative, `Relations` changes the shape of
|
|
499
|
+
data that already exists, `Surface` changes something already consumed, or `Impact` names a term
|
|
500
|
+
whose meaning shifts under existing callers - say so explicitly rather than burying it in the
|
|
501
|
+
table, because those are the four a reviewer would want pointed at.
|
|
502
|
+
|
|
503
|
+
Then continue into [checks.md](checks.md). Every route's statuses, every door, and every entity
|
|
504
|
+
here owes a set row or a check there - that derivation is the next step's job, and it is the one
|
|
505
|
+
that catches what this file left implicit.
|
|
506
|
+
|
|
507
|
+
## Notes
|
|
508
|
+
|
|
509
|
+
- **P1 is a vertical slice** - a complete, demo-able outcome, not a layer.
|
|
510
|
+
- **A criterion you cannot imagine a test for is not ready.** You do not name the test here;
|
|
511
|
+
that happens in `checks.md`, against the repo's real test setup.
|
|
512
|
+
- **Priority tags are advisory.** They order the work; they never scale down verification.
|
|
513
|
+
- The two halves are written in one sitting, and the shape is written **after** the criteria -
|
|
514
|
+
the order is what stops a criterion from being invented to justify a component.
|