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.
Files changed (142) hide show
  1. package/.agents/skills/ponytail/SKILL.md +111 -0
  2. package/.agents/skills/ponytail-audit/SKILL.md +37 -0
  3. package/.agents/skills/ponytail-debt/SKILL.md +41 -0
  4. package/.agents/skills/ponytail-gain/SKILL.md +47 -0
  5. package/.agents/skills/ponytail-help/SKILL.md +70 -0
  6. package/.agents/skills/ponytail-review/SKILL.md +52 -0
  7. package/.agents/skills/prompt-review/SKILL.md +60 -0
  8. package/.agents/skills/wtk/SKILL.md +64 -0
  9. package/.agents/skills/wtk/references/artifacts.md +82 -0
  10. package/.agents/skills/wtk/references/evidence.md +68 -0
  11. package/.agents/skills/wtk/references/git.md +75 -0
  12. package/.agents/skills/wtk/references/test-contract.md +76 -0
  13. package/.agents/skills/wtk/references/validation.md +127 -0
  14. package/.agents/skills/wtk-config/SKILL.md +102 -0
  15. package/.agents/skills/wtk-config/assets/agents/claude/deep-reviewer.md +40 -0
  16. package/.agents/skills/wtk-config/assets/agents/claude/designer.md +57 -0
  17. package/.agents/skills/wtk-config/assets/agents/claude/explorer.md +41 -0
  18. package/.agents/skills/wtk-config/assets/agents/claude/implementer.md +57 -0
  19. package/.agents/skills/wtk-config/assets/agents/claude/planner.md +68 -0
  20. package/.agents/skills/wtk-config/assets/agents/claude/verifier.md +80 -0
  21. package/.agents/skills/wtk-config/assets/agents/codex/deep-reviewer.toml +41 -0
  22. package/.agents/skills/wtk-config/assets/agents/codex/designer.toml +55 -0
  23. package/.agents/skills/wtk-config/assets/agents/codex/explorer.toml +35 -0
  24. package/.agents/skills/wtk-config/assets/agents/codex/implementer.toml +52 -0
  25. package/.agents/skills/wtk-config/assets/agents/codex/planner.toml +66 -0
  26. package/.agents/skills/wtk-config/assets/agents/codex/verifier.toml +78 -0
  27. package/.agents/skills/wtk-config/assets/agents/cursor/deep-reviewer.md +38 -0
  28. package/.agents/skills/wtk-config/assets/agents/cursor/designer.md +55 -0
  29. package/.agents/skills/wtk-config/assets/agents/cursor/explorer.md +39 -0
  30. package/.agents/skills/wtk-config/assets/agents/cursor/implementer.md +55 -0
  31. package/.agents/skills/wtk-config/assets/agents/cursor/planner.md +66 -0
  32. package/.agents/skills/wtk-config/assets/agents/cursor/verifier.md +79 -0
  33. package/.agents/skills/wtk-config/scripts/ad-index.py +123 -0
  34. package/.agents/skills/wtk-config/scripts/repository_intelligence.py +671 -0
  35. package/.agents/skills/wtk-config/scripts/workflow_config.py +972 -0
  36. package/.agents/skills/wtk-deep-review/SKILL.md +164 -0
  37. package/.agents/skills/wtk-deep-review/assets/PROMPT.md +56 -0
  38. package/.agents/skills/wtk-deep-review/assets/REVIEW_UI.html +1340 -0
  39. package/.agents/skills/wtk-deep-review/assets/findings.schema.json +113 -0
  40. package/.agents/skills/wtk-deep-review/references/context-pack.md +88 -0
  41. package/.agents/skills/wtk-deep-review/references/orchestration.md +164 -0
  42. package/.agents/skills/wtk-deep-review/references/output-contracts.md +149 -0
  43. package/.agents/skills/wtk-deep-review/references/publish-github.md +83 -0
  44. package/.agents/skills/wtk-deep-review/references/state-and-learnings.md +64 -0
  45. package/.agents/skills/wtk-deep-review/references/subagent-runtimes.md +33 -0
  46. package/.agents/skills/wtk-deep-review/references/taxonomy.md +70 -0
  47. package/.agents/skills/wtk-deep-review/scripts/_common.py +371 -0
  48. package/.agents/skills/wtk-deep-review/scripts/build_jobs.py +561 -0
  49. package/.agents/skills/wtk-deep-review/scripts/build_knowledge.py +349 -0
  50. package/.agents/skills/wtk-deep-review/scripts/build_manifest.py +479 -0
  51. package/.agents/skills/wtk-deep-review/scripts/graft_context.py +160 -0
  52. package/.agents/skills/wtk-deep-review/scripts/graphify_context.py +75 -0
  53. package/.agents/skills/wtk-deep-review/scripts/merge_findings.py +313 -0
  54. package/.agents/skills/wtk-deep-review/scripts/render_html.py +240 -0
  55. package/.agents/skills/wtk-deep-review/scripts/render_review.py +282 -0
  56. package/.agents/skills/wtk-deep-review/scripts/run_jobs.py +436 -0
  57. package/.agents/skills/wtk-deep-review/scripts/token_metrics.py +369 -0
  58. package/.agents/skills/wtk-discover/SKILL.md +270 -0
  59. package/.agents/skills/wtk-discover/references/document-format.md +149 -0
  60. package/.agents/skills/wtk-implement/SKILL.md +95 -0
  61. package/.agents/skills/wtk-implement/references/checklist-format.md +116 -0
  62. package/.agents/skills/wtk-implement/references/screens.md +129 -0
  63. package/.agents/skills/wtk-implement/references/test-policy.md +152 -0
  64. package/.agents/skills/wtk-implement/references/verify.md +297 -0
  65. package/.agents/skills/wtk-knowledge-check/SKILL.md +16 -0
  66. package/.agents/skills/wtk-knowledge-check/scripts/check.ts +502 -0
  67. package/.agents/skills/wtk-knowledge-check/scripts/cli.ts +32 -0
  68. package/.agents/skills/wtk-knowledge-check/scripts/frontmatter.ts +44 -0
  69. package/.agents/skills/wtk-lean/.skill-meta.json +6 -0
  70. package/.agents/skills/wtk-lean/NOTICE.md +18 -0
  71. package/.agents/skills/wtk-lean/SKILL.md +137 -0
  72. package/.agents/skills/wtk-lean/references/build.md +168 -0
  73. package/.agents/skills/wtk-lean/references/checks.md +253 -0
  74. package/.agents/skills/wtk-lean/references/memory.md +156 -0
  75. package/.agents/skills/wtk-lean/references/plan.md +514 -0
  76. package/.agents/skills/wtk-lean/references/verify.md +336 -0
  77. package/.agents/skills/wtk-lean/scripts/check_commit.py +121 -0
  78. package/.agents/skills/wtk-lean/scripts/fixtures/checks.md +98 -0
  79. package/.agents/skills/wtk-lean/scripts/fixtures/plan.md +119 -0
  80. package/.agents/skills/wtk-lean/scripts/fixtures/verification.md +41 -0
  81. package/.agents/skills/wtk-lean/scripts/lessons.py +412 -0
  82. package/.agents/skills/wtk-lean/scripts/selftest.py +401 -0
  83. package/.agents/skills/wtk-lean/scripts/validate_checks.py +417 -0
  84. package/.agents/skills/wtk-lean/scripts/validate_plan.py +580 -0
  85. package/.agents/skills/wtk-lean/scripts/validate_verification.py +357 -0
  86. package/.agents/skills/wtk-plan/SKILL.md +102 -0
  87. package/.agents/skills/wtk-plan/references/document-format.md +222 -0
  88. package/.agents/skills/wtk-qa/SKILL.md +14 -0
  89. package/.agents/skills/wtk-qa-execute/SKILL.md +115 -0
  90. package/.agents/skills/wtk-qa-execute/references/fix-loop.md +23 -0
  91. package/.agents/skills/wtk-qa-execute/references/session-protocol.md +25 -0
  92. package/.agents/skills/wtk-qa-plan/SKILL.md +111 -0
  93. package/.agents/skills/wtk-qa-plan/references/profile.md +35 -0
  94. package/.agents/skills/wtk-ship/SKILL.md +49 -0
  95. package/.agents/skills/wtk-ship/remediation.py +170 -0
  96. package/.agents/skills/wtk-ship/scripts/close_feature.py +83 -0
  97. package/.agents/skills/wtk-ship/scripts/review_convergence.py +373 -0
  98. package/.wtk.toml.example +93 -0
  99. package/AGENTS.md +129 -0
  100. package/NOTICE.md +12 -0
  101. package/README.md +440 -0
  102. package/bin/wtk.js +25 -0
  103. package/docs/toolkit/README.md +56 -0
  104. package/docs/toolkit/decisions.md +61 -0
  105. package/docs/toolkit/guidelines/CONTEXT-BUDGET.md +64 -0
  106. package/docs/toolkit/guidelines/DX.md +67 -0
  107. package/docs/toolkit/guidelines/FRONTEND.md +73 -0
  108. package/docs/toolkit/guidelines/KNOWLEDGE-WIKI.md +128 -0
  109. package/docs/toolkit/guidelines/MODELING.md +134 -0
  110. package/docs/toolkit/guidelines/QA-EXECUTION.md +46 -0
  111. package/docs/toolkit/guidelines/QA-SCENARIOS.md +165 -0
  112. package/docs/toolkit/guidelines/REVIEW-ROUNDS.md +160 -0
  113. package/docs/toolkit/guidelines/SECURITY.md +160 -0
  114. package/docs/toolkit/guidelines/UI-UX.md +117 -0
  115. package/docs/toolkit/guidelines/WORKFLOW-MEMORY.md +73 -0
  116. package/docs/toolkit/guidelines.md +50 -0
  117. package/docs/toolkit/loop.md +77 -0
  118. package/docs/toolkit/purpose.md +51 -0
  119. package/docs/toolkit/repository-intelligence.md +53 -0
  120. package/docs/toolkit/reviews.md +77 -0
  121. package/knowledge/AGENTS.md +316 -0
  122. package/knowledge/raw/README.md +17 -0
  123. package/package.json +74 -0
  124. package/scripts/install_security_skills.py +816 -0
  125. package/scripts/installer/engine.js +254 -0
  126. package/scripts/installer/knowledge.js +28 -0
  127. package/scripts/installer/packets.js +77 -0
  128. package/scripts/installer/terminal.js +98 -0
  129. package/scripts/installer/transaction.js +75 -0
  130. package/skills-lock.json +92 -0
  131. package/templates/adoption/agents/core.md +23 -0
  132. package/templates/adoption/agents/quality.md +3 -0
  133. package/templates/adoption/knowledge/wiki/architecture/index.md +3 -0
  134. package/templates/adoption/knowledge/wiki/decisions/index.md +3 -0
  135. package/templates/adoption/knowledge/wiki/design/index.md +3 -0
  136. package/templates/adoption/knowledge/wiki/domain/index.md +3 -0
  137. package/templates/adoption/knowledge/wiki/index.md +3 -0
  138. package/templates/adoption/knowledge/wiki/log.md +3 -0
  139. package/templates/adoption/knowledge/wiki/open-questions/index.md +3 -0
  140. package/templates/adoption/knowledge/wiki/product/index.md +3 -0
  141. package/templates/adoption/knowledge/wiki/research/index.md +3 -0
  142. 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.