Skip to content

Commit dad002f

Browse files
committed
add risk-based-testing
1 parent 209b127 commit dad002f

File tree

8 files changed

+251
-9
lines changed

8 files changed

+251
-9
lines changed

.specify/templates/spec-template.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ When creating this spec from a user prompt:
8282
- **[Entity 1]**: [What it represents, key attributes without implementation]
8383
- **[Entity 2]**: [What it represents, relationships to other entities]
8484

85+
## Risk Register *(mandatory)*
86+
Document the critical risks the team must mitigate. Capture each risk on its own line using the canonical format so downstream automation can parse it:
87+
88+
- `- RISK: R1 | Statement: Unauthorized access to admin endpoints | Impact: High | Likelihood: Medium | Test: Assert RBAC denies non-admin roles`
89+
90+
Guidance:
91+
- **RISK**: Stable identifier (`R1`, `SEC-02`, etc.)
92+
- **Statement**: Concise summary of the exposure in business terms
93+
- **Impact/Likelihood**: Qualitative level (`Low`, `Medium`, `High`)
94+
- **Test**: The validation focus needed to prove the mitigation works (e.g., "Fuzz payload to detect overflow")
95+
- Add additional pipe-delimited attributes when needed (`Owner`, `Notes`, etc.); keep the key names capitalized for readability.
96+
8597
---
8698

8799
## Review & Acceptance Checklist

scripts/bash/check-prerequisites.sh

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,57 @@ done
7878
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7979
source "$SCRIPT_DIR/common.sh"
8080

81+
# Extract risk entries from a markdown file's Risk Register section
82+
extract_risks() {
83+
local file="$1"
84+
if [[ ! -f "$file" ]]; then
85+
echo "[]"
86+
return
87+
fi
88+
89+
python3 - "$file" <<'PY'
90+
import json
91+
import re
92+
import sys
93+
from pathlib import Path
94+
95+
path = Path(sys.argv[1])
96+
pattern = re.compile(r"^-\s*RISK:\s*(.+)$", re.IGNORECASE)
97+
risks = []
98+
99+
for line in path.read_text().splitlines():
100+
match = pattern.match(line.strip())
101+
if not match:
102+
continue
103+
104+
parts = [p.strip() for p in match.group(1).split("|") if p.strip()]
105+
data = {}
106+
107+
if parts and ":" not in parts[0]:
108+
data["id"] = parts[0]
109+
parts = parts[1:]
110+
111+
for part in parts:
112+
if ":" not in part:
113+
continue
114+
key, value = part.split(":", 1)
115+
key = key.strip()
116+
value = value.strip()
117+
normalized = key.lower().replace(" ", "_")
118+
if normalized == "risk":
119+
data["id"] = value
120+
else:
121+
data[normalized] = value
122+
123+
if data:
124+
if "id" not in data:
125+
data["id"] = f"missing-id-{len(risks)+1}"
126+
risks.append(data)
127+
128+
print(json.dumps(risks, ensure_ascii=False))
129+
PY
130+
}
131+
81132
# Get feature paths and validate branch
82133
eval $(get_feature_paths)
83134
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
@@ -160,7 +211,9 @@ if $JSON_MODE; then
160211
json_docs="[${json_docs%,}]"
161212
fi
162213

163-
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
214+
SPEC_RISKS=$(extract_risks "$FEATURE_SPEC")
215+
PLAN_RISKS=$(extract_risks "$IMPL_PLAN")
216+
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s,"SPEC_RISKS":%s,"PLAN_RISKS":%s}\n' "$FEATURE_DIR" "$json_docs" "$SPEC_RISKS" "$PLAN_RISKS"
164217
else
165218
# Text output
166219
echo "FEATURE_DIR:$FEATURE_DIR"
@@ -175,4 +228,26 @@ else
175228
if $INCLUDE_TASKS; then
176229
check_file "$TASKS" "tasks.md"
177230
fi
231+
232+
spec_risks_count=$(extract_risks "$FEATURE_SPEC" | python3 - <<'PY'
233+
import json, sys
234+
try:
235+
data = json.load(sys.stdin)
236+
except json.JSONDecodeError:
237+
data = []
238+
print(len(data))
239+
PY
240+
)
241+
plan_risks_count=$(extract_risks "$IMPL_PLAN" | python3 - <<'PY'
242+
import json, sys
243+
try:
244+
data = json.load(sys.stdin)
245+
except json.JSONDecodeError:
246+
data = []
247+
print(len(data))
248+
PY
249+
)
250+
251+
echo "SPEC_RISKS: $spec_risks_count"
252+
echo "PLAN_RISKS: $plan_risks_count"
178253
fi

scripts/powershell/check-prerequisites.ps1

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,64 @@ EXAMPLES:
5656
# Source common functions
5757
. "$PSScriptRoot/common.ps1"
5858

59+
function Get-RisksFromFile {
60+
param([string]$Path)
61+
62+
if (-not (Test-Path $Path -PathType Leaf)) {
63+
return @()
64+
}
65+
66+
$risks = @()
67+
$pattern = '^-\s*RISK:\s*(.+)$'
68+
$index = 1
69+
70+
foreach ($line in Get-Content -Path $Path) {
71+
$trim = $line.Trim()
72+
if ($trim -notmatch $pattern) { continue }
73+
74+
$content = $Matches[1]
75+
$parts = @()
76+
foreach ($piece in ($content -split '\|')) {
77+
$trimmed = $piece.Trim()
78+
if ($trimmed) { $parts += $trimmed }
79+
}
80+
81+
$data = @{}
82+
83+
if ($parts.Count -gt 0 -and $parts[0] -notmatch ':') {
84+
$data['id'] = $parts[0]
85+
if ($parts.Count -gt 1) {
86+
$parts = $parts[1..($parts.Count - 1)]
87+
} else {
88+
$parts = @()
89+
}
90+
}
91+
92+
foreach ($part in $parts) {
93+
if ($part -match '^\s*([^:]+):\s*(.+)$') {
94+
$key = $Matches[1].Trim()
95+
$value = $Matches[2].Trim()
96+
$normalized = $key.ToLower().Replace(' ', '_')
97+
98+
if ($normalized -eq 'risk') {
99+
$data['id'] = $value
100+
} else {
101+
$data[$normalized] = $value
102+
}
103+
}
104+
}
105+
106+
if (-not $data.ContainsKey('id')) {
107+
$data['id'] = "missing-id-$index"
108+
}
109+
$index += 1
110+
111+
$risks += [PSCustomObject]$data
112+
}
113+
114+
return $risks
115+
}
116+
59117
# Get feature paths and validate branch
60118
$paths = Get-FeaturePathsEnv
61119

@@ -125,11 +183,16 @@ if ($IncludeTasks -and (Test-Path $paths.TASKS)) {
125183
}
126184

127185
# Output results
186+
$specRisks = Get-RisksFromFile -Path $paths.FEATURE_SPEC
187+
$planRisks = Get-RisksFromFile -Path $paths.IMPL_PLAN
188+
128189
if ($Json) {
129190
# JSON output
130191
[PSCustomObject]@{
131192
FEATURE_DIR = $paths.FEATURE_DIR
132193
AVAILABLE_DOCS = $docs
194+
SPEC_RISKS = $specRisks
195+
PLAN_RISKS = $planRisks
133196
} | ConvertTo-Json -Compress
134197
} else {
135198
# Text output
@@ -145,4 +208,7 @@ if ($Json) {
145208
if ($IncludeTasks) {
146209
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
147210
}
211+
212+
Write-Output "SPEC_RISKS: $($specRisks.Count)"
213+
Write-Output "PLAN_RISKS: $($planRisks.Count)"
148214
}

templates/commands/implement.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ User input:
1111

1212
$ARGUMENTS
1313

14-
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
14+
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR, AVAILABLE_DOCS, SPEC_RISKS, and PLAN_RISKS. All paths must be absolute.
1515

1616
2. Verify feature context:
1717
- Load `context.md` from FEATURE_DIR.
@@ -25,6 +25,7 @@ $ARGUMENTS
2525
- **IF EXISTS**: Read research.md for technical decisions and constraints
2626
- **IF EXISTS**: Read quickstart.md for integration scenarios
2727
- Extract the Execution Mode for each task (`[SYNC]` or `[ASYNC]`) and ensure every task is explicitly tagged. STOP and raise an error if any tasks lack a mode.
28+
- Consolidate the Risk Register by merging SPEC_RISKS and PLAN_RISKS. Ensure every Risk ID listed in tasks.md has a corresponding definition (statement, mitigation, test strategy, evidence artefact). STOP if any gaps remain.
2829

2930
4. Parse tasks.md structure and extract:
3031
- **Task phases**: Setup, Tests, Core, Integration, Polish
@@ -48,6 +49,7 @@ $ARGUMENTS
4849
- **Core development**: Implement models, services, CLI commands, endpoints
4950
- **Integration work**: Database connections, middleware, logging, external services
5051
- **Polish and validation**: Unit tests, performance optimization, documentation
52+
- **Risk validation**: For each `[SYNC]` risk test task (`TRxx`), execute the specified tests, capture pass/fail output, and record the evidence in the artefact defined by plan.md (e.g., `risk-tests/R1.log` or the Risk Evidence Log table).
5153

5254
7. Progress tracking and error handling:
5355
- Report progress after each completed task
@@ -56,6 +58,7 @@ $ARGUMENTS
5658
- Provide clear error messages with context for debugging
5759
- Suggest next steps if implementation cannot proceed
5860
- **IMPORTANT** For completed tasks, mark the task off as `[X]` in tasks.md while preserving `[SYNC]/[ASYNC]` tags and [P] markers.
61+
- Update the **Risk Evidence Log** in tasks.md with the test task ID, evidence artefact, and a concise summary (include command executed and assertion outcome). Missing evidence is a blocker for completion.
5962

6063
8. Completion validation:
6164
- Verify all required tasks are completed

templates/commands/tasks.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ User input:
1111

1212
$ARGUMENTS
1313

14-
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
14+
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR, AVAILABLE_DOCS, SPEC_RISKS, and PLAN_RISKS. All paths must be absolute.
1515
2. Load and validate `context.md` from the feature directory:
1616
- STOP if the file is missing or contains `[NEEDS INPUT]` markers.
1717
- Capture mission highlights, relevant code paths, directives, and gateway status for downstream reasoning.
@@ -28,7 +28,12 @@ $ARGUMENTS
2828
- Simple libraries might not need data-model.md
2929
- Generate tasks based on what's available
3030

31-
4. Generate tasks following the template:
31+
4. Build a consolidated Risk Register:
32+
- Merge SPEC_RISKS and PLAN_RISKS by `id` (Risk ID). If the same risk appears in both, prefer the richer description from plan.md for mitigation details.
33+
- STOP and prompt the developer if the spec declares risks but the plan lacks matching mitigation/test strategy rows.
34+
- For each consolidated risk, capture: `id`, `statement`, `impact`, `likelihood`, `test` (focus), and `evidence` (artefact path). Missing fields must be escalated to the developer.
35+
36+
5. Generate tasks following the template:
3237
- Use `/templates/tasks-template.md` as the base
3338
- Replace example tasks with actual tasks based on:
3439
* **Setup tasks**: Project init, dependencies, linting
@@ -37,34 +42,37 @@ $ARGUMENTS
3742
* **Integration tasks**: DB connections, middleware, logging
3843
* **Polish tasks [P]**: Unit tests, performance, docs
3944
- For every task, append the Execution Mode tag `[SYNC]` or `[ASYNC]` as dictated by the plan. Never invent a mode—ask the developer when absent.
45+
- Introduce dedicated `[SYNC]` risk test tasks (prefix `TR`) for each Risk ID, referencing the exact file path to implement and the evidence artefact where `/implement` must store test output.
4046

41-
5. Task generation rules:
47+
6. Task generation rules:
4248
- Each contract file → contract test task marked [P]
4349
- Each entity in data-model → model creation task marked [P]
4450
- Each endpoint → implementation task (not parallel if shared files)
4551
- Each user story → integration test marked [P]
4652
- Different files = can be parallel [P]
4753
- Same file = sequential (no [P])
4854
- Preserve the Execution Mode from the plan so downstream tooling can enforce SYNC vs ASYNC workflows.
55+
- Every Risk ID MUST have at least one `[SYNC]` test task with a clearly defined evidence artefact. If multiple mitigations exist, generate a task per mitigation/test.
4956

50-
6. Order tasks by dependencies:
57+
7. Order tasks by dependencies:
5158
- Setup before everything
5259
- Tests before implementation (TDD)
5360
- Models before services
5461
- Services before endpoints
5562
- Core before integration
5663
- Everything before polish
5764

58-
7. Include parallel execution examples:
65+
8. Include parallel execution examples:
5966
- Group [P] tasks that can run together
6067
- Show actual Task agent commands
6168

62-
8. Create FEATURE_DIR/tasks.md with:
69+
9. Create FEATURE_DIR/tasks.md with:
6370
- Correct feature name from implementation plan
6471
- Numbered tasks (T001, T002, etc.)
6572
- Clear file paths for each task
6673
- Dependency notes
6774
- Parallel execution guidance
75+
- Updated **Risk Evidence Log** table populated with Risk IDs and placeholder evidence entries (`TBD`) for `/implement` to update.
6876

6977
Context for task generation: {ARGS}
7078

templates/plan-template.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ specs/[###-feature]/
7575
└── tasks.md # Phase 2 output (/tasks command - NOT created by /plan)
7676
```
7777

78+
## Risk Mitigation & Test Strategy
79+
*Map every risk from the specification's Risk Register to a concrete mitigation and validation artefact.*
80+
81+
| Risk ID | Statement | Mitigation Owner | Test Strategy | Evidence Artefact |
82+
|---------|-----------|------------------|---------------|--------------------|
83+
| R1 | Unauthorized access to admin endpoints | Security | RBAC denies non-admin roles via integration test | risk-tests/R1.log |
84+
85+
- Keep `Risk ID` identical to the spec entry (e.g., `R1`).
86+
- `Test Strategy` describes the exact validation (unit, integration, fuzz, chaos, etc.)
87+
- `Evidence Artefact` states where `/implement` must record the passing test output (e.g., `specs/001-feature/risk-test-evidence.md`).
88+
7889
### Source Code (repository root)
7990
<!--
8091
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
@@ -186,6 +197,7 @@ directories captured above]
186197
- Each user story → integration test task
187198
- Implementation tasks to make tests pass
188199
- Carry forward the Execution Mode from the Triage Overview: annotate each generated task with `[SYNC]` or `[ASYNC]` based on the finalized plan decision.
200+
- For every risk entry, generate at least one dedicated `[SYNC]` test task referencing the Risk ID and the evidence artefact.
189201

190202
**Ordering Strategy**:
191203
- TDD order: Tests before implementation

templates/tasks-template.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
- [ ] T006 [SYNC] [P] Integration test user registration in tests/integration/test_registration.py
5656
- [ ] T007 [SYNC] [P] Integration test auth flow in tests/integration/test_auth.py
5757

58+
## Phase 3.2b: Risk-Based Tests (Spec Risk Register)
59+
- [ ] TR01 [SYNC] Risk R1 — RBAC denies non-admin roles (tests/integration/test_admin_access.py) → Capture evidence in risk-tests/R1.log
60+
- [ ] TR02 [SYNC] Risk R2 — Prevent data loss during retries (tests/integration/test_retry_durability.py) → Capture evidence in risk-tests/R2.log
61+
5862
## Phase 3.3: Core Implementation (ONLY after tests are failing)
5963
- [ ] T008 [ASYNC] [P] User model in src/models/user.py
6064
- [ ] T009 [ASYNC] [P] UserService CRUD in src/services/user_service.py
@@ -98,6 +102,7 @@ Task: "Integration test auth in tests/integration/test_auth.py"
98102
- Verify tests fail before implementing
99103
- Commit after each task
100104
- Avoid: vague tasks, same file conflicts
105+
- Risk tasks (`TRxx`) must reference the exact Risk ID and evidence location defined in plan.md.
101106

102107
## Task Generation Rules
103108
*Applied during main() execution*
@@ -118,6 +123,11 @@ Task: "Integration test auth in tests/integration/test_auth.py"
118123
- Setup → Tests → Models → Services → Endpoints → Polish
119124
- Dependencies block parallel execution
120125

126+
5. **Risk Coverage**:
127+
- For each Risk ID in spec/plan, create at least one `[SYNC]` test task prefixed `TR` that references the risk, test path, and evidence artefact.
128+
- If multiple mitigations exist, create a task per mitigation/test.
129+
- Document required evidence capture in the task description (e.g., `→ Capture evidence in risk-tests/R1.log`).
130+
121131
## Validation Checklist
122132
*GATE: Checked by main() before returning*
123133

@@ -126,4 +136,10 @@ Task: "Integration test auth in tests/integration/test_auth.py"
126136
- [ ] All tests come before implementation
127137
- [ ] Parallel tasks truly independent
128138
- [ ] Each task specifies exact file path
129-
- [ ] No task modifies same file as another [P] task
139+
- [ ] No task modifies same file as another [P] task
140+
- [ ] Every Risk ID has at least one `[SYNC]` risk test task with evidence path
141+
142+
## Risk Evidence Log (maintained during /implement)
143+
| Risk ID | Test Task ID | Evidence Artefact | Evidence Summary |
144+
|---------|--------------|-------------------|------------------|
145+
| R1 | TR01 | risk-tests/R1.log | TBD – populate with `/implement` test output |

0 commit comments

Comments
 (0)