shapeup-sdlc 1.6.2
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/.claude/settings.local.example.json +20 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.env.shapeup.example +14 -0
- package/AGENTS.md +133 -0
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/SECURITY.md +72 -0
- package/bin/init.mjs +329 -0
- package/commands/build.md +14 -0
- package/commands/eval.md +15 -0
- package/commands/hammer.md +12 -0
- package/commands/orient.md +11 -0
- package/commands/qa.md +12 -0
- package/commands/retro.md +13 -0
- package/commands/scopes.md +14 -0
- package/commands/shape.md +12 -0
- package/commands/ship.md +53 -0
- package/commands/wire.md +11 -0
- package/hooks/anti-rationalization.mjs +244 -0
- package/hooks/compact-snapshot.mjs +47 -0
- package/hooks/gate-deadline.mjs +151 -0
- package/hooks/gate-intake.mjs +110 -0
- package/hooks/gate-l2.mjs +161 -0
- package/hooks/gate-zerowork.mjs +264 -0
- package/hooks/hooks.json +118 -0
- package/hooks/lib/decision.mjs +183 -0
- package/hooks/safety-spine.mjs +296 -0
- package/hooks/sandbox-guard.mjs +172 -0
- package/hooks/session-rehydrate.mjs +109 -0
- package/hooks/slop-cleaner.mjs +176 -0
- package/oracles/_shared.mjs +46 -0
- package/oracles/http-oracle.mjs +155 -0
- package/oracles/index.mjs +36 -0
- package/oracles/process-oracle.mjs +146 -0
- package/oracles/snapshot-oracle.mjs +119 -0
- package/oracles/test-oracle.mjs +138 -0
- package/package.json +49 -0
- package/skills/advisor-protocol/SKILL.md +171 -0
- package/skills/ba-pitch-analyzer/SKILL.md +175 -0
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +84 -0
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +65 -0
- package/skills/ba-pitch-analyzer/assets/templates/assess-report.tmpl.md +127 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/be-service.contract.tmpl.md +62 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/offline-storage.contract.tmpl.md +92 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +66 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/context-map.tmpl.md +64 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/event-choreography.tmpl.md +77 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +104 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +79 -0
- package/skills/ba-pitch-analyzer/assets/templates/domain-model.tmpl.md +79 -0
- package/skills/ba-pitch-analyzer/assets/templates/feedback.tmpl.md +87 -0
- package/skills/ba-pitch-analyzer/assets/templates/integration.tmpl.md +64 -0
- package/skills/ba-pitch-analyzer/assets/templates/run-state.tmpl.md +77 -0
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +105 -0
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +215 -0
- package/skills/ba-pitch-analyzer/assets/templates/task-board.tmpl.md +35 -0
- package/skills/ba-pitch-analyzer/assets/templates/task-spike.tmpl.md +86 -0
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +84 -0
- package/skills/ba-pitch-analyzer/assets/templates/usecase.tmpl.md +105 -0
- package/skills/ba-pitch-analyzer/assets/templates/ux-behavior.tmpl.md +65 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +152 -0
- package/skills/ba-pitch-analyzer/references/ddd-patterns.md +173 -0
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +275 -0
- package/skills/ba-pitch-analyzer/references/integration-analysis.md +152 -0
- package/skills/ba-pitch-analyzer/references/task-generation.md +623 -0
- package/skills/ba-pitch-analyzer/references/test-surface.md +102 -0
- package/skills/ba-pitch-analyzer/references/ux-behavior-patterns.md +269 -0
- package/skills/ba-pitch-analyzer/scripts/board-derive.mjs +241 -0
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +239 -0
- package/skills/coach/SKILL.md +202 -0
- package/skills/orient/SKILL.md +262 -0
- package/skills/qa-edge-hunter/SKILL.md +369 -0
- package/skills/scope-architect/SKILL.md +111 -0
- package/skills/scope-hammer/SKILL.md +186 -0
- package/skills/shapeup/SKILL.md +403 -0
- package/skills/shapeup/resources/breadboard-reflection.md +88 -0
- package/skills/shapeup/resources/breadboarding.md +334 -0
- package/skills/shapeup/resources/context-compaction.md +240 -0
- package/skills/shapeup/resources/framing-doc.md +92 -0
- package/skills/shapeup/resources/kickoff-doc.md +95 -0
- package/skills/shapeup/resources/shaping.md +194 -0
- package/skills/shapeup/resources/spike.md +86 -0
- package/skills/solution-architect/SKILL.md +129 -0
- package/skills/spec-evaluator/README.md +93 -0
- package/skills/spec-evaluator/SKILL.md +213 -0
- package/skills/spec-evaluator/references/anti-leniency.md +50 -0
- package/skills/spec-evaluator/references/dimension-contract.md +126 -0
- package/skills/spec-evaluator/references/dimensions/_registry.md +40 -0
- package/skills/spec-evaluator/references/dimensions/completeness.md +102 -0
- package/skills/spec-evaluator/references/dimensions/integration.md +129 -0
- package/skills/spec-evaluator/references/dimensions/performance.md +48 -0
- package/skills/spec-evaluator/references/dimensions/security.md +60 -0
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +134 -0
- package/skills/spec-evaluator/references/dimensions/tdd-surface.md +110 -0
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +100 -0
- package/skills/spec-evaluator/references/probing.md +177 -0
- package/skills/spec-evaluator/references/report-schema.md +101 -0
- package/skills/spec-evaluator/references/verdict-ledger.md +92 -0
- package/skills/spec-evaluator/scripts/verdict-ledger.mjs +166 -0
- package/skills/task-executor/SKILL.md +194 -0
- package/skills/tech-lead/README.md +71 -0
- package/skills/tech-lead/SKILL.md +459 -0
- package/skills/tech-lead/references/delegation.md +254 -0
- package/skills/tech-lead/references/gates.md +379 -0
- package/skills/tech-lead/references/invocation.md +45 -0
- package/skills/tech-lead/references/ledger-schema.md +214 -0
- package/skills/tech-lead/references/round-protocol.md +184 -0
- package/skills/tech-lead/references/state-model.md +66 -0
- package/skills/tech-lead/references/tiny-lane.md +52 -0
- package/skills/tech-lead/schemas/domain.schema.json +2294 -0
- package/skills/tech-lead/schemas/gate-answers.schema.json +92 -0
- package/skills/tech-lead/schemas/work-order.schema.json +21 -0
- package/skills/tech-lead/schemas/work-result.schema.json +40 -0
- package/skills/tech-lead/scripts/aegis-digest.mjs +124 -0
- package/skills/tech-lead/scripts/budget-check.mjs +156 -0
- package/skills/tech-lead/scripts/compile-order.mjs +518 -0
- package/skills/tech-lead/scripts/fit-check.mjs +196 -0
- package/skills/tech-lead/scripts/gate-answers.mjs +338 -0
- package/skills/tech-lead/scripts/ingest-result.mjs +270 -0
- package/skills/tech-lead/scripts/init-run.mjs +326 -0
- package/skills/tech-lead/scripts/lib/argv.mjs +224 -0
- package/skills/tech-lead/scripts/lib/contract-md.mjs +481 -0
- package/skills/tech-lead/scripts/lib/is-main.mjs +82 -0
- package/skills/tech-lead/scripts/lib/paths.mjs +278 -0
- package/skills/tech-lead/scripts/lib/ratchet-tree.mjs +112 -0
- package/skills/tech-lead/scripts/run-snapshot.mjs +269 -0
- package/skills/tech-lead/scripts/ship-report.mjs +275 -0
- package/skills/tech-lead/scripts/stats.mjs +423 -0
- package/skills/tech-lead/scripts/t0-verify.mjs +470 -0
- package/skills/tech-lead/scripts/trace-lint.mjs +367 -0
- package/skills/tech-lead/scripts/validate-envelope.mjs +283 -0
- package/skills/translator/README.md +66 -0
- package/skills/translator/SKILL.md +258 -0
- package/skills/translator/references/preservation-rules.md +102 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
# Task Generation Rules
|
|
2
|
+
|
|
3
|
+
Reference for Phase 6. Read before writing any `TASK-NNN` files.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Principle
|
|
8
|
+
|
|
9
|
+
A task is the smallest unit of work that:
|
|
10
|
+
1. Can be verified independently (run a command → pass/fail)
|
|
11
|
+
2. Touches one package or one concern
|
|
12
|
+
3. Can be committed as a standalone change
|
|
13
|
+
|
|
14
|
+
If a task can't be verified by running `pnpm test` or `pnpm typecheck`, it's too vague.
|
|
15
|
+
If a task touches 3 packages, it needs to be split.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Task Types
|
|
20
|
+
|
|
21
|
+
| Type | Purpose | Output | AC Style |
|
|
22
|
+
|------|---------|--------|----------|
|
|
23
|
+
| `FEAT` | New capability | Shippable code | Commands + observable outcomes |
|
|
24
|
+
| `FIX` | Bug correction | Patched code | Regression test passes |
|
|
25
|
+
| `CHORE` | Non-functional work | Config / tooling change | Command exits 0 |
|
|
26
|
+
| `MIGRATION` | DB schema change | Migration file | Migration runs + rolls back |
|
|
27
|
+
| `DOCS` | Documentation only | Markdown files | File exists + content check |
|
|
28
|
+
| `SPIKE` | Feasibility investigation | **Decision document** | All questions answered with citation |
|
|
29
|
+
|
|
30
|
+
**SPIKE is not a FEAT.** It produces knowledge, not code. Time-boxed, not estimated.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## SPIKE Task Rules
|
|
35
|
+
|
|
36
|
+
A SPIKE task MUST be generated whenever Phase 1b detected unverified third-party capabilities.
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
---
|
|
40
|
+
id: TASK-[NNN]
|
|
41
|
+
type: SPIKE
|
|
42
|
+
slug: spike-[api-name]-feasibility
|
|
43
|
+
time_box_hours: 4 # hard cap — if not answerable in 4h, escalate to PO
|
|
44
|
+
api_ref: API-[NN] # references api-feasibility.md investigation block
|
|
45
|
+
blocks: # REQUIRED — every task that cannot start until SPIKE done
|
|
46
|
+
- TASK-[NNN]
|
|
47
|
+
- TASK-[NNN]
|
|
48
|
+
status: todo
|
|
49
|
+
priority: 1 # always priority 1 — nothing can proceed without it
|
|
50
|
+
package: research # not a code package — signals non-implementation task
|
|
51
|
+
estimated_hours: ~ # leave blank — SPIKE uses time_box_hours instead
|
|
52
|
+
linked_docs:
|
|
53
|
+
- "[[api-feasibility#API-NN]]"
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Definition of Done for every SPIKE:**
|
|
58
|
+
```markdown
|
|
59
|
+
## Definition of Done
|
|
60
|
+
- [ ] All questions in [[api-feasibility#API-NN]] answered with direct source URL citation
|
|
61
|
+
- [ ] Contract file [[contracts/[repo].contract.md]] updated — no remaining ⏳ TBD fields
|
|
62
|
+
- [ ] If capability confirmed: tasks in `blocks` list updated to remove ⏳ BLOCKED annotation
|
|
63
|
+
- [ ] If capability NOT confirmed: PO notified with fallback scope options from api-feasibility.md
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Verification method section (mandatory for SPIKE):**
|
|
67
|
+
```markdown
|
|
68
|
+
## Verification Method
|
|
69
|
+
1. Read [Official Docs URL] — section: [specific section name]
|
|
70
|
+
2. If not documented: search [Community/GitHub/Discord] for [specific query]
|
|
71
|
+
3. If ambiguous: write minimal curl/SDK test against sandbox environment
|
|
72
|
+
4. Record finding in api-feasibility.md API-NN block before closing task
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Contract-First Rule
|
|
78
|
+
|
|
79
|
+
Before generating any implementation task that touches a repository:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Step 1: Verify contracts/[repo].contract.md exists
|
|
83
|
+
→ If missing: generate TASK-NNN-[repo]-contract-stub BEFORE the implementation task
|
|
84
|
+
|
|
85
|
+
Step 2: Check contract source type
|
|
86
|
+
→ be-service or offline-storage: proceed normally
|
|
87
|
+
→ third-party-api AND contract has ⏳ TBD fields:
|
|
88
|
+
Add ⏳ BLOCKED annotation to implementation task
|
|
89
|
+
Add implementation task ID to SPIKE blocks: list
|
|
90
|
+
|
|
91
|
+
Step 3: Implementation task Context section MUST include:
|
|
92
|
+
"Implement [RepoName] per [[contracts/[repo].contract.md]]"
|
|
93
|
+
|
|
94
|
+
Step 4: Implementation task AC MUST include these three lines:
|
|
95
|
+
- [ ] Request shape matches [[contracts/[repo].contract.md#Request]] table
|
|
96
|
+
- [ ] Response mapping matches [[contracts/[repo].contract.md#Response]] table
|
|
97
|
+
- [ ] All error codes in contract Error Cases table are handled
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Mandatory Ordering Rules
|
|
103
|
+
|
|
104
|
+
Tasks MUST follow this sequence within a feature. Never invert layers:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Layer 1: Shared types & DB schema (packages/shared, migrations)
|
|
108
|
+
Layer 2: Domain model & repository (packages/shared or apps/api/domain)
|
|
109
|
+
Layer 3: Application / use cases (apps/api/usecases or apps/api/services)
|
|
110
|
+
Layer 4: Infrastructure / HTTP (apps/api/routes, apps/api/controllers)
|
|
111
|
+
Layer 5: Frontend integration (apps/web/features/...)
|
|
112
|
+
Layer 6: End-to-end tests (e2e/, integration tests)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Every task at Layer N depends on all relevant tasks at Layer N-1.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Lens-Aware Task Layer Breakdown
|
|
120
|
+
|
|
121
|
+
The layer structure differs by lens. Read the order's `payload.lens` for the active lens
|
|
122
|
+
before generating tasks.
|
|
123
|
+
|
|
124
|
+
### LITE — Mobile-first layer breakdown
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Layer 1: Local schema / types (packages/shared — entity shapes only, no repo interfaces)
|
|
128
|
+
Layer 2: Local state management (apps/mobile — Zustand/Redux store, offline cache)
|
|
129
|
+
Layer 3: API call stubs (apps/mobile — typed fetch wrappers, no contract defined yet)
|
|
130
|
+
Layer 4: UI components (apps/mobile — screens, navigation, platform variants)
|
|
131
|
+
Layer 5: Integration smoke tests (apps/mobile — happy path only, mocked API responses)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`use_case_refs` in LITE tasks maps to: *"User does X in screen Y"* — mobile perspective.
|
|
135
|
+
TypeScript Input/Output interfaces in UC files are **optional** in LITE (no API contract).
|
|
136
|
+
Tasks do NOT reference `contracts/` — use `[[ux-behavior#ScreenName]]` instead.
|
|
137
|
+
|
|
138
|
+
### STANDARD — App ↔ API layer breakdown
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Layer 1: Shared schema / DB types (packages/shared — full entity + VO types)
|
|
142
|
+
Layer 2: Domain model (packages/shared or apps/api/domain)
|
|
143
|
+
Layer 3: Repository contracts (contracts/ — one per repo interface)
|
|
144
|
+
Layer 4: Application / use cases (apps/api/usecases — API perspective)
|
|
145
|
+
Layer 5: HTTP layer (apps/api/routes, controllers)
|
|
146
|
+
Layer 6: Mobile integration (apps/mobile — consumes contracts, replaces stubs)
|
|
147
|
+
Layer 7: End-to-end tests (e2e/ — contract + integration verified)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`use_case_refs` in STANDARD tasks maps to: *"App calls endpoint X → API does Y"*.
|
|
151
|
+
Every task at Layer 4+ MUST reference `[[contracts/[repo].contract.md]]`.
|
|
152
|
+
|
|
153
|
+
### LITE → STANDARD Reconciliation Pass
|
|
154
|
+
|
|
155
|
+
When upgrading, use cases with mobile perspective need an **API Contract sub-section added**:
|
|
156
|
+
|
|
157
|
+
```markdown
|
|
158
|
+
## API Contract ← ADD THIS to existing UC files during reconciliation
|
|
159
|
+
> Added during STANDARD upgrade — original mobile steps preserved above.
|
|
160
|
+
|
|
161
|
+
POST /[resource]
|
|
162
|
+
Request → [[contracts/[repo].contract.md#Request]]
|
|
163
|
+
Response → [[contracts/[repo].contract.md#Response]]
|
|
164
|
+
Errors → [[contracts/[repo].contract.md#Error-Cases]]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Do NOT overwrite existing Steps section — append API Contract sub-section only.
|
|
168
|
+
Report reconciled files in the WorkResult's `deviations[]` with note `reconciled-by-upgrade`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Task Decomposition Decision Tree
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Is the work in this task > 8 hours?
|
|
176
|
+
├── YES → Split into smaller tasks
|
|
177
|
+
└── NO → Continue
|
|
178
|
+
|
|
179
|
+
Does the task touch more than one package?
|
|
180
|
+
├── YES → Split by package
|
|
181
|
+
└── NO → Continue
|
|
182
|
+
|
|
183
|
+
Does the task have two independent concerns?
|
|
184
|
+
(e.g., "create schema AND implement service")
|
|
185
|
+
├── YES → Split by concern
|
|
186
|
+
└── NO → Task is appropriately scoped
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Acceptance Criteria Rules
|
|
192
|
+
|
|
193
|
+
Every criterion must be checkable by a command or observable action:
|
|
194
|
+
|
|
195
|
+
```markdown
|
|
196
|
+
# ✅ Good — verifiable
|
|
197
|
+
- [ ] `pnpm --filter shared typecheck` exits 0
|
|
198
|
+
- [ ] `pnpm --filter api test src/domain/order.test.ts` passes
|
|
199
|
+
- [ ] `GET /api/orders/:id` returns 200 with `{ id, status, totalAmount }`
|
|
200
|
+
- [ ] Migration file exists at `packages/shared/drizzle/migrations/XXXXXX_add_orders.sql`
|
|
201
|
+
- [ ] Type `Order` is exported from `packages/shared/index.ts`
|
|
202
|
+
|
|
203
|
+
# ❌ Bad — not verifiable
|
|
204
|
+
- [ ] The order aggregate is well-designed
|
|
205
|
+
- [ ] Code follows DDD principles
|
|
206
|
+
- [ ] Service is implemented correctly
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## AC Trigger Matrix
|
|
212
|
+
|
|
213
|
+
Phase 6 applies this matrix to determine which AC sub-sections are REQUIRED
|
|
214
|
+
for each task. SKILL self-checks during generation — do not skip.
|
|
215
|
+
|
|
216
|
+
| Trigger Signal | ac_inverse | ac_empty_state | ac_boundary |
|
|
217
|
+
|----------------|:----------:|:--------------:|:-----------:|
|
|
218
|
+
| `layer: ui` + keyword `show/hide/display/visible/render` | ✓ | | |
|
|
219
|
+
| `task.type: FIX` + `layer: ui` — unconditional, no keyword required | ✓ | | |
|
|
220
|
+
| `layer: repository` or `layer: api` + keyword `fetch/load/list` | | ✓ | |
|
|
221
|
+
| `layer: ui` + description references data prop (`user.X`, `list.length`, `item?.field`) | | ✓ | |
|
|
222
|
+
| Any layer + numeric value, `size`, `limit`, `max`, `min` in description | | | ✓ |
|
|
223
|
+
|
|
224
|
+
**Invariant-backed regression (unconditional):**
|
|
225
|
+
A task generated to verify a UC `[INV-NN]` (its Context wikilinks `[[usecases/UC-Name#Invariants]]`)
|
|
226
|
+
MUST carry a regression-style `## Acceptance Criteria` whose every item is verifiable by command —
|
|
227
|
+
e.g. load a pre-change fixture → assert no throw, assert count preserved, assert no stray data.
|
|
228
|
+
A vague AC like "old board still works" FAILS L3-04 and the executor will gate-reject it.
|
|
229
|
+
This is the cost of absorbing the invariant into AC at *generation* time so the executor needs
|
|
230
|
+
zero awareness of where the invariant lives (Steps vs Invariants) — it just reads the task file.
|
|
231
|
+
|
|
232
|
+
**Exemptions — AC sub-sections are OPTIONAL (no L3 penalty if absent):**
|
|
233
|
+
```
|
|
234
|
+
task.type IN [INFRA, CONFIG, MIGRATION, DOCS]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Dependency-blocked AC:**
|
|
238
|
+
If a required AC sub-section cannot be written because a dependency task is
|
|
239
|
+
not yet implemented (e.g., `ac_empty_state` requires a repository that doesn't
|
|
240
|
+
exist yet), annotate as follows instead of leaving the slot empty:
|
|
241
|
+
```markdown
|
|
242
|
+
- [ ] ⏳ BLOCKED: Empty state test pending [[TASK-NNN]] (repository not yet available)
|
|
243
|
+
```
|
|
244
|
+
Blocked ACs are captured in `scope-summary.md` as blockers — NOT penalized in L3.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## BDD Scenario Rules
|
|
249
|
+
|
|
250
|
+
`### 🧪 BDD Scenarios` is REQUIRED in `## Acceptance Criteria` when:
|
|
251
|
+
- `task.type: FEAT` AND (task has a user-actor OR task crosses a service/API/repository boundary)
|
|
252
|
+
|
|
253
|
+
**Exemptions:** CHORE · DOCS · MIGRATION · INFRA · SPIKE
|
|
254
|
+
|
|
255
|
+
**Format:**
|
|
256
|
+
```
|
|
257
|
+
**Scenario: [Plain-English name]**
|
|
258
|
+
Given [precondition — who/what is in what state]
|
|
259
|
+
When [actor performs specific action]
|
|
260
|
+
Then [expected observable outcome]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Rules:
|
|
264
|
+
- Minimum 1 scenario (happy path). Maximum 3 per task — fewer, more signal-rich.
|
|
265
|
+
- Each scenario must map to ≥1 item in `### ✅ Baseline` or `### 🔁 Inverse Conditions`.
|
|
266
|
+
- Scenarios describe behavior, not implementation — no class names or method calls.
|
|
267
|
+
- Never invent behavior not traceable to UC steps, domain model, or ux-behavior.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Integration Flow Rules
|
|
272
|
+
|
|
273
|
+
`### 🔗 Integration Flow` is REQUIRED when the task crosses ≥1 service boundary:
|
|
274
|
+
- Layer 4+ tasks (HTTP controllers and above in STANDARD)
|
|
275
|
+
- Any task referencing an external service or third-party API
|
|
276
|
+
- Frontend tasks that call an API endpoint
|
|
277
|
+
|
|
278
|
+
**Format:**
|
|
279
|
+
```
|
|
280
|
+
**[Source layer] → [Target layer/service]**
|
|
281
|
+
Given [upstream caller/actor is in [state]]
|
|
282
|
+
When [action triggers at [layer — e.g. POST /api/resource or Repository.save()]]
|
|
283
|
+
Then [downstream side effect — DB row, event, response shape]
|
|
284
|
+
And [caller receives — HTTP status + response body shape]
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Rules:
|
|
288
|
+
- Name layers and surfaces explicitly: `POST /api/boards → BoardRepository.save() → DB: boards`
|
|
289
|
+
- Cover only the call chain this task owns — not the whole system.
|
|
290
|
+
- If a SPIKE is outstanding for this boundary: `⏳ UNVERIFIED — pending [[TASK-NNN-spike]]`
|
|
291
|
+
- Omit section (and note as blocked) if the integration design is not yet resolved.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Non-Go Section (Mandatory)
|
|
296
|
+
|
|
297
|
+
Every task must explicitly state what is NOT in scope:
|
|
298
|
+
|
|
299
|
+
```markdown
|
|
300
|
+
## Non-Go (not in this task)
|
|
301
|
+
- Repository implementation → TASK-002
|
|
302
|
+
- API endpoints → TASK-004
|
|
303
|
+
- Frontend components → TASK-007
|
|
304
|
+
- Error handling for edge case X → TASK-009
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
This prevents Claude Code from over-implementing and breaking task boundaries.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Context Section Format
|
|
312
|
+
|
|
313
|
+
The Context section gives Claude Code the minimum information to implement:
|
|
314
|
+
|
|
315
|
+
```markdown
|
|
316
|
+
## Context
|
|
317
|
+
Implement the `Order` aggregate as defined in [[domain-model#Aggregate-Order]].
|
|
318
|
+
The aggregate root must enforce the invariant: totalAmount = sum of lineItem subtotals.
|
|
319
|
+
See [[usecases/UC-CreateOrder#Input]] for the data shape entering the system.
|
|
320
|
+
Existing `User` entity is in `packages/shared/src/domain/user.ts` — reference for style.
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Rules:
|
|
324
|
+
- Always wikilink to the source spec (not re-describe it)
|
|
325
|
+
- Point to existing code as style reference
|
|
326
|
+
- Mention constraints that aren't in the spec doc
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Implementation Notes Format
|
|
331
|
+
|
|
332
|
+
Optional hints — only include when there's a non-obvious implementation decision:
|
|
333
|
+
|
|
334
|
+
```markdown
|
|
335
|
+
## Implementation Notes
|
|
336
|
+
- Use `integer` for all monetary amounts (VND, no decimal). Never `float`.
|
|
337
|
+
- Drizzle enum: `pgEnum('order_status', ['draft', 'placed', 'paid', 'failed'])`
|
|
338
|
+
- Export both the table schema AND the inferred type:
|
|
339
|
+
`export type Order = typeof orders.$inferSelect`
|
|
340
|
+
- Do NOT use Drizzle relations yet — that comes in TASK-003
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Task Numbering and Naming
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
TASK-001-[slug].md
|
|
349
|
+
TASK-002-[slug].md
|
|
350
|
+
...
|
|
351
|
+
TASK-010-[slug].md ← zero-pad to 3 digits for sort order
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Slug rules:
|
|
355
|
+
- Verb-noun format: `domain-schema`, `order-repository`, `create-order-usecase`
|
|
356
|
+
- No package name in slug (it's in frontmatter)
|
|
357
|
+
- Max 4 words
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Link-Field Integrity (v3.3)
|
|
362
|
+
|
|
363
|
+
**`depends_on` is the single authoritative edge; `unlocks` is derived, never hand-authored.**
|
|
364
|
+
On EVERY board write — analyze, generate-board, reconcile — recompute (via
|
|
365
|
+
`board-derive.mjs --write`, never by hand) the `unlocks` field of every task on the board as the
|
|
366
|
+
exact inverse of the full board's `depends_on` graph, then write it. Adding one task that
|
|
367
|
+
declares `depends_on: [TASK-007]` therefore rewrites `TASK-007.unlocks` in the same pass.
|
|
368
|
+
"Write both sides when you remember" allows drift; a derived field cannot drift
|
|
369
|
+
(island-escape shipped 10 asymmetric edges under the old rule). Audit L3-06 fails the board
|
|
370
|
+
on any asymmetric edge.
|
|
371
|
+
|
|
372
|
+
The same discipline has a boundary: it applies only when both sides live in the SAME root
|
|
373
|
+
(both task files are LOCAL). Never store a link from a committed doc back to a LOCAL task —
|
|
374
|
+
that is why UC `related_tasks` was retired (see doc-schemas.md, Use Case).
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Task Board (`tasks/_index.md`) Format
|
|
379
|
+
|
|
380
|
+
> Written to the LOCAL gitignored root `.shapeup/<slug>/tasks/_index.md`, not
|
|
381
|
+
> `spec_folder` — see SKILL.md Phase 6 "Locality" note (v3.2).
|
|
382
|
+
|
|
383
|
+
```markdown
|
|
384
|
+
# Task Board: [Feature Name]
|
|
385
|
+
|
|
386
|
+
| ID | Title | Package | Status | Priority | Depends On | Est. |
|
|
387
|
+
|----|-------|---------|--------|----------|------------|------|
|
|
388
|
+
| [[TASK-001\|TASK-001]] | Domain schema | shared | ⬜ ready | 1 | — | 4h |
|
|
389
|
+
| [[TASK-002\|TASK-002]] | Order repository | api | ⬜ ready | 2 | TASK-001 | 3h |
|
|
390
|
+
| [[TASK-003\|TASK-003]] | CreateOrder use case | api | ⬜ ready | 3 | TASK-002 | 4h |
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Status emoji: ⬜ ready · 🔄 in-progress · 🚫 blocked · ✅ done
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Common Task Patterns for Monorepo
|
|
398
|
+
|
|
399
|
+
### Pattern: New Shared Schema
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
TASK-NNN: [Entity] schema + migration
|
|
403
|
+
Package: packages/shared
|
|
404
|
+
Acceptance:
|
|
405
|
+
- Schema file created
|
|
406
|
+
- Migration generated with drizzle-kit
|
|
407
|
+
- Types exported from index.ts
|
|
408
|
+
- pnpm --filter shared typecheck passes
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Pattern: Repository Implementation
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
TASK-NNN: [Entity]Repository implementation
|
|
415
|
+
Package: apps/api (or packages/shared if pure domain)
|
|
416
|
+
Acceptance — Baseline:
|
|
417
|
+
- Class implements interface from domain-model.md
|
|
418
|
+
- `pnpm --filter api test [file]` passes
|
|
419
|
+
Acceptance — Empty State (REQUIRED — layer=repository):
|
|
420
|
+
- findById returns null (not throws) when ID not found
|
|
421
|
+
- findAll returns [] (not null) when table is empty
|
|
422
|
+
- All methods handle DB connection error → throw RepositoryError, not raw DB error
|
|
423
|
+
Depends on: schema task
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Pattern: Use Case Implementation
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
TASK-NNN: [UseCaseName] use case
|
|
430
|
+
Package: apps/api
|
|
431
|
+
Acceptance — Baseline:
|
|
432
|
+
- `pnpm --filter api test [file]` passes
|
|
433
|
+
- Input validated: missing required field → throws UseCaseError with code E_XXX
|
|
434
|
+
- Output shape matches TypeScript interface in [[usecases/UC-Name#Output]]
|
|
435
|
+
Acceptance — Inverse:
|
|
436
|
+
- Unauthenticated caller → throws AuthError (not proceeds)
|
|
437
|
+
- Caller without [ROLE] permission → throws ForbiddenError
|
|
438
|
+
Acceptance — Empty State:
|
|
439
|
+
- ⏳ BLOCKED: integration test pending [[TASK-NNN]] (repository not yet available)
|
|
440
|
+
Depends on: repository task
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Pattern: HTTP Endpoint
|
|
444
|
+
|
|
445
|
+
```
|
|
446
|
+
TASK-NNN: [METHOD] /[path] endpoint
|
|
447
|
+
Package: apps/api
|
|
448
|
+
Acceptance:
|
|
449
|
+
- Route registered in router
|
|
450
|
+
- Request/response types match use case IO
|
|
451
|
+
- 401 when unauthenticated
|
|
452
|
+
- Integration test passes
|
|
453
|
+
Depends on: use case task
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Pattern: Frontend Feature
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
TASK-NNN: [ScreenName] component
|
|
460
|
+
Package: apps/web
|
|
461
|
+
Acceptance — Baseline:
|
|
462
|
+
- `pnpm --filter web typecheck` passes
|
|
463
|
+
- Implements all states from [[ux-behavior#ScreenName]]
|
|
464
|
+
- All RULE-XX from ux-behavior implemented
|
|
465
|
+
- All error codes from error catalog handled
|
|
466
|
+
Acceptance — Inverse (REQUIRED — layer=ui):
|
|
467
|
+
- [Primary CTA] does NOT render in loading state
|
|
468
|
+
- [Primary CTA] does NOT render when user lacks permission [ROLE]
|
|
469
|
+
Acceptance — Empty State (REQUIRED — data-driven UI):
|
|
470
|
+
- EmptyState renders when API returns []
|
|
471
|
+
- No crash when `[primaryDataProp]` is null on first render
|
|
472
|
+
Acceptance — Boundary:
|
|
473
|
+
- [Input field] rejects input > [N] characters with inline error
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Pattern: Integration Test
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
TASK-NNN: Integration test — [use-case or feature name]
|
|
480
|
+
Package: apps/api/test | apps/web/e2e | e2e/
|
|
481
|
+
Layer: integration
|
|
482
|
+
Depends on: all implementation tasks for this feature (HTTP endpoint task minimum)
|
|
483
|
+
Estimated: 2–4h
|
|
484
|
+
|
|
485
|
+
Acceptance — Baseline:
|
|
486
|
+
- `pnpm --filter [pkg] test:integration [file]` passes
|
|
487
|
+
- [METHOD] /api/[resource] with valid auth → [expected status] + expected body shape
|
|
488
|
+
- DB round-trip confirmed: persisted record matches submitted payload
|
|
489
|
+
- Unauthenticated request → 401
|
|
490
|
+
|
|
491
|
+
Acceptance — BDD Scenarios (REQUIRED — FEAT + cross-boundary):
|
|
492
|
+
|
|
493
|
+
Scenario: Happy-path round-trip
|
|
494
|
+
Given an authenticated user with [role]
|
|
495
|
+
When [METHOD] /api/[resource] is called with valid payload
|
|
496
|
+
Then [expected status] response with {id, [fields]}
|
|
497
|
+
And DB row exists in [table] matching the submitted payload
|
|
498
|
+
|
|
499
|
+
Scenario: Auth rejection
|
|
500
|
+
Given an unauthenticated caller
|
|
501
|
+
When [METHOD] /api/[resource] is called
|
|
502
|
+
Then 401 Unauthorized with no side effects in DB
|
|
503
|
+
|
|
504
|
+
Acceptance — Integration Flow (REQUIRED — cross-service):
|
|
505
|
+
|
|
506
|
+
POST /api/[resource] → [UseCase].execute() → [Repository].save() → DB: [table]
|
|
507
|
+
Given authenticated request arrives at [Controller]
|
|
508
|
+
When use case validates + persists via repository
|
|
509
|
+
Then DB row committed; response body matches [[contracts/[repo].contract.md#Response]]
|
|
510
|
+
And domain event [EventName] published (if applicable)
|
|
511
|
+
|
|
512
|
+
Non-Go:
|
|
513
|
+
- Performance / load testing → separate task
|
|
514
|
+
- UI E2E / Playwright flows → separate TASK-NNN
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Discovered-Task Reconciliation (operation: reconcile)
|
|
520
|
+
|
|
521
|
+
Used by the reconcile operation over the discovered ledger at a round boundary. This is a pure
|
|
522
|
+
reducer: read existing state + the ledger delta, emit new state. The pitch and the
|
|
523
|
+
upfront DDD layer are FROZEN — this mode never re-runs Phase 1–5.
|
|
524
|
+
|
|
525
|
+
**Single anchor, no branch.** Every reconciled task anchors `use_case_refs` to exactly one
|
|
526
|
+
trust source: a UC. There is no second coverage trust (no `scope`/`invariant` frontmatter on
|
|
527
|
+
tasks). Scope (Basecamp sense) maps onto a UC; an invariant lives *inside* the UC it came from.
|
|
528
|
+
|
|
529
|
+
**Reconcile loop:**
|
|
530
|
+
```
|
|
531
|
+
1. Verify the ledger belongs to this spec. A discovered ledger rarely carries a pitch_hash
|
|
532
|
+
(its frontmatter names the raw materials, e.g. source: shaping.md + breadboard.md).
|
|
533
|
+
So match on identity that DOES exist:
|
|
534
|
+
a. ledger.feature == run-state.feature (REQUIRED — mismatch → STOP)
|
|
535
|
+
b. if ledger carries pitch_hash, it must == run-state.pitch_hash
|
|
536
|
+
Neither resolvable → STOP: "ledger does not match this spec (different feature slug)."
|
|
537
|
+
2. READ-ONLY for the frozen zone: domain-model, usecases/ (Steps), ux-behavior, contracts/.
|
|
538
|
+
3. Parse ledger scopes → map each to its owning UC (S1→UC-…, S2→UC-…, S3→UC-…).
|
|
539
|
+
A ledger scope with no matching UC, and that introduces a NEW actor/action,
|
|
540
|
+
is NOT a discovered task — it is a shaping miss. STOP and escalate to PO.
|
|
541
|
+
(Spawning a new UC mid-cycle = silent re-shaping = anti-Shape-Up.)
|
|
542
|
+
4. Parse discovered items under each scope's "Discovered" section:
|
|
543
|
+
[+] Keep → new task to generate
|
|
544
|
+
~ / Cut → append a row to synthesis "Hammered Out (Cut)" — NO file
|
|
545
|
+
[ ] already has a file → skip
|
|
546
|
+
5. For a Keep item that asserts a new invariant on its scope's UC:
|
|
547
|
+
APPEND an [INV-NN] line to that UC's ## Invariants section (append-only,
|
|
548
|
+
never touch Steps/Input/Output). Log the UC path in run-state.human_edited_files.
|
|
549
|
+
Generate the regression task with command-verifiable AC (see AC Trigger Matrix).
|
|
550
|
+
6. Number new tasks by reading max id in tasks/_index.md and incrementing.
|
|
551
|
+
NEVER renumber existing tasks — that would break every depends_on wikilink the
|
|
552
|
+
executor has already resolved.
|
|
553
|
+
7. Recompute `unlocks` across the WHOLE board from the depends_on inverse (Link-Field
|
|
554
|
+
Integrity above). This is the one write this mode makes to existing task files, and it
|
|
555
|
+
touches only that frontmatter field — a new task's depends_on MUST patch its
|
|
556
|
+
dependencies' unlocks in this same pass, or the edge ships asymmetric.
|
|
557
|
+
8. Drift check (flag, never fix): for each scope with a T0 verdict artifact
|
|
558
|
+
(t0/<scope>/t0/verdicts/*.json, round-ledger Hill table), compare the scope's mechanical
|
|
559
|
+
state against its tasks' local status/AC checkboxes. A FINISHED/T0-green scope whose
|
|
560
|
+
tasks still read `status: ready` (or vice versa) gets a ⚠️ drift marker on those rows in
|
|
561
|
+
the regenerated tasks/_index.md + one line in the gate output. Join on SCOPE, never on
|
|
562
|
+
task id — ids are per-machine and renumber on bootstrap. Status authority stays with
|
|
563
|
+
task-executor / the tech lead; this mode only surfaces the disagreement.
|
|
564
|
+
9. Regenerate ONLY the derived files: tasks/_index.md, scope-summary.md, synthesis.md
|
|
565
|
+
(+ the unlocks frontmatter recompute from step 7).
|
|
566
|
+
10. Run board-derive.mjs (Appetite Guard arithmetic, below); report overflow as a discovery.
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Appetite Guard (forcing function, not a report):**
|
|
570
|
+
```
|
|
571
|
+
appetite_hours = parse from pitch frontmatter (e.g. ~3 weeks → hours)
|
|
572
|
+
keep_hours = Σ estimated_hours over tasks with status ≠ cut
|
|
573
|
+
IF keep_hours > appetite_hours:
|
|
574
|
+
⏸ print HAMMER prompt — surface the overflow + candidate cuts (must→nice),
|
|
575
|
+
then WAIT. Never auto-resolve. Options offered:
|
|
576
|
+
cut TASK-NNN | shrink the new task's scope | expand appetite (needs PO re-bet)
|
|
577
|
+
```
|
|
578
|
+
This is scope hammering at the gate boundary — the overflow is surfaced, never silently absorbed.
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Scope Contracts
|
|
583
|
+
|
|
584
|
+
**Naming note:** the discovery ledger's "scope" sections (above) predate the formal Scope
|
|
585
|
+
Contract artifact below and name the same thing — a `discovery/ledger.md` scope heading MUST
|
|
586
|
+
match an existing `scope_id` from a `scopes/<scope-id>.md` contract, or (a Keep item
|
|
587
|
+
introducing new flow) become the seed for a new one via a scope-architect remap order. One concept, two touch points.
|
|
588
|
+
|
|
589
|
+
**Import/flow slicing (PA1 countermeasure).** Build a lightweight import graph over the task
|
|
590
|
+
board's touched files: for each file a task writes, note what it imports and what imports it
|
|
591
|
+
(grep for `import .* from ['"](\.\.?/[^'"]+)`-class patterns is sufficient — a full AST parser
|
|
592
|
+
is an optimization, not a prerequisite, per design spec R6). Two files are in the same flow if
|
|
593
|
+
they sit on one call chain: a UI component → the API route it calls → the use case that route
|
|
594
|
+
invokes → the repository that use case depends on. Group tasks by flow, not by which top-level
|
|
595
|
+
directory their file lives in. A scope whose `allowed_file_substrate` is entirely `apps/web/**`
|
|
596
|
+
or entirely `apps/api/**` with no cross-layer flow is the PA1 failure mode — re-slice it.
|
|
597
|
+
|
|
598
|
+
**Scope contract schema** (written to `scopes/<scope-id>.md`, one file per scope):
|
|
599
|
+
```json
|
|
600
|
+
{
|
|
601
|
+
"scope_id": "cart-creation",
|
|
602
|
+
"topology_type": "ICEBERG",
|
|
603
|
+
"business_goal": "Shopper can create a cart and see it persist",
|
|
604
|
+
"allowed_file_substrate": ["apps/web/cart/*.tsx", "apps/api/cart/*.ts", "packages/domain/cart/*.ts"],
|
|
605
|
+
"shared_substrate": ["packages/shared/http.ts"],
|
|
606
|
+
"discovered_tasks_pool": [],
|
|
607
|
+
"e2e_verification_fixtures": ["fixtures/cart-create.spec.ts"],
|
|
608
|
+
"affordance_manifest": {
|
|
609
|
+
"interactive_elements": [{ "test_id": "add-to-cart-btn", "role": "button" }],
|
|
610
|
+
"required_states": ["idle", "loading", "success", "error", "empty"]
|
|
611
|
+
},
|
|
612
|
+
"hill_phase": "UPHILL_UNKNOWN",
|
|
613
|
+
"superseded_by": null
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
`hill_phase` is always written `UPHILL_UNKNOWN` at generation time — it is derived later from
|
|
617
|
+
mechanical T0/T1/seesaw facts, never declared by `ba` (design spec DD-10). `superseded_by` stays
|
|
618
|
+
`null` until a scope-architect split-scope order retires this contract in favor of its replacements.
|
|
619
|
+
|
|
620
|
+
**PA2 size lint:** a scope whose `allowed_file_substrate` glob set resolves to more than ~15
|
|
621
|
+
files gets a ⚠️ from spec-lint PA2 (hard-cap configurable via pitch frontmatter `scope_size_cap`, default
|
|
622
|
+
15). A CHOWDER scope is exempt by design (it is the deliberate strays bucket) but still gets
|
|
623
|
+
flagged if it grows past 2x the cap — that usually means real flows are hiding in it unsliced.
|