@metasession.co/devaudit-cli 0.1.76 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/index.js +2 -1
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/sdlc/files/_common/1-plan-requirement.md +6 -456
  5. package/sdlc/files/_common/2-implement-and-test.md +6 -294
  6. package/sdlc/files/_common/3-compile-evidence.md +6 -809
  7. package/sdlc/files/_common/4-submit-for-review.md +7 -372
  8. package/sdlc/files/_common/5-deploy-main.md +6 -265
  9. package/sdlc/files/_common/implementing-an-sdlc-issue.md +7 -434
  10. package/sdlc/files/_common/scripts/close-out-release.sh +8 -1
  11. package/sdlc/files/_common/scripts/close-out-release.test.sh +59 -0
  12. package/sdlc/files/_common/scripts/derive-release-version.sh +2 -1
  13. package/sdlc/files/_common/scripts/derive-release-version.test.sh +29 -0
  14. package/sdlc/files/_common/scripts/validate-test-summary.sh +146 -0
  15. package/sdlc/files/_common/scripts/validate-test-summary.test.sh +268 -0
  16. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +28 -2
  17. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +21 -2
  18. package/sdlc/files/ci/ci.yml.template +36 -1
  19. package/sdlc/files/ci/compliance-validation.yml.template +3 -0
  20. package/sdlc/files/stacks/node/hooks/pre-push +19 -0
  21. package/sdlc/package.json +21 -0
  22. package/sdlc/src/bin/devaudit-sdlc.js +95 -0
  23. package/sdlc/src/blueprints/1-plan-requirement.raw.md +464 -0
  24. package/sdlc/src/blueprints/2-implement-and-test.raw.md +314 -0
  25. package/sdlc/src/blueprints/3-compile-evidence.raw.md +817 -0
  26. package/sdlc/src/blueprints/4-submit-for-review.raw.md +379 -0
  27. package/sdlc/src/blueprints/5-deploy-main.raw.md +273 -0
  28. package/sdlc/src/blueprints/implementing-an-sdlc-issue.raw.md +441 -0
@@ -0,0 +1,146 @@
1
+ #!/bin/bash
2
+ # validate-test-summary.sh — Validates test-execution-summary.md content.
3
+ #
4
+ # Usage:
5
+ # ./scripts/validate-test-summary.sh [base-branch]
6
+ #
7
+ # Checks that test-execution-summary.md files for in-scope REQs do not
8
+ # contain invalid gate states like "deferred" or "Deferred to CI".
9
+ # E2E gate results must be one of: PASS, FAIL, NOT_NEEDED (with reason),
10
+ # or SKIPPED (with operator-approved rationale). "Deferred to CI" is not
11
+ # a valid SDLC state (devaudit-installer#240).
12
+ #
13
+ # Designed to run as a CI job on PRs to main, alongside
14
+ # validate-compliance-artifacts.sh.
15
+ #
16
+ # Install: cp this file to your project's scripts/ directory && chmod +x scripts/validate-test-summary.sh
17
+
18
+ set -euo pipefail
19
+
20
+ BASE_BRANCH="${1:-origin/main}"
21
+ EXIT_CODE=0
22
+
23
+ echo "=== Test Execution Summary Validation ==="
24
+ echo "Comparing: $BASE_BRANCH...HEAD"
25
+ echo ""
26
+
27
+ # Extract the requirement(s) THIS PR implements — same logic as
28
+ # validate-compliance-artifacts.sh
29
+ PR_MSGS=$(git log "$BASE_BRANCH"..HEAD --format='%B' || true)
30
+ REQUIREMENTS=$(
31
+ {
32
+ printf '%s\n' "$PR_MSGS" | grep -oP '\[\KREQ-\d{3,}(?=\])' || true
33
+ printf '%s\n' "$PR_MSGS" | grep -iP '^\s*Ref:' | grep -oP 'REQ-\d{3,}' || true
34
+ } | sort -u
35
+ )
36
+
37
+ if [ -z "$REQUIREMENTS" ]; then
38
+ echo "No REQ-XXX references found in PR commits — skipping test summary validation."
39
+ exit 0
40
+ fi
41
+
42
+ echo "Requirements found in PR commits: $REQUIREMENTS"
43
+ echo ""
44
+
45
+ for REQ in $REQUIREMENTS; do
46
+ echo "--- Checking $REQ ---"
47
+
48
+ SUMMARY="compliance/evidence/$REQ/test-execution-summary.md"
49
+
50
+ if [ ! -f "$SUMMARY" ]; then
51
+ echo " INFO: $SUMMARY not found — skipping (validate-compliance-artifacts.sh checks existence)"
52
+ echo ""
53
+ continue
54
+ fi
55
+
56
+ echo " OK: test-execution-summary.md exists"
57
+
58
+ # Check 1: Reject "deferred" (case-insensitive) anywhere in the file
59
+ if grep -qi 'deferred' "$SUMMARY"; then
60
+ echo " ERROR: 'deferred' found in $SUMMARY — 'Deferred to CI' is not a valid gate state (devaudit-installer#238, #240)."
61
+ echo " E2E gate results must be: PASS, FAIL, NOT_NEEDED (with reason), or SKIPPED (with operator-approved rationale)."
62
+ echo " Offending lines:"
63
+ grep -in 'deferred' "$SUMMARY" | sed 's/^/ /'
64
+ EXIT_CODE=1
65
+ fi
66
+
67
+ # Check 2: Reject "browsers not installed" as a gate outcome
68
+ if grep -qi 'browsers not installed' "$SUMMARY"; then
69
+ echo " ERROR: 'browsers not installed' found in $SUMMARY — this is an environment setup issue, not a gate state (devaudit-installer#238, #240)."
70
+ echo " Install browsers with 'npx playwright install' and re-run the E2E suite."
71
+ echo " Offending lines:"
72
+ grep -in 'browsers not installed' "$SUMMARY" | sed 's/^/ /'
73
+ EXIT_CODE=1
74
+ fi
75
+
76
+ # Check 3: Reject "Deferred to CI" explicitly (catches the exact pattern from #238)
77
+ if grep -qi 'deferred to ci' "$SUMMARY"; then
78
+ echo " ERROR: 'Deferred to CI' found in $SUMMARY — CI is a safety net, not a replacement for local E2E execution (devaudit-installer#238, #240)."
79
+ echo " Offending lines:"
80
+ grep -in 'deferred to ci' "$SUMMARY" | sed 's/^/ /'
81
+ EXIT_CODE=1
82
+ fi
83
+
84
+ # Check 4: Validate E2E gate result in the gate results table
85
+ # Look for lines matching "| E2E ... | <result> |" pattern
86
+ E2E_LINES=$(grep -i '| *e2e' "$SUMMARY" 2>/dev/null || true)
87
+ if [ -n "$E2E_LINES" ]; then
88
+ while IFS= read -r line; do
89
+ # Extract the result column (2nd | field after "E2E")
90
+ RESULT=$(echo "$line" | grep -oP '\|\s*\K[^|]+(?=\s*\|)' | head -2 | tail -1 | xargs || true)
91
+ if [ -n "$RESULT" ]; then
92
+ RESULT_LOWER=$(echo "$RESULT" | tr '[:upper:]' '[:lower:]')
93
+ # Skip empty or separator lines
94
+ if [ -z "$RESULT_LOWER" ] || echo "$RESULT_LOWER" | grep -q '^[|-]*$'; then
95
+ continue
96
+ fi
97
+ # Check for invalid "deferred" state
98
+ if echo "$RESULT_LOWER" | grep -qi 'defer'; then
99
+ echo " ERROR: E2E gate result '$RESULT' is not valid — 'deferred' is not a gate state (devaudit-installer#240)"
100
+ EXIT_CODE=1
101
+ fi
102
+ fi
103
+ done <<< "$E2E_LINES"
104
+ fi
105
+
106
+ # Check 5: If E2E result is SKIPPED or NOT_NEEDED, require a rationale on the same line
107
+ if grep -qi '| *e2e.*| *skipped' "$SUMMARY" 2>/dev/null; then
108
+ SKIPPED_LINE=$(grep -i '| *e2e.*| *skipped' "$SUMMARY" | head -1)
109
+ # Rationale can be in the details column (after the result column) or inline with the state
110
+ if echo "$SKIPPED_LINE" | grep -qi 'skipped.*[—–-].*\|| *skipped.*|.*[a-z]'; then
111
+ echo " OK: E2E gate result is SKIPPED with rationale"
112
+ else
113
+ echo " ERROR: E2E gate result is SKIPPED but no rationale provided. Add a reason in the Details column (e.g. 'SKIPPED — API-only change, no UI surface (operator-approved)') (devaudit-installer#240)"
114
+ EXIT_CODE=1
115
+ fi
116
+ fi
117
+
118
+ if grep -qi '| *e2e.*| *not_needed' "$SUMMARY" 2>/dev/null; then
119
+ NN_LINE=$(grep -i '| *e2e.*| *not_needed' "$SUMMARY" | head -1)
120
+ if echo "$NN_LINE" | grep -qi 'not_needed.*[—–-].*\|| *not_needed.*|.*[a-z]'; then
121
+ echo " OK: E2E gate result is NOT_NEEDED with rationale"
122
+ else
123
+ echo " ERROR: E2E gate result is NOT_NEEDED but no rationale provided. Add a reason in the Details column (e.g. 'NOT_NEEDED — schema-only change, no UI surface') (devaudit-installer#240)"
124
+ EXIT_CODE=1
125
+ fi
126
+ fi
127
+
128
+ # Check 6: Reject "E2E deferred to CI" in final assessment lines
129
+ if grep -qi 'e2e deferred\|e2e.*deferred to ci' "$SUMMARY" 2>/dev/null; then
130
+ echo " ERROR: 'E2E deferred' language found in final assessment — 'deferred' is not a valid gate state (devaudit-installer#240)"
131
+ echo " Offending lines:"
132
+ grep -in 'e2e deferred\|e2e.*deferred to ci' "$SUMMARY" | sed 's/^/ /'
133
+ EXIT_CODE=1
134
+ fi
135
+
136
+ echo ""
137
+ done
138
+
139
+ echo ""
140
+ if [ $EXIT_CODE -eq 0 ]; then
141
+ echo "=== All test execution summary checks passed ==="
142
+ else
143
+ echo "=== FAILED: Invalid gate states found in test execution summary ==="
144
+ fi
145
+
146
+ exit $EXIT_CODE
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env bash
2
+ # validate-test-summary.test.sh — Tests for validate-test-summary.sh.
3
+ #
4
+ # Builds a throwaway git repo per case, creates compliance evidence with
5
+ # crafted test-execution-summary.md files, runs the validator, and asserts
6
+ # on the exit code. Hermetic: runs inside mktemp'd directories that are
7
+ # torn down at the end.
8
+ #
9
+ # Usage:
10
+ # ./scripts/validate-test-summary.test.sh
11
+
12
+ set -euo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15
+ HELPER="$SCRIPT_DIR/validate-test-summary.sh"
16
+ [ -x "$HELPER" ] || chmod +x "$HELPER"
17
+
18
+ PASS=0
19
+ FAIL=0
20
+
21
+ WORK=$(mktemp -d)
22
+ trap 'rm -rf "$WORK"' EXIT
23
+
24
+ # Build a fresh git fixture with a commit referencing REQ-XXX.
25
+ # Sets up compliance/evidence/REQ-XXX/ directory.
26
+ # $1 = dir, $2 = REQ ID, $3 = summary file content (via stdin)
27
+ make_fixture() {
28
+ local dir="$1" req="$2"
29
+ rm -rf "$dir"
30
+ mkdir -p "$dir"
31
+ cd "$dir"
32
+ git init -q --initial-branch=main
33
+ git config user.email "test@example.com"
34
+ git config user.name "test"
35
+
36
+ # Create a base branch and a feature branch with a REQ commit
37
+ echo "x" > f.txt
38
+ git add f.txt
39
+ git commit -q -m "initial"
40
+
41
+ git checkout -q -b feature
42
+ echo "y" > g.txt
43
+ git add g.txt
44
+ git commit -q -m "feat: [${req}] test change
45
+
46
+ Ref: ${req}"
47
+
48
+ # Create evidence directory and RTM row
49
+ mkdir -p "compliance/evidence/${req}"
50
+ echo "| ${req} | #999 | HIGH | compliance/evidence/${req}/ | TESTED - PENDING SIGN-OFF | TBD | -- |" > compliance/RTM.md
51
+ git add compliance/RTM.md
52
+ git commit -q -m "docs: add RTM row for ${req}"
53
+
54
+ # Write the test-execution-summary.md from stdin
55
+ cat > "compliance/evidence/${req}/test-execution-summary.md"
56
+
57
+ # Stay on feature branch so the validator sees the diff from main
58
+ }
59
+
60
+ # Run the validator against the fixture, capture exit code.
61
+ # $1 = fixture dir
62
+ run_validator() {
63
+ local dir="$1"
64
+ cd "$dir"
65
+ local exit_code=0
66
+ bash "$HELPER" main > /tmp/validate-test-summary-output.txt 2>&1 || exit_code=$?
67
+ echo "$exit_code"
68
+ }
69
+
70
+ assert_exit() {
71
+ local desc="$1" want="$2" got="$3"
72
+ if [ "$got" = "$want" ]; then
73
+ echo " PASS: $desc"
74
+ PASS=$((PASS + 1))
75
+ else
76
+ echo " FAIL: $desc"
77
+ echo " want exit: $want"
78
+ echo " got exit: $got"
79
+ cat /tmp/validate-test-summary-output.txt | sed 's/^/ /'
80
+ FAIL=$((FAIL + 1))
81
+ fi
82
+ }
83
+
84
+ echo "=== validate-test-summary.sh tests ==="
85
+
86
+ # --- Case 1: Clean summary with PASS — should pass ---
87
+ D1="$WORK/case1"
88
+ make_fixture "$D1" "REQ-001" << 'EOF'
89
+ # Test Execution Summary — REQ-001
90
+
91
+ ## Test design
92
+
93
+ **Layers planned:** unit, e2e
94
+ **Layers covered:** unit ✓, e2e ✓
95
+
96
+ ## Gate Results
97
+
98
+ | Gate | Result | Details |
99
+ | ---------------- | -------- | -------------------- |
100
+ | TypeScript | PASS | 0 errors |
101
+ | E2E Tests | PASS | All specs green |
102
+ | Unit Tests | PASS | 42 passed |
103
+
104
+ **Final assessment:** All gates passed.
105
+ EOF
106
+ EXIT1=$(run_validator "$D1")
107
+ assert_exit "clean summary with PASS — should pass" 0 "$EXIT1"
108
+
109
+ # --- Case 2: Summary with 'deferred' — should fail ---
110
+ D2="$WORK/case2"
111
+ make_fixture "$D2" "REQ-002" << 'EOF'
112
+ # Test Execution Summary — REQ-002
113
+
114
+ ## Test design
115
+
116
+ **Layers covered:** unit ✓, e2e deferred
117
+
118
+ ## Gate Results
119
+
120
+ | Gate | Result | Details |
121
+ | ---------------- | -------- | ---------------------------------------- |
122
+ | E2E Tests | DEFERRED | Playwright browsers not installed locally |
123
+
124
+ **Final assessment:** All local gates passed. E2E deferred to CI.
125
+ EOF
126
+ EXIT2=$(run_validator "$D2")
127
+ assert_exit "summary with 'deferred' — should fail" 1 "$EXIT2"
128
+
129
+ # --- Case 3: Summary with 'Deferred to CI' — should fail ---
130
+ D3="$WORK/case3"
131
+ make_fixture "$D3" "REQ-003" << 'EOF'
132
+ # Test Execution Summary — REQ-003
133
+
134
+ **Final assessment:** All gates passed. E2E Deferred to CI.
135
+ EOF
136
+ EXIT3=$(run_validator "$D3")
137
+ assert_exit "summary with 'Deferred to CI' — should fail" 1 "$EXIT3"
138
+
139
+ # --- Case 4: Summary with 'browsers not installed' — should fail ---
140
+ D4="$WORK/case4"
141
+ make_fixture "$D4" "REQ-004" << 'EOF'
142
+ # Test Execution Summary — REQ-004
143
+
144
+ ## Gate Results
145
+
146
+ | Gate | Result | Details |
147
+ | ---------------- | -------- | ---------------------------------------- |
148
+ | E2E Tests | SKIPPED | Playwright browsers not installed locally |
149
+
150
+ **Final assessment:** E2E skipped — browsers not installed.
151
+ EOF
152
+ EXIT4=$(run_validator "$D4")
153
+ assert_exit "summary with 'browsers not installed' — should fail" 1 "$EXIT4"
154
+
155
+ # --- Case 5: Summary with NOT_NEEDED and rationale — should pass ---
156
+ D5="$WORK/case5"
157
+ make_fixture "$D5" "REQ-005" << 'EOF'
158
+ # Test Execution Summary — REQ-005
159
+
160
+ ## Gate Results
161
+
162
+ | Gate | Result | Details |
163
+ | ---------------- | ------------ | ------------------------------------------ |
164
+ | E2E Tests | NOT_NEEDED | Schema-only change, no UI surface |
165
+
166
+ **Final assessment:** All gates passed. E2E not needed for this REQ.
167
+ EOF
168
+ EXIT5=$(run_validator "$D5")
169
+ assert_exit "summary with NOT_NEEDED and rationale — should pass" 0 "$EXIT5"
170
+
171
+ # --- Case 6: Summary with SKIPPED and rationale — should pass ---
172
+ D6="$WORK/case6"
173
+ make_fixture "$D6" "REQ-006" << 'EOF'
174
+ # Test Execution Summary — REQ-006
175
+
176
+ ## Gate Results
177
+
178
+ | Gate | Result | Details |
179
+ | ---------------- | ------------ | ------------------------------------------ |
180
+ | E2E Tests | SKIPPED | API-only change, no UI surface (operator-approved) |
181
+
182
+ **Final assessment:** All gates passed. E2E skipped — API-only change.
183
+ EOF
184
+ EXIT6=$(run_validator "$D6")
185
+ assert_exit "summary with SKIPPED and rationale — should pass" 0 "$EXIT6"
186
+
187
+ # --- Case 7: Summary with SKIPPED but no rationale — should fail ---
188
+ D7="$WORK/case7"
189
+ make_fixture "$D7" "REQ-007" << 'EOF'
190
+ # Test Execution Summary — REQ-007
191
+
192
+ ## Gate Results
193
+
194
+ | Gate | Result | Details |
195
+ | ---------------- | ------------ | ---------- |
196
+ | E2E Tests | SKIPPED | |
197
+
198
+ **Final assessment:** All gates passed.
199
+ EOF
200
+ EXIT7=$(run_validator "$D7")
201
+ assert_exit "summary with SKIPPED but no rationale — should fail" 1 "$EXIT7"
202
+
203
+ # --- Case 8: No REQ references — should pass (skip) ---
204
+ D8="$WORK/case8"
205
+ rm -rf "$D8"
206
+ mkdir -p "$D8"
207
+ cd "$D8"
208
+ git init -q --initial-branch=main
209
+ git config user.email "test@example.com"
210
+ git config user.name "test"
211
+ echo "x" > f.txt
212
+ git add f.txt
213
+ git commit -q -m "initial"
214
+ git checkout -q -b feature
215
+ echo "y" > g.txt
216
+ git add g.txt
217
+ git commit -q -m "docs: update README"
218
+ EXIT8=$(run_validator "$D8")
219
+ assert_exit "no REQ references — should pass (skip)" 0 "$EXIT8"
220
+
221
+ # --- Case 9: No test-execution-summary.md — should pass (skip) ---
222
+ D9="$WORK/case9"
223
+ make_fixture "$D9" "REQ-009" << 'EOF'
224
+ # placeholder
225
+ EOF
226
+ rm "$D9/compliance/evidence/REQ-009/test-execution-summary.md"
227
+ EXIT9=$(run_validator "$D9")
228
+ assert_exit "no test-execution-summary.md — should pass (skip)" 0 "$EXIT9"
229
+
230
+ # --- Case 10: Summary with 'e2e deferred' in final assessment — should fail ---
231
+ D10="$WORK/case10"
232
+ make_fixture "$D10" "REQ-010" << 'EOF'
233
+ # Test Execution Summary — REQ-010
234
+
235
+ ## Gate Results
236
+
237
+ | Gate | Result | Details |
238
+ | ---------------- | -------- | --------------- |
239
+ | E2E Tests | PASS | All specs green |
240
+
241
+ **Final assessment:** All gates passed. E2E deferred to CI for critical tier.
242
+ EOF
243
+ EXIT10=$(run_validator "$D10")
244
+ assert_exit "summary with 'e2e deferred' in final assessment — should fail" 1 "$EXIT10"
245
+
246
+ # --- Case 11: NOT_NEEDED without rationale — should fail ---
247
+ D11="$WORK/case11"
248
+ make_fixture "$D11" "REQ-011" << 'EOF'
249
+ # Test Execution Summary — REQ-011
250
+
251
+ ## Gate Results
252
+
253
+ | Gate | Result | Details |
254
+ | ---------------- | ------------ | ---------- |
255
+ | E2E Tests | NOT_NEEDED | |
256
+
257
+ **Final assessment:** All gates passed.
258
+ EOF
259
+ EXIT11=$(run_validator "$D11")
260
+ assert_exit "summary with NOT_NEEDED but no rationale — should fail" 1 "$EXIT11"
261
+
262
+ echo ""
263
+ echo "=== Summary: $PASS pass / $FAIL fail ==="
264
+
265
+ if [ "$FAIL" -gt 0 ]; then
266
+ exit 1
267
+ fi
268
+ exit 0
@@ -241,8 +241,34 @@ Halt and report the gap to the user:
241
241
 
242
242
  Do **not** proceed to Phase 6 until all gaps are resolved. The user may choose to skip an AC (e.g. API-only, transport-only) — that's valid, but it must be an explicit decision recorded in the test-execution-summary, not an omission.
243
243
 
244
+ **Write the evidence-wiring sentinel (devaudit-installer#226).** After all Phase 5½ checks pass (all `@requirement`, `evidenceShot()`, and `tagTest()` calls verified), write a `.e2e-evidence-wired` sentinel file in the repo root so the pre-push hook and `sdlc-implementer` Phase 2 step 5b can verify evidence wiring was validated:
245
+
246
+ ```bash
247
+ echo "WIRED $(date -u +%Y-%m-%dT%H:%M:%SZ) REQ-XXX" > .e2e-evidence-wired
248
+ ```
249
+
250
+ If the user explicitly skipped an AC (e.g. API-only), note it in the sentinel:
251
+
252
+ ```bash
253
+ echo "WIRED $(date -u +%Y-%m-%dT%H:%M:%SZ) REQ-XXX (AC3 skipped — API-only)" > .e2e-evidence-wired
254
+ ```
255
+
256
+ The file is gitignored and never committed — it's a local-only signal that evidence wiring was validated in this working directory.
257
+
244
258
  ### Phase 6 — Execute and report
245
259
 
260
+ **Pre-flight: browser availability (devaudit-installer#238).** Before running the suite, verify Playwright browsers are installed:
261
+
262
+ ```bash
263
+ npx playwright install --dry-run 2>&1 | grep -q "is already installed" || npx playwright install
264
+ ```
265
+
266
+ If the install fails (e.g. missing system dependencies on Linux), run `npx playwright install --with-deps` (requires sudo on some systems — ask the operator). Do not defer E2E execution to CI because browsers are not installed. Installing browsers takes ~30 seconds; deferring breaks the evidence trail.
267
+
268
+ **Do not defer E2E to CI (devaudit-installer#238).** If browsers are not installed, install them. If the dev server will not start, debug it. If the database is not running, start it. CI is a safety net, not a replacement for local E2E execution. The `.e2e-gate-passed` sentinel must be written by a local run — skipping it by deferring to CI breaks the evidence-completeness chain and causes the evidence-completeness gate (#237) to fire with false negatives.
269
+
270
+ **Gate state vocabulary (devaudit-installer#240).** In `test-execution-summary.md`, E2E gate results must be one of: `PASS`, `FAIL`, `NOT_NEEDED` (with reason), or `SKIPPED` (with operator-approved rationale). The word "deferred" must never appear in `test-execution-summary.md` — not as a gate state, not in prose, not in final assessment. The CI validator (`validate-test-summary.sh`) rejects any file containing "deferred" anywhere. If E2E was not run locally, record it as `SKIPPED` with the reason and flag it as a gate failure for the reviewer. Do not write "E2E deferred to CI" or "Playwright browsers not installed locally" — these are environment issues, not gate states.
271
+
246
272
  Run the suite. Strategy:
247
273
 
248
274
  1. **Iterate focused.** During fix-and-verify, run only the failing specs (`--grep`, spec-path args, or a CI input that scopes to a subset). Cycle time is what makes the loop tractable — full regression for every iteration burns CI budget and operator patience. Expect to loop: fix → focused run → fix → focused run, many times.
@@ -404,8 +430,8 @@ Wrap up with a summary the user can drop into the PR or ticket:
404
430
  **Then feed the test-design record (devaudit#50).** The Stage 3 `test-execution-summary.md` (generated per `3-compile-evidence.md` Step 1a) carries a `## Test design` section at the top. Before Stage 3 finalises the file, populate that section with the design-time decisions this skill made, so the SDLC has a recorded trace that scope was *decided*, not implicit:
405
431
 
406
432
  - **Layers planned** — which of `unit | integration | e2e | visual | manual` applied to this REQ
407
- - **Layers covered** — same list with ✓ or `deferred`
408
- - **Deferrals** — explicit one-line rationale per deferred layer (`e2e N/A — schema-only, no UI yet` rather than silent absence)
433
+ - **Layers covered** — same list with ✓ or `NOT_NEEDED` (with reason)
434
+ - **Exemptions** — explicit one-line rationale per exempt layer (`e2e NOT_NEEDED — schema-only, no UI yet` rather than silent absence). Do not use "deferred" as a gate state — it is not a valid SDLC state (devaudit-installer#240). The CI validator (`validate-test-summary.sh`) will reject any summary containing "deferred".
409
435
  - **Skill invocation** — _"`e2e-test-engineer` invoked on turn N during Phase 2"_, with a turn pointer the reviewer can verify against the chat transcript
410
436
 
411
437
  If you authored or modified `e2e/**/*.spec.ts` directly without invoking this skill, that's a delegation gap — the `sdlc-implementer` Phase 2 audit (devaudit#132) will catch it before Phase 3. The honest record is: the skill ran (or didn't), the layers were chosen for stated reasons, and the test-execution-summary attribution points back at the chat turn where the decision happened.
@@ -265,7 +265,7 @@ Runs **first**, before any `REQ-XXX` is assigned. It decides which of the six ch
265
265
  3. **Announce a "Workflow Decision" block** (template below): change-type, commit-type, whether a `REQ-XXX` is needed, risk class, which stages/gates run, which approvals the **operator** must perform (UAT four-eyes, Production approval), and what is **skipped**.
266
266
  4. **Pause policy — pause-when-it-matters.** Pause for explicit confirmation on **tracked / heavier** paths, or when classification is **ambiguous**; **announce-and-auto-proceed** on trivial / housekeeping. The operator can always reclassify ("treat this as housekeeping" / "this is HIGH risk").
267
267
  5. **Route — and stay on to completion.** A route is a choice of _which workflow to drive_, never a hand-off that abandons the operator. Whatever the path, the skill keeps guiding step by step until no further action is required (typically: merged).
268
- - **tracked** (feature / bug fix / refactor / perf) → continue into Phase 1 below (full Stages 1–5). **Write the skill-invocation sentinel** (devaudit-installer#226): `echo "INVOKED $(date -u +%Y-%m-%dT%H:%M:%SZ)" > .sdlc-implementer-invoked`. This file is gitignored and never committed it's a local-only signal that the pre-push hook checks before allowing `feat`/`fix`/`refactor`/`perf` commits to be pushed. Without this sentinel, the pre-push hook will refuse the push and `validate-commits.sh` in CI will flag missing RTM provenance.
268
+ - **tracked** (feature / bug fix / refactor / perf) → continue into Phase 1 below (full Stages 1–5). **Write the skill-invocation sentinel** (devaudit-installer#226): `npx devaudit-sdlc --phase=issue`. This invokes the v0.2.0 CLI engine, which appends a JSON phase record to `.sdlc-implementer-invoked` (a gitignored, local-only file). The pre-push hook checks for this file's existence before allowing `feat`/`fix`/`refactor`/`perf` commits to be pushed. Without this sentinel, the pre-push hook will refuse the push and `validate-commits.sh` in CI will flag missing RTM provenance. Each subsequent phase transition (Phase 1, 2, 3, 4, 5) must also invoke `npx devaudit-sdlc --phase=<N>` to append its phase record to the sentinel.
269
269
  - **housekeeping / trivial** → drive the **Lightweight path** below to completion. No `REQ-XXX`, no RTM row, no evidence pack, no portal release approvals — but the skill still branches, runs the gates, opens the PR, and walks the operator through review → merge.
270
270
  - **compliance-doc-only** → drive the same Lightweight path as a docs push (or PR, per the project's flow) referencing the **existing** `REQ-XXX`: no new requirement and no quality-gate ceremony, but driven through to merge.
271
271
  6. **Write labels back.** Apply the inferred `type:*` / `risk:*` labels so the issue ends up labelled — `gh label create <label> --force` to ensure the label exists (idempotent; no failure if a label-seeding step never ran), then `gh issue edit <N> --add-label <label>`. Future triage is then a glance.
@@ -409,6 +409,15 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
409
409
  - **Requirements deviation** (an AC is wrong, incomplete, or missing) — trigger the [requirements gap flow](#requirements-gap-flow-devaudit-installer212). Do NOT just note it as a plan deviation. The AC table must be updated, `requirements-aligner` must be re-invoked to re-check SRS alignment, and the RTM must be updated.
410
410
 
411
411
  The classification question: "Did the implementation deviate from the plan's approach (how to build it), or did the plan's requirements deviate from reality (what to build)?" If the former, note and continue. If the latter, trigger the requirements gap flow.
412
+
413
+ 4b. **Reconcile test-plan.md file paths with actual files (devaudit-installer#241).** After writing/updating tests (both unit and E2E), diff the actual test file paths against `compliance/evidence/REQ-XXX/test-plan.md`. The test plan was authored during Stage 1 with predicted file paths — during implementation, tests are often added to existing files instead of creating new ones. This is a natural drift, but it must be reconciled before committing so `validate-compliance-artifacts.sh` doesn't fail at PR time.
414
+
415
+ For each file path referenced in `test-plan.md`:
416
+ - If the file exists on disk → OK, no action needed.
417
+ - If the file does not exist → check whether a test covering the same AC was added to a different file. If so, update `test-plan.md` to reference the actual file path.
418
+ - If the file does not exist and no equivalent test was found → **HALT**: "test-plan.md references <file> but no test file exists and no equivalent test covering the same AC was found. Either create the test file, update test-plan.md to point to the actual test file, or remove the entry if the AC is no longer relevant."
419
+
420
+ Commit the updated `test-plan.md` alongside the test code. This is a file-path reconciliation — it does not change the AC table (that's the step 5b plan ↔ test-scope AC consistency check, which is separate and checks AC drift, not file-path drift).
412
421
  5. **Run gates locally, cheap-first.** The gates are not equivalent-cost — `npm run lint` is seconds, `npx playwright test` is 30–60 minutes. Iterate on the fast gates; spend the e2e cost once.
413
422
 
414
423
  **Fast gates** (run on every change, ideally pre-commit):
@@ -428,7 +437,9 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
428
437
  git diff --name-only "$INTEGRATION_BRANCH"...HEAD -- 'app/**/*.tsx' 'src/**/*.tsx' 'pages/**/*.tsx' 'app/**/*.jsx' 'src/**/*.jsx' 'pages/**/*.jsx'
429
438
  ```
430
439
 
431
- - **If UI-facing files are present:** check for `.e2e-gate-passed` sentinel file (written by `e2e-test-engineer` after a successful run) or `playwright-report/` directory with recent content. If neither exists, **HALT**: "E2E gate was not run. The change touches UI-facing files. Run `npx playwright test` (or invoke `e2e-test-engineer`) before committing. The pre-push hook will also block this push."
440
+ - **If UI-facing files are present:** check for `.e2e-gate-passed` sentinel file (written by `e2e-test-engineer` after a successful run) or `playwright-report/` directory with recent content. If neither exists, **HALT**: "E2E gate was not run. The change touches UI-facing files. Run `npx playwright test` (or invoke `e2e-test-engineer`) before committing. Do not defer to CI — install Playwright browsers with `npx playwright install` if needed. The pre-push hook will also block this push."
441
+
442
+ **Also check for `.e2e-evidence-wired` sentinel (devaudit-installer#226).** If `e2e/**/*.spec.ts` files were authored or modified, verify the `.e2e-evidence-wired` sentinel exists (written by `e2e-test-engineer` Phase 5½ after validating `tagTest()` and `evidenceShot()` calls). If missing, **HALT**: "Evidence wiring validation (Phase 5½) was not run. The change includes E2E spec files but no evidence wiring sentinel. Invoke `e2e-test-engineer` to validate `tagTest()` and `evidenceShot()` calls before committing. The pre-push hook will also block this push."
432
443
  - **If no UI-facing files (API-only, config, docs):** skip the check. Note the exemption in the commit body: "E2E gate skipped — no UI-facing files in this change."
433
444
  - **If `e2e-test-engineer` was invoked and determined e2e is not needed** (e.g. schema-only change): the skill writes `.e2e-gate-passed` with a `NOT_NEEDED` reason. The sentinel check passes. Note the exemption in the commit body: "E2E gate not needed — e2e-test-engineer assessed no UI surface (turn N)."
434
445
 
@@ -491,6 +502,14 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
491
502
  - The nil report uploads as `incident_report` evidence via `compliance-evidence.yml`'s `upload_incident_report` function, flipping `ISO29119.3.5.4` to COVERED for clean releases.
492
503
  - If incidents WERE closed, skip nil report generation — the populated incident report(s) from `incident-export.yml` serve as the evidence.
493
504
 
505
+ 5b. **Validate test-execution-summary.md gate states (devaudit-installer#240).** Before uploading evidence, verify that `compliance/evidence/REQ-XXX/test-execution-summary.md` does not contain invalid gate states. Run:
506
+
507
+ ```bash
508
+ bash scripts/validate-test-summary.sh origin/main
509
+ ```
510
+
511
+ If the validator fails, fix the summary before proceeding. E2E gate results must be one of: `PASS`, `FAIL`, `NOT_NEEDED` (with reason), or `SKIPPED` (with operator-approved rationale). The word "deferred" must never appear in `test-execution-summary.md` — not as a gate state, not in prose, not in final assessment. "Deferred to CI" and "Playwright browsers not installed locally" are environment issues, not gate states. The CI validator (`validate-test-summary.sh`) will reject any summary containing "deferred" or "browsers not installed" on PRs to main.
512
+
494
513
  6. **Organise artefacts** under `compliance/evidence/REQ-XXX/` with date-prefixed naming:
495
514
 
496
515
  ```
@@ -246,6 +246,8 @@ jobs:
246
246
  DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
247
247
  steps:
248
248
  - uses: actions/checkout@v6
249
+ with:
250
+ fetch-depth: 0
249
251
 
250
252
  - name: Resolve DevAudit base URL
251
253
  run: |
@@ -321,6 +323,14 @@ jobs:
321
323
  if [ -z "$SINCE_REF" ]; then
322
324
  SINCE_REF="HEAD~50"
323
325
  fi
326
+ if ! git rev-parse --verify "$SINCE_REF" >/dev/null 2>&1; then
327
+ echo "::warning::Ref ${SINCE_REF} not found — falling back to HEAD~10"
328
+ SINCE_REF="HEAD~10"
329
+ fi
330
+ if ! git rev-parse --verify "$SINCE_REF" >/dev/null 2>&1; then
331
+ echo "::warning::Ref ${SINCE_REF} not found — skipping bundled changes"
332
+ exit 0
333
+ fi
324
334
  echo "Scanning bundled changes since ${SINCE_REF} for ${VERSION}"
325
335
  BUNDLED_FILE="compliance/pending-releases/BUNDLED-CHANGES-${VERSION}.md"
326
336
  mkdir -p compliance/pending-releases
@@ -783,10 +793,35 @@ jobs:
783
793
  # specs exist on disk. If specs are found but weren't run in
784
794
  # this tier, emit a non-blocking warning instead of an error.
785
795
  SPECS_ON_DISK=$(grep -rl --include='*.spec.ts' -e "@requirement ${REQ}" -e "tagTest.*${REQ}" -e "'${REQ}'" e2e/ 2>/dev/null | wc -l)
796
+
797
+ # devaudit-installer#237 — also scan unit test files for
798
+ # @requirement REQ-XXX annotations. A REQ with thorough unit
799
+ # test coverage but no E2E specs (legitimate for API-only or
800
+ # backend-only changes) should not be hard-blocked.
801
+ UNIT_TESTS_ON_DISK=$(grep -rl --include='*.test.ts' --include='*.test.tsx' -e "@requirement ${REQ}" -e "'${REQ}'" e2e/ __tests__/ tests/ src/ 2>/dev/null | wc -l)
802
+
803
+ # devaudit-installer#237 — check for test-execution-summary.md
804
+ # as a secondary signal. This is a human-authored document and
805
+ # weaker than machine-generated evidence, so it only converts
806
+ # a hard error to a warning when combined with unit test files
807
+ # on disk — never alone.
808
+ HAS_SUMMARY=false
809
+ if [ -f "compliance/evidence/${REQ}/test-execution-summary.md" ]; then
810
+ HAS_SUMMARY=true
811
+ fi
812
+
786
813
  if [ "$SPECS_ON_DISK" -gt 0 ]; then
814
+ # Scenario B: E2E specs exist on disk but outside the smoke tier
787
815
  echo "::warning::Evidence completeness gate: ${REQ} has zero tagged tests in this smoke run and zero per-AC screenshots, but ${SPECS_ON_DISK} spec file(s) on disk reference it (outside the smoke project). Evidence exists but was not executed in this CI tier. Consider running --project=critical or --grep \"${REQ}\" to capture evidence."
816
+ elif [ "$UNIT_TESTS_ON_DISK" -gt 0 ] && [ "$HAS_SUMMARY" = "true" ]; then
817
+ # Scenario C: unit test files exist with @requirement annotations
818
+ # and a test-execution-summary.md documents coverage
819
+ echo "::warning::Evidence completeness gate: ${REQ} has zero E2E tagged tests and zero screenshots, but ${UNIT_TESTS_ON_DISK} unit test file(s) on disk reference it and a test-execution-summary.md exists. Unit test evidence found but execution was not verified in this run. If this is an API-only or backend-only change, this is expected."
788
820
  else
789
- echo "::error::Evidence completeness gate: ${REQ} has zero tagged tests AND zero per-AC screenshots. The release cannot proceed to UAT with no traceable evidence. Ensure specs carry @requirement ${REQ} annotations and evidenceShot() calls (skill Phase 5½, issue #170)."
821
+ # Scenario A: genuinely no evidence no E2E specs, no unit tests,
822
+ # no test-execution-summary. Hard error preserved (#237 Constraint 3).
823
+ echo "::error::Evidence completeness gate: ${REQ} has zero traceable evidence — no tagged E2E tests, no per-AC screenshots, no unit test files with @requirement annotations, and no test-execution-summary.md. The release cannot proceed to UAT with no traceable per-REQ evidence."
824
+ echo "::error::If E2E specs exist for this REQ, ensure they carry @requirement ${REQ} JSDoc annotations and tagTest() calls (e2e-test-engineer Phase 5½, issue #170). If unit tests exist, add @requirement ${REQ} annotations to the .test.ts files. If this is a pure config/docs change with no testable surface, document it in test-execution-summary.md."
790
825
  EVIDENCE_GAPS=$((EVIDENCE_GAPS + 1))
791
826
  fi
792
827
  else
@@ -30,5 +30,8 @@ jobs:
30
30
  - name: Validate compliance artifacts
31
31
  run: bash scripts/validate-compliance-artifacts.sh origin/main
32
32
 
33
+ - name: Validate test execution summaries
34
+ run: bash scripts/validate-test-summary.sh origin/main
35
+
33
36
  - name: Validate commit conventions
34
37
  run: bash scripts/validate-commits.sh origin/main
@@ -30,6 +30,8 @@ echo "Pre-push: TypeScript check passed."
30
30
  # Only fires if UI-facing files are in the push. Checks for either
31
31
  # playwright-report/ directory or .e2e-gate-passed sentinel (written by
32
32
  # e2e-test-engineer after a successful run).
33
+ # Also checks .e2e-evidence-wired sentinel if e2e spec files changed
34
+ # (written by e2e-test-engineer Phase 5½ after validating tagTest/evidenceShot).
33
35
  INTEGRATION_BRANCH=$(jq -r '.integration_branch // "develop"' sdlc-config.json 2>/dev/null || echo "develop")
34
36
 
35
37
  # Read stdin to get the pushed refs (husky pre-push protocol)
@@ -70,6 +72,23 @@ while read -r local_ref local_sha remote_ref remote_sha; do
70
72
  fi
71
73
  echo "Pre-push: E2E evidence check passed."
72
74
  fi
75
+
76
+ # Check for E2E spec file changes — require .e2e-evidence-wired sentinel
77
+ SPEC_FILES=$(git diff --name-only "$RANGE" -- 'e2e/**/*.spec.ts' 2>/dev/null || true)
78
+
79
+ if [ -n "$SPEC_FILES" ]; then
80
+ if [ ! -f .e2e-evidence-wired ]; then
81
+ echo ""
82
+ echo "ERROR: Evidence wiring validation (Phase 5½) was not run."
83
+ echo " E2E spec files changed in this push:"
84
+ echo "$SPEC_FILES" | sed 's/^/ /'
85
+ echo ""
86
+ echo " Invoke e2e-test-engineer to validate tagTest() and evidenceShot() calls."
87
+ echo " Bypass with --no-verify (last resort, not a habit)."
88
+ exit 1
89
+ fi
90
+ echo "Pre-push: E2E evidence wiring check passed."
91
+ fi
73
92
  done
74
93
 
75
94
  # ── 3. Skill-invocation sentinel (devaudit-installer#226) ────────────
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "devaudit-sdlc",
3
+ "version": "0.3.0",
4
+ "description": "DevAudit SDLC CLI Engine — cross-agent terminal-driven SDLC orchestrator.",
5
+ "bin": {
6
+ "devaudit-sdlc": "./src/bin/devaudit-sdlc.js"
7
+ },
8
+ "files": [
9
+ "src/bin/devaudit-sdlc.js",
10
+ "src/blueprints"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/metasession-dev/DevAudit-Installer",
18
+ "directory": "sdlc"
19
+ },
20
+ "license": "Apache-2.0"
21
+ }