@metasession.co/devaudit-cli 0.1.70 → 0.1.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metasession.co/devaudit-cli",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "DevAudit CLI — installs, syncs, and operates the Metasession SDLC across consumer projects.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@clack/prompts": "^0.8.2",
36
- "@metasession.co/devaudit-plugin-sdk": "^0.1.70",
36
+ "@metasession.co/devaudit-plugin-sdk": "^0.1.72",
37
37
  "ajv": "^8.20.0",
38
38
  "commander": "^12.1.0",
39
39
  "consola": "^3.2.3",
@@ -41,7 +41,9 @@
41
41
  "execa": "^9.5.1"
42
42
  },
43
43
  "devDependencies": {
44
+ "@types/js-yaml": "^4.0.9",
44
45
  "@types/node": "^22.9.0",
46
+ "js-yaml": "^5.1.0",
45
47
  "msw": "^2.7.0",
46
48
  "tsup": "^8.3.5",
47
49
  "typescript": "^5.7.2",
@@ -26,6 +26,13 @@
26
26
  # bare date — when exactly one ticket is open, attribute to it.
27
27
  # Multiple open tickets stays ambiguous → bare-date fallback.
28
28
  #
29
+ # Note (DevAudit-Installer#220): `devaudit update` syncs now include
30
+ # `[skip ci]` in their commit message, so they no longer trigger CI
31
+ # at all. The bare-date fallback (step 5) is therefore reached only by
32
+ # human-authored housekeeping commits — the intended use case. Skipped
33
+ # housekeeping changes are bundled into the next REQ release via
34
+ # `generate-bundled-changes.sh` (run by the register-release CI job).
35
+ #
29
36
  # Step 4-bis (DevAudit-Installer#95) is the zero-ceremony equivalent:
30
37
  # RTM.md is the file the operator already maintains as the source of
31
38
  # truth for release state. When step 4 finds no ticket and exactly one
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # generate-bundled-changes.sh
3
+ #
4
+ # Scans commits since a given ref (tag, SHA, or branch) and filters for
5
+ # housekeeping commit types (chore/docs/ci/build/test/revert) that were
6
+ # skipped via [skip ci] or otherwise not individually released. Outputs
7
+ # a markdown summary suitable for upload as `bundled_changes` evidence
8
+ # against a REQ-tagged release.
9
+ #
10
+ # Synced into the consumer's `scripts/` by `devaudit update`; invoked
11
+ # from `ci.yml` register-release job when the derived version is a
12
+ # REQ-tagged release (not bare-date).
13
+ #
14
+ # Usage:
15
+ # bash scripts/generate-bundled-changes.sh <since-ref> [<version>]
16
+ #
17
+ # Where:
18
+ # <since-ref> — git ref to scan from (tag, SHA, or branch name).
19
+ # Typically the previous release tag or the merge-base
20
+ # of the current branch and the previous release.
21
+ # <version> — (optional) the release version for the header.
22
+ # Defaults to "current release".
23
+ #
24
+ # Output (stdout): markdown document listing housekeeping commits
25
+ # absorbed into this release.
26
+ #
27
+ # DevAudit-Installer#220.
28
+
29
+ set -euo pipefail
30
+
31
+ SINCE_REF="${1:-}"
32
+ VERSION="${2:-current release}"
33
+
34
+ if [ -z "$SINCE_REF" ]; then
35
+ echo "Usage: bash scripts/generate-bundled-changes.sh <since-ref> [<version>]" >&2
36
+ exit 1
37
+ fi
38
+
39
+ # Verify the ref exists before proceeding.
40
+ if ! git rev-parse --verify "$SINCE_REF" >/dev/null 2>&1; then
41
+ echo "Error: ref '$SINCE_REF' not found in git history." >&2
42
+ exit 1
43
+ fi
44
+
45
+ # Housekeeping commit type prefixes (Conventional Commits).
46
+ # feat/fix are tracked changes — excluded. Anything else is housekeeping.
47
+ # The git log format is "<short-sha>\t<subject>", so the regex matches
48
+ # the commit type after the tab character.
49
+ HOUSEKEEPING_TYPES='^[0-9a-f]+ (chore|docs|ci|build|test|revert|style|perf|refactor)(\(.+\))?!?:'
50
+
51
+ # Collect commits since the ref, filtering for housekeeping types.
52
+ # Format: <short-sha>\t<subject>
53
+ COMMITS=$(git log "$SINCE_REF"..HEAD --format='%h %s' 2>/dev/null || true)
54
+
55
+ if [ -z "$COMMITS" ]; then
56
+ # No commits since ref — output an empty summary.
57
+ echo "## Bundled Changes"
58
+ echo ""
59
+ echo "No housekeeping commits found since \`${SINCE_REF}\`."
60
+ echo ""
61
+ exit 0
62
+ fi
63
+
64
+ # Filter for housekeeping commit types.
65
+ BUNDLED=$(echo "$COMMITS" | grep -E "$HOUSEKEEPING_TYPES" || true)
66
+
67
+ if [ -z "$BUNDLED" ]; then
68
+ echo "## Bundled Changes"
69
+ echo ""
70
+ echo "No housekeeping commits found since \`${SINCE_REF}\`."
71
+ echo ""
72
+ exit 0
73
+ fi
74
+
75
+ # Count the bundled commits.
76
+ COUNT=$(echo "$BUNDLED" | wc -l | tr -d ' ')
77
+
78
+ # Generate the markdown summary.
79
+ echo "## Bundled Changes"
80
+ echo ""
81
+ echo "The following ${COUNT} housekeeping commit(s) were absorbed into release \`${VERSION}\` since \`${SINCE_REF}\`:"
82
+ echo ""
83
+ echo "$BUNDLED" | while IFS=$'\t' read -r SHA SUBJECT; do
84
+ echo "- \`${SHA}\` ${SUBJECT}"
85
+ done
86
+ echo ""
87
+ echo "These commits were not individually released (tooling syncs / housekeeping)."
88
+ echo "Gate evidence above covers the full state of develop at CI time, including these changes."
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env bash
2
+ # generate-bundled-changes.test.sh — Tests for generate-bundled-changes.sh.
3
+ #
4
+ # Builds a throwaway git repo per case, crafts commits with mixed types,
5
+ # runs the helper against it, asserts on the stdout content. Hermetic:
6
+ # runs inside mktemp'd directories that are torn down at the end.
7
+ #
8
+ # Usage:
9
+ # ./scripts/generate-bundled-changes.test.sh
10
+
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
14
+ HELPER="$SCRIPT_DIR/generate-bundled-changes.sh"
15
+ [ -x "$HELPER" ] || chmod +x "$HELPER"
16
+
17
+ PASS=0
18
+ FAIL=0
19
+
20
+ TMPDIR_BASE="$(mktemp -d)"
21
+ trap 'rm -rf "$TMPDIR_BASE"' EXIT
22
+
23
+ # Build a fresh git fixture with an initial commit.
24
+ make_fixture() {
25
+ local dir="$1"
26
+ rm -rf "$dir"
27
+ mkdir -p "$dir"
28
+ cd "$dir"
29
+ git init -q --initial-branch=main
30
+ git config user.email "test@example.com"
31
+ git config user.name "test"
32
+ echo "x" > f.txt
33
+ git add f.txt
34
+ git commit -q -m "feat: initial commit [REQ-001]"
35
+ }
36
+
37
+ # Add a commit with a given message.
38
+ add_commit() {
39
+ local msg="$1"
40
+ echo "$(date +%s%N)" >> f.txt
41
+ git add f.txt
42
+ git commit -q -m "$msg"
43
+ }
44
+
45
+ assert_contains() {
46
+ local desc="$1" want="$2" got="$3"
47
+ if echo "$got" | grep -qF "$want"; then
48
+ echo " PASS: $desc"
49
+ PASS=$((PASS + 1))
50
+ else
51
+ echo " FAIL: $desc"
52
+ echo " want (substring): $want"
53
+ echo " got:"
54
+ echo "$got" | sed 's/^/ /'
55
+ FAIL=$((FAIL + 1))
56
+ fi
57
+ }
58
+
59
+ assert_not_contains() {
60
+ local desc="$1" want="$2" got="$3"
61
+ if echo "$got" | grep -qF "$want"; then
62
+ echo " FAIL: $desc"
63
+ echo " should not contain: $want"
64
+ echo " got:"
65
+ echo "$got" | sed 's/^/ /'
66
+ FAIL=$((FAIL + 1))
67
+ else
68
+ echo " PASS: $desc"
69
+ PASS=$((PASS + 1))
70
+ fi
71
+ }
72
+
73
+ # ─── Test 1: Mixed commits — only housekeeping types in output ─────
74
+ echo "Test 1: mixed commits filter to housekeeping only"
75
+ DIR1="$TMPDIR_BASE/test1"
76
+ make_fixture "$DIR1"
77
+ add_commit "chore: sync DevAudit templates from v0.1.69 to v0.1.70 [skip ci]"
78
+ add_commit "docs: update API reference for /bookings endpoint"
79
+ add_commit "feat: add booking widget [REQ-042]"
80
+ add_commit "chore: bump eslint 9.0.5 to 9.0.6"
81
+ add_commit "fix: resolve null pointer in booking service [REQ-042]"
82
+ # Tag the initial commit as the "since" ref
83
+ SINCE=$(git rev-list --max-parents=0 HEAD)
84
+ OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-042" 2>&1 || true)
85
+ assert_contains "housekeeping chore: sync in output" "chore: sync DevAudit templates" "$OUTPUT"
86
+ assert_contains "housekeeping docs: update in output" "docs: update API reference" "$OUTPUT"
87
+ assert_contains "housekeeping chore: bump eslint in output" "chore: bump eslint" "$OUTPUT"
88
+ assert_not_contains "feat commit excluded" "feat: add booking widget" "$OUTPUT"
89
+ assert_not_contains "fix commit excluded" "fix: resolve null pointer" "$OUTPUT"
90
+ assert_contains "header present" "## Bundled Changes" "$OUTPUT"
91
+ assert_contains "version in header" "REQ-042" "$OUTPUT"
92
+ echo
93
+
94
+ # ─── Test 2: No housekeeping commits ────────────────────────────────
95
+ echo "Test 2: no housekeeping commits — empty summary"
96
+ DIR2="$TMPDIR_BASE/test2"
97
+ make_fixture "$DIR2"
98
+ add_commit "feat: add feature A [REQ-010]"
99
+ add_commit "fix: fix bug B [REQ-010]"
100
+ SINCE=$(git rev-list --max-parents=0 HEAD)
101
+ OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-010" 2>&1 || true)
102
+ assert_contains "no housekeeping message" "No housekeeping commits found" "$OUTPUT"
103
+ echo
104
+
105
+ # ─── Test 3: All housekeeping types captured ────────────────────────
106
+ echo "Test 3: all housekeeping commit types captured"
107
+ DIR3="$TMPDIR_BASE/test3"
108
+ make_fixture "$DIR3"
109
+ add_commit "ci: update workflow timeout"
110
+ add_commit "build: bump node version to 22"
111
+ add_commit "test: add unit test for booking"
112
+ add_commit "revert: remove experimental feature"
113
+ add_commit "style: fix formatting in utils"
114
+ add_commit "perf: optimize query in booking"
115
+ add_commit "refactor: extract validation logic"
116
+ SINCE=$(git rev-list --max-parents=0 HEAD)
117
+ OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-030" 2>&1 || true)
118
+ assert_contains "ci: type captured" "ci: update workflow timeout" "$OUTPUT"
119
+ assert_contains "build: type captured" "build: bump node version" "$OUTPUT"
120
+ assert_contains "test: type captured" "test: add unit test for booking" "$OUTPUT"
121
+ assert_contains "revert: type captured" "revert: remove experimental feature" "$OUTPUT"
122
+ assert_contains "style: type captured" "style: fix formatting" "$OUTPUT"
123
+ assert_contains "perf: type captured" "perf: optimize query" "$OUTPUT"
124
+ assert_contains "refactor: type captured" "refactor: extract validation" "$OUTPUT"
125
+ echo
126
+
127
+ # ─── Test 4: No commits since ref ───────────────────────────────────
128
+ echo "Test 4: no commits since ref — empty summary"
129
+ DIR4="$TMPDIR_BASE/test4"
130
+ make_fixture "$DIR4"
131
+ SINCE=$(git rev-parse HEAD)
132
+ OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-001" 2>&1 || true)
133
+ assert_contains "no commits message" "No housekeeping commits found" "$OUTPUT"
134
+ echo
135
+
136
+ # ─── Test 5: Invalid ref ────────────────────────────────────────────
137
+ echo "Test 5: invalid ref — error exit"
138
+ DIR5="$TMPDIR_BASE/test5"
139
+ make_fixture "$DIR5"
140
+ set +e
141
+ OUTPUT=$(bash "$HELPER" "nonexistent-ref" "REQ-001" 2>&1)
142
+ RC=$?
143
+ set -e
144
+ if [ "$RC" -ne 0 ]; then
145
+ echo " PASS: non-zero exit code for invalid ref"
146
+ PASS=$((PASS + 1))
147
+ else
148
+ echo " FAIL: expected non-zero exit code for invalid ref"
149
+ FAIL=$((FAIL + 1))
150
+ fi
151
+ echo
152
+
153
+ # ─── Test 6: Scoped commit types (chore(deps)) ─────────────────────
154
+ echo "Test 6: scoped housekeeping commits captured"
155
+ DIR6="$TMPDIR_BASE/test6"
156
+ make_fixture "$DIR6"
157
+ add_commit "chore(deps): bump eslint from 9.0.5 to 9.0.6"
158
+ add_commit "ci(workflow): update timeout to 30 minutes"
159
+ SINCE=$(git rev-list --max-parents=0 HEAD)
160
+ OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-050" 2>&1 || true)
161
+ assert_contains "scoped chore(deps) captured" "chore(deps): bump eslint" "$OUTPUT"
162
+ assert_contains "scoped ci(workflow) captured" "ci(workflow): update timeout" "$OUTPUT"
163
+ echo
164
+
165
+ # ─── Test 7: Missing since-ref argument ────────────────────────────
166
+ echo "Test 7: missing since-ref argument — error exit"
167
+ DIR7="$TMPDIR_BASE/test7"
168
+ make_fixture "$DIR7"
169
+ set +e
170
+ OUTPUT=$(bash "$HELPER" 2>&1)
171
+ RC=$?
172
+ set -e
173
+ if [ "$RC" -ne 0 ]; then
174
+ echo " PASS: non-zero exit code for missing argument"
175
+ PASS=$((PASS + 1))
176
+ else
177
+ echo " FAIL: expected non-zero exit code for missing argument"
178
+ FAIL=$((FAIL + 1))
179
+ fi
180
+ echo
181
+
182
+ echo "=== Summary: ${PASS} pass / ${FAIL} fail ==="
183
+ [ "$FAIL" -eq 0 ]
@@ -74,6 +74,29 @@ while IFS= read -r sha; do
74
74
  EXIT_CODE=1
75
75
  continue
76
76
  fi
77
+
78
+ # RTM provenance check (devaudit-installer#226): verify the REQ-XXX
79
+ # cited in the commit has an sdlc-implementer@<version> provenance
80
+ # stamp in compliance/RTM.md. Without the stamp, the skill was not
81
+ # invoked and the RTM row was created manually.
82
+ REQ_ID=$(echo "$SUBJECT" | grep -oP '\[REQ-\d{3,}\]' | tr -d '[]' || true)
83
+ if [ -z "$REQ_ID" ]; then
84
+ REQ_ID=$(echo "$BODY" | grep -oiP 'Ref:\s*REQ-\d{3,}' | grep -oP 'REQ-\d{3,}' | tail -1 || true)
85
+ fi
86
+ if [ -n "$REQ_ID" ] && [ -f compliance/RTM.md ]; then
87
+ RTM_ROW=$(grep -m1 -E "^\| ${REQ_ID} " compliance/RTM.md || true)
88
+ if [ -n "$RTM_ROW" ]; then
89
+ if ! echo "$RTM_ROW" | grep -q 'sdlc-implementer@'; then
90
+ echo "ERROR [$SHORT]: $REQ_ID in commit message has no sdlc-implementer provenance in RTM.md."
91
+ echo " The RTM row for $REQ_ID was not created by the sdlc-implementer skill."
92
+ echo " Either invoke sdlc-implementer and re-run, or add the provenance marker"
93
+ echo " 'sdlc-implementer@<version>' to the RTM row manually with operator sign-off."
94
+ FAILED=$((FAILED + 1))
95
+ EXIT_CODE=1
96
+ continue
97
+ fi
98
+ fi
99
+ fi
77
100
  ;;
78
101
  esac
79
102
 
@@ -288,6 +288,20 @@ There is no `regression/` sub-directory, no `@regression` tag, no manifest file.
288
288
 
289
289
  You don't need to do anything explicit for this step — it's a property of the pipeline, not an action. Surface it in the final report so the reviewer knows the new tests are now load-bearing for every future release.
290
290
 
291
+ **Write the E2E gate sentinel (devaudit-installer#226).** After a successful full regression run (Phase 6 step 2), write a `.e2e-gate-passed` sentinel file in the repo root so the pre-push hook and `sdlc-implementer` Phase 2 step 5b can verify the E2E gate ran:
292
+
293
+ ```bash
294
+ echo "PASSED $(date -u +%Y-%m-%dT%H:%M:%SZ) ${{ github.run_id }}" > .e2e-gate-passed
295
+ ```
296
+
297
+ If you determined e2e is not needed for this REQ (schema-only, API-only, no UI surface), write the sentinel with a `NOT_NEEDED` reason instead:
298
+
299
+ ```bash
300
+ echo "NOT_NEEDED $(date -u +%Y-%m-%dT%H:%M:%SZ) <reason>" > .e2e-gate-passed
301
+ ```
302
+
303
+ The file is gitignored and never committed — it's a local-only signal that the gate was run in this working directory.
304
+
291
305
  ### Filing defects
292
306
 
293
307
  Use whatever tracker integration you found in Phase 1: `gh issue create`, `glab issue create`, a Jira or Linear MCP tool, `az boards work-item create`. If nothing is available, produce a markdown report with each defect formatted ready to paste.
@@ -96,7 +96,7 @@ When the user asks for a change that goes beyond the current REQ's acceptance cr
96
96
 
97
97
  4. **Wait for the user to confirm one of:**
98
98
  - **(a) File a separate issue** (new REQ) — ship the current REQ as-is. The agent continues with the original scope.
99
- - **(b) Amend REQ-XXX's scope** — explicitly expand `test-scope.md` / `implementation-plan.md`, update the plan, invalidate existing evidence, and re-walk Stage 3. This option carries a warning: *"Amending scope after evidence is compiled (Stage 3+) invalidates the existing test-execution-summary, screenshots, and UAT verification. All Stage 3 evidence must be re-compiled."*
99
+ - **(b) Amend REQ-XXX's scope** — explicitly expand `test-scope.md` / `test-plan.md` / `implementation-plan.md`, update the plan, invalidate existing evidence, and re-walk Stage 3. Re-extract both `test-scope.md` and `test-plan.md` from the updated plan (Phase 1 step 5b drift management). This option carries a warning: *"Amending scope after evidence is compiled (Stage 3+) invalidates the existing test-execution-summary, screenshots, and UAT verification. All Stage 3 evidence must be re-compiled."*
100
100
  - **(c) Abandon the request** — do nothing, continue with the original scope.
101
101
 
102
102
  **Do not implement the out-of-scope change before the user picks (a), (b), or (c).** The inertia trap is real: the agent is mid-flow, the codebase is open, and the request sounds reasonable. The gate exists to interrupt that inertia — STOP, surface the scope gap, and let the user decide.
@@ -140,10 +140,11 @@ Amendment steps:
140
140
  2. Re-invoke `requirements-aligner` → propose new/updated SRS stubs
141
141
  3. Operator edits SRS stubs into canonical Given/When/Then prose in `docs/SRS.md`
142
142
  4. Update `compliance/RTM.md`
143
- 5. Commit: `compliance: [REQ-XXX] amend ACs requirements gap <description>`
144
- 6. If Phase 2+: re-run affected tests (delegating to `e2e-test-engineer` if e2e)
145
- 7. If Phase 3+: re-compile affected evidence
146
- 8. Continue from current phase
143
+ 5. Re-extract `compliance/evidence/REQ-XXX/test-scope.md` and `compliance/evidence/REQ-XXX/test-plan.md` from the updated plan (Phase 1 step 5b drift management, devaudit-installer#226)
144
+ 6. Commit: `compliance: [REQ-XXX] amend ACs requirements gap <description>`
145
+ 7. If Phase 2+: re-run affected tests (delegating to `e2e-test-engineer` if e2e)
146
+ 8. If Phase 3+: re-compile affected evidence
147
+ 9. Continue from current phase
147
148
 
148
149
  **(c) File a follow-up REQ** — ship the current REQ as-is, file a separate issue for the requirements gap. Appropriate when the gap is a genuine new behaviour that deserves its own cycle. Continue with the current REQ unchanged.
149
150
 
@@ -254,7 +255,7 @@ Runs **first**, before any `REQ-XXX` is assigned. It decides which of the six ch
254
255
  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**.
255
256
  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").
256
257
  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).
257
- - **tracked** (feature / bug fix / refactor / perf) → continue into Phase 1 below (full Stages 1–5).
258
+ - **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.
258
259
  - **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.
259
260
  - **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.
260
261
  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.
@@ -354,6 +355,15 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
354
355
 
355
356
  **Don't delete sections** — mark with `N/A — <reason>` if a clause genuinely doesn't apply (e.g. UI-only change with no personal-data scope). Empty stubs commit-then-upload as placeholder evidence and break the audit trail.
356
357
 
358
+ 5b. **Extract `test-scope.md` and `test-plan.md` from the implementation plan (devaudit-installer#226).** After step 5, extract two artefacts from the plan into `compliance/evidence/REQ-XXX/` so CI's `validate-compliance-artifacts.sh` finds them in the expected location:
359
+
360
+ - **`compliance/evidence/REQ-XXX/test-scope.md`** — the AC table with SRS-IDs, risk class, and verification method per AC. Extracted from the plan's "Acceptance criteria" table.
361
+ - **`compliance/evidence/REQ-XXX/test-plan.md`** — the test file listing with AC coverage mapping: which test file covers which AC, test type (unit/integration/e2e), and whether each AC has at least one test. Extracted from the plan's "Test scope" section.
362
+
363
+ These files are the CI validator's expected artefacts. Without them, `validate-compliance-artifacts.sh` fails and the pre-push hook blocks the push. The plan directory (`compliance/plans/REQ-XXX/`) retains the full plan; these are extracted subsets for CI validation.
364
+
365
+ **Drift management (devaudit-installer#226).** If the plan's AC table changes after step 5b (requirements gap flow option (b), scope-expansion halt gate option (b), or plan deviation), re-extract both `test-scope.md` and `test-plan.md` from the updated plan. The extracted files must always match the plan's current AC table. Phase 2 step 5b checks plan ↔ test-scope AC consistency before running E2E.
366
+
357
367
  6. **Invoke `requirements-aligner` to populate the SRS-ID column on the AC table.** The plan's "Acceptance criteria" table carries an SRS-ID column per AC; `requirements-aligner` fuzzy-matches each AC against `docs/SRS.md` and proposes new `REQ-AREA-NNN` stubs, flags stale items, or annotates `@srs-deferred`. Don't author the SRS-ID column inline — call via the standard Claude Code Skill mechanism (`Skill(name: "requirements-aligner", …)`). Block plan APPROVAL until every AC has a resolved SRS-ID per the skill's Phase 1 contract (configurable via `sdlc-config.json:requirements_aligner.block_on_stage_1`; ramp-up mode default-on for legacy projects).
358
368
 
359
369
  **SRS stub editing (devaudit-installer#212 Gap 5).** After `requirements-aligner` returns, the plan's SRS items proposed/touched section may contain N new stubs and M stale items. The operator must edit `docs/SRS.md` to flesh out the stubs into canonical Given/When/Then prose and update stale items. Do not proceed to Phase 2 until the SRS is updated. The skill enforces this by checking that no stub placeholders remain in `docs/SRS.md` for the proposed SRS-IDs before auto-continuing. A stub placeholder is any SRS entry containing `<TODO>` or the `@srs-stub` marker.
@@ -364,7 +374,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
364
374
  - **`requirements-aligner` fails** (e.g. no `docs/SRS.md`): If `block_on_stage_1` is true, halt — "requirements-aligner could not find SRS.md. Operator action — create `docs/SRS.md` or disable `requirements_aligner.block_on_stage_1` in `sdlc-config.json`." If false, warn and continue with `@srs-deferred` on all ACs.
365
375
  - **`adr-author` fails**: Warn and continue — ADR is advisory by default. Mark the plan's "Architecture decisions" section as "ADR assessment skipped — <error>".
366
376
  - **`risk-register-keeper` fails**: If `block_on_stage_1` is true, halt. If false, warn and continue — mark the plan's "Risk register entries" section as "Risk assessment skipped — <error>".
367
- 9. **Update `compliance/RTM.md`** with the new entry: REQ-XXX, title, risk class, linked issue, linked test cases (placeholder).
377
+ 9. **Update `compliance/RTM.md`** with the new entry: REQ-XXX, title, risk class, linked issue, linked test cases (placeholder), and a provenance marker `sdlc-implementer@<version>` (devaudit-installer#226). The provenance column is the last column in the RTM row — `validate-commits.sh` in CI checks for its presence when `feat`/`fix`/`refactor`/`perf` commits cite a REQ-XXX. Without the stamp, CI fails with "no sdlc-implementer provenance in RTM.md." If the RTM table doesn't have a provenance column, add one with header `Provenance`.
368
378
  10. **Post plan summary as an issue comment.** Format: TL;DR; Risk class + signals; Acceptance criteria (with SRS-IDs); Architectural decisions (ADR-NNN reference or no-ADR rationale); Risk register entries (RISK-NNN list); Technical approach (one paragraph); Dependencies; Test scope.
369
379
  11. **Checkpoint** — pause for human approval **iff** risk class is HIGH or CRITICAL. LOW and MEDIUM pass through to Phase 2 automatically. The checkpoint can be forced on for all classes via the `--require-plan-approval` flag (or `DEVAUDIT_REQUIRE_PLAN_APPROVAL=1` env var) for orgs that want it always-on.
370
380
  12. **Update SDLC status sticky** before exiting Phase 1: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 1 complete — plan written to compliance/plans/REQ-XXX/implementation-plan.md (risk class <CLASS>)" "Phase 2 — sdlc-implementer auto-continuing"` (or "Operator action — review plan + ping resume" if the HIGH/CRITICAL checkpoint paused).
@@ -401,6 +411,19 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
401
411
  **E2E gate** — run _once_, after the fast gates are clean:
402
412
  - `npx playwright test` (delegated to `e2e-test-engineer`, which has its own focused-iteration discipline for within-e2e fix-and-verify loops)
403
413
 
414
+ 5b. **E2E gate verification — mandatory before commit (devaudit-installer#226).** After running gates in step 5, verify the E2E gate actually ran before proceeding to step 7 (commit). This is the skill-level enforcement that backs the pre-push hook.
415
+
416
+ Check whether the change touches UI-facing files:
417
+ ```bash
418
+ git diff --name-only "$INTEGRATION_BRANCH"...HEAD -- 'app/**/*.tsx' 'src/**/*.tsx' 'pages/**/*.tsx' 'app/**/*.jsx' 'src/**/*.jsx' 'pages/**/*.jsx'
419
+ ```
420
+
421
+ - **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."
422
+ - **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."
423
+ - **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)."
424
+
425
+ **Plan ↔ test-scope AC consistency check (devaudit-installer#226 drift management).** Before running E2E, verify the AC table in `compliance/plans/REQ-XXX/implementation-plan.md` matches `compliance/evidence/REQ-XXX/test-scope.md`. Compare the AC IDs in both files — if they diverge (new AC added to plan but not re-extracted, or AC removed from plan but still in test-scope), **HALT**: "test-scope.md is out of sync with implementation-plan.md. AC IDs differ: <diff>. Re-extract test-scope.md and test-plan.md from the updated plan (Phase 1 step 5b) before running E2E."
426
+
404
427
  6. **On gate failure**, iterate up to N=3 attempts. Each iteration: read the failure output, propose a fix, apply, re-run. On exhausted attempts, halt with the full failure output and explicit resume instructions: "Gate <name> failed after N=3 attempts. Last failure: <output>. Operator action — fix the failure, commit to the feature branch, push, then ping `resume REQ-XXX`. The skill will re-run the gate from where it left off." Update the sticky with the same. Never use `--no-verify`, `eslint-disable`, `@ts-expect-error`, `xfail`, or any other bypass.
405
428
  7. **Commit** using Conventional Commits with `Ref: REQ-XXX` trailer and `Co-Authored-By: Claude` trailer. One commit per logical step; never amend a commit that's already been pushed.
406
429
  8. **Land the work on `$INTEGRATION_BRANCH`.** Push the feature branch, then open a PR `feat/REQ-XXX-<slug> → $INTEGRATION_BRANCH` and merge it once CI is green. This is the **integration hop** — there is no UAT four-eyes gate here (that's the release PR in Phase 4); for MEDIUM+ risk get a peer review on this PR per the project's norms. The push to `$INTEGRATION_BRANCH` is what triggers `ci.yml` to register the release and upload gate evidence. **Merge conflict resolution:** if the PR has merge conflicts (another feature branch merged first), pull the latest `$INTEGRATION_BRANCH` into the feature branch (`git merge "$INTEGRATION_BRANCH" --no-edit`), resolve conflicts (preferring the feature branch's changes for files this REQ touches), push, wait for CI. If conflicts are in files this REQ doesn't touch, halt — "Merge conflict in <files> from another feature. Operator action — review the conflict, these files are outside REQ-XXX's scope."
@@ -462,6 +485,9 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
462
485
 
463
486
  ```
464
487
  compliance/evidence/REQ-XXX/
488
+ ├── test-scope.md ← extracted in Phase 1 step 5b
489
+ ├── test-plan.md ← extracted in Phase 1 step 5b
490
+ ├── implementation-plan.md ← copied from compliance/plans/ in step 6b
465
491
  ├── srs-alignment.md ← produced in step 1 by requirements-aligner
466
492
  ├── architecture-decision.md ← produced in step 2 by adr-author
467
493
  ├── risk-assessment.md ← produced in step 3 by risk-register-keeper
@@ -474,6 +500,14 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
474
500
 
475
501
  Copy Playwright's `test-results/` folder verbatim into `YYYY-MM-DD_traces/` so trace-by-test-name is available for audit without walking the HTML report's hash-name index. For HIGH/CRITICAL releases the traces are part of the audit trail — _"what state was the page in when test X failed and was overridden?"_ answers in one `ls` instead of an HTML-report walk.
476
502
 
503
+ 6b. **Copy `implementation-plan.md` from `compliance/plans/` to `compliance/evidence/` (devaudit-installer#226).** The CI validator (`validate-compliance-artifacts.sh`) expects `implementation-plan.md` in `compliance/evidence/REQ-XXX/`, not in `compliance/plans/REQ-XXX/`. Copy (not move) the file so the plan directory retains the original:
504
+
505
+ ```bash
506
+ cp compliance/plans/REQ-XXX/implementation-plan.md compliance/evidence/REQ-XXX/implementation-plan.md
507
+ ```
508
+
509
+ This ensures the pre-push hook's compliance validator check passes. The plan directory retains the original for in-flight reference; the evidence directory has the copy for CI validation and audit.
510
+
477
511
  6. **Upload each artefact to the portal** — retry each upload up to 3 times with backoff (5s, 15s, 45s). If an upload still fails after 3 retries, mark it in the RTM as `UPLOAD_FAILED` and surface a warning. Do not mark the RTM as verified for failed uploads. On `resume REQ-XXX`, re-attempt failed uploads before proceeding to Phase 4.
478
512
  ```bash
479
513
  devaudit push <project-slug> REQ-XXX <evidence-type> <file> \
@@ -528,6 +562,12 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
528
562
 
529
563
  Invoked separately by the user after UAT activity on the portal. Trigger: "resume REQ-XXX", "REQ-XXX UAT done", or just re-firing the skill on the same issue.
530
564
 
565
+ 0. **Re-read state (devaudit-installer#226).** On resume, reconstruct state from the filesystem before acting:
566
+ - Re-read `compliance/RTM.md` to check the REQ-XXX row's current status.
567
+ - Check whether `compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md` still exists (not yet moved to `approved-releases/`).
568
+ - Check `git log` for the merge commit on `$RELEASE_BRANCH`.
569
+ - If resuming after an environment detour (not a UAT approval), re-read `compliance/evidence/REQ-XXX/` to see which artefacts already exist and resume at the appropriate phase.
570
+
531
571
  1. **Read portal state.** `curl` `https://devaudit.metasession.co/api/projects/<slug>/releases/<version>` and inspect the approval status. Retry up to 3 times with exponential backoff (5s, 15s, 45s) on 5xx responses. If all retries fail, halt — "Portal unreachable at <URL>. Cannot determine UAT approval state. Operator action — verify portal availability + API key, then ping `resume REQ-XXX`." If the API returns 401/403, halt — "Portal API key rejected (HTTP 401/403). The `DEVAUDIT_API_KEY` secret may be expired or revoked. Operator action — rotate the API key, then ping `resume REQ-XXX`." Never guess the portal state.
532
572
 
533
573
  2. **Branch on state:**
@@ -541,14 +581,23 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
541
581
  - If `released` (Option B): PATCH directly to `released` — `PATCH /releases/<version>` with `{"status": "released"}`.
542
582
  - If `prod_review` (Option A, default): after `post-deploy-prod.yml` completes, the release is at `prod_review`. Update the sticky to "Phase 5 — production deployed; awaiting prod approval on portal" and halt. The operator clicks "Approve Production" (`prod_review` → `prod_approved`) then "Mark as Released" (`prod_approved` → `released`) in the portal. On next `resume REQ-XXX`, the skill reads the portal state — if `released`, finalise (close issue, update sticky to terminal). If `prod_approved`, prompt the operator to click "Mark as Released". If still `prod_review`, report "awaiting production approval" and stop.
543
583
  - Comment on the issue: "Released. Production smoke evidence: <link>." (only after release status is `released`)
544
- - **Update SDLC status sticky** to the terminal state: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 5 complete — release marked Released; production smoke evidence uploaded" "Done close issue + retire feature branch (sdlc-implementer halts)"`.
584
+ - **Phase 5 close-out mandatory post-merge compliance steps (devaudit-installer#226).** After the release is marked `released` on the portal, perform the close-out:
585
+ 1. **Update RTM status** — change the REQ-XXX row from `TESTED - PENDING SIGN-OFF` to `APPROVED - DEPLOYED` in `compliance/RTM.md`.
586
+ 2. **Move release ticket** — move `compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md` to `compliance/approved-releases/`. This prevents CI's `upload-evidence` job from treating the REQ as in-scope for subsequent releases.
587
+ 3. **Verify portal approval** — confirm the DevAudit portal release record is in `released` status.
588
+ 4. **Commit the close-out** — `git add compliance/RTM.md compliance/pending-releases/ compliance/approved-releases/ && git commit -m "compliance: [REQ-XXX] close out release — RTM updated to APPROVED - DEPLOYED, ticket moved to approved-releases/"`.
589
+ 5. **Push the close-out commit** to `$INTEGRATION_BRANCH`.
590
+
591
+ These steps are mandatory — the native agent must not treat "PR merged" or "release marked Released" as completion without performing the close-out. Without moving the release ticket to `approved-releases/`, CI continues uploading evidence for the REQ on every subsequent release, polluting the portal's audit trail.
592
+ - **Update SDLC status sticky** to the terminal state: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 5 complete — release marked Released; production smoke evidence uploaded; close-out committed (RTM → APPROVED - DEPLOYED, ticket → approved-releases/)" "Done — close issue + retire feature branch (sdlc-implementer halts)"`.
545
593
  - Close the issue.
546
594
  - If production smoke fails: do NOT mark as Released. **Delegate incident filing to `governance-doc-author` (devaudit-installer#210 §6c)** — invoke `Skill(name: "governance-doc-author", args: "Production smoke failure for REQ-XXX. File an incident issue with the `incident` label + `### Framework attribution` section (ISO29119.3.5.4 + SOC2.CC7.2), severity `blocker`. Include the production URL, git SHA, testCycleId (CI run ID), and smoke results in the issue body.")`. Page the on-call per the project's incident playbook, follow the rollback plan from the implementation plan. If the rollback also fails: update the sticky to "Phase 5 CRITICAL — production smoke failed AND rollback failed. Production is in a broken state. Operator action — page on-call immediately, engage hosting platform support, declare incident per the project's incident playbook. This is beyond the skill's scope." Do NOT attempt further automated remediation. **Update the sticky** to reflect the incident state: `… "Phase 5 BLOCKED — production smoke failed; INCIDENT issue #N filed" "Operator action — read INCIDENT #N + execute rollback per plan"`.
547
595
 
548
596
  - **Changes requested** → run change-request loop:
549
597
  - Fetch change-request comments from the PR (`gh pr view <M> --comments`) and from the portal release page.
550
- - **Classify the change request (devaudit-installer#210 §6b):** Assess whether the change request is a **defect** (the implementation doesn't match the AC — the reviewer found a bug) or a **scope change** (the AC itself needs amendment — the reviewer wants new behaviour the AC didn't specify).
598
+ - **Classify the change request (devaudit-installer#210 §6b, devaudit-installer#212 Gap 6):** Assess whether the change request is a **defect** (the implementation doesn't match the AC — the reviewer found a bug), a **requirements gap** (the reviewer identified behaviour that should have been an AC but wasn't — the plan was incomplete, not the implementation), or a **scope change** (the reviewer wants new behaviour this REQ genuinely shouldn't deliver).
551
599
  - **If it's a defect:** Delegate to `e2e-test-engineer` to file the incident with the `incident` label + `### Framework attribution` section before fixing. Invoke `Skill(name: "e2e-test-engineer", args: "Phase 6 triage — UAT reviewer found defect in REQ-XXX. Change-request comment: <summary>. Triage and file application defects with the `incident` label + `### Framework attribution` section per the standard convention.")`. `e2e-test-engineer` returns with a summary of filed incidents. Then fix the defect in the same Phase 2 re-run. This ensures UAT-discovered defects produce `incident_report` evidence, closing `ISO29119.3.5.4` for the defect — not just silently fixing it.
600
+ - **If it's a requirements gap:** Trigger the [requirements gap flow](#requirements-gap-flow-devaudit-installer212) with the reviewer's feedback as the gap report. The operator decides: accept the gap (ship without the edge case), amend the ACs (add the edge case as a new AC, update plan + SRS, re-walk Phase 2-3), or file a follow-up REQ. If "amend" is chosen, the change-request iteration includes the new AC in the delta-plan section, `requirements-aligner` is re-invoked for the new AC, and the SRS is updated. Do NOT file an incident — the plan was incomplete, not the implementation buggy. Do NOT use the scope-expansion halt gate — this is a plan quality issue, not a scope creep request.
552
601
  - **If it's a scope change:** Use the existing scope-expansion halt gate (REQ-SKILL-IMPLEMENTER-019) — file a separate issue or amend the REQ scope. Do NOT file an incident — the AC itself needs amendment, not a defect fix.
553
602
  - Add a new `## Change-request iteration N` section to `compliance/plans/REQ-XXX/implementation-plan.md` describing what changed and why.
554
603
  - **Update the release ticket status** (`compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md`): set `**Status:**` to `CHANGES REQUESTED — ITERATION N` when entering the iteration. Commit the ticket update alongside the delta-plan section in the same iteration commit.
@@ -569,6 +618,59 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
569
618
 
570
619
  - **UAT rejected** → halt. Post on the issue: "UAT rejected this release. Options: (a) close PR and re-plan from scratch, (b) escalate to a different reviewer, (c) appeal the rejection with new context." See [`references/change-request-loop.md`](./references/change-request-loop.md) §"If the change-request is a rejection" for details. Do not attempt to address — the reviewer has marked the release fundamentally broken. Wait for the user to decide.
571
620
 
621
+ ## Native agent responsibilities and re-invocation protocol (devaudit-installer#226)
622
+
623
+ The `skill` tool is a **stateless instruction injection** — not a persistent sub-agent. When invoked, the skill's markdown is loaded into the native agent's context. The native agent executes the steps. But the skill has no persistent state, no execution monitoring, and no re-invoke triggers. The following protocol bridges this gap.
624
+
625
+ ### Native agent responsibilities
626
+
627
+ The native agent (the AI coding assistant executing this skill) is responsible for:
628
+
629
+ - **Command execution** — running `npx playwright test`, `npm run dev`, `mongod`, etc.
630
+ - **Environment debugging** — port conflicts, stale locks, missing binaries, Playwright browser installs
631
+ - **Browser inspection** — reading page snapshots, DOM state, error dialogs during E2E debugging
632
+ - **External service interaction** — Railway deploy status, UAT health checks, DevAudit portal (human-only)
633
+ - **Git operations** — merge conflicts, push retries, branch management
634
+
635
+ The skill is responsible for workflow decisions, compliance checklists, and phase transitions. The native agent executes; the skill decides what to do next.
636
+
637
+ ### Re-invocation protocol
638
+
639
+ After any **environment detour** — service startup failure, test debugging iteration loop, CI failure fix — the native agent must re-invoke this skill to get back on track:
640
+
641
+ ```
642
+ Skill(name: "sdlc-implementer", args: "resume REQ-XXX — <detour description>, re-enter at Phase N")
643
+ ```
644
+
645
+ The skill re-reads state from the filesystem and continues from where it left off. It is idempotent on re-entry:
646
+
647
+ - Re-read `compliance/RTM.md` to confirm the REQ-XXX row exists and has a provenance stamp.
648
+ - Re-read `compliance/evidence/REQ-XXX/` to see which artefacts already exist.
649
+ - Check `git log` for commits already made on this branch.
650
+ - Resume at the appropriate phase: if Phase 2 implementation is complete but Phase 3 artefacts are missing, resume at Phase 3 step 1.
651
+
652
+ The native agent must NOT continue to the next phase (Phase 3 → Phase 4, Phase 2 → Phase 3) without re-invoking the skill. The only exception is the skill's own auto-continue steps (Phase 2 step 11) where the skill explicitly says it will continue to the next phase in the same turn.
653
+
654
+ **PR merged to main ≠ done.** After the release PR is merged to `main`, the native agent must re-invoke the skill for Phase 5 close-out (see Phase 5 step 0 below). The merge is not the end of the workflow — post-merge compliance steps are mandatory.
655
+
656
+ ### Commit-scoping rule for SRS updates (devaudit-installer#226)
657
+
658
+ When a commit is authored as part of REQ-XXX's SDLC flow, the commit subject must cite `[REQ-XXX]` — the **active REQ** driving the current SDLC flow — regardless of which other REQs the content touches. Other REQs may appear in the body for traceability, but never in the subject.
659
+
660
+ **Correct:**
661
+ ```
662
+ docs: [REQ-085] add SRS stubs for REQ-083/084/085 — order status revert, checkout separation, tab payment
663
+
664
+ Ref: REQ-083, REQ-084
665
+ ```
666
+
667
+ **Incorrect:**
668
+ ```
669
+ docs: [REQ-083/084] update SRS with order status revert fix and checkout separation
670
+ ```
671
+
672
+ The portal's commit scanner parses `REQ-XXX` tags from commit subjects within a PR's diff. Citing the wrong REQ in the subject causes the portal to associate out-of-scope REQs with the current release.
673
+
572
674
  ## Compliance constraints
573
675
 
574
676
  Hard rules — the skill's SKILL.md fails review if any of these are violated. Audited against ISO 29119, ISO 27001, SOC 2, GDPR, and the EU AI Act; details in [`references/compliance-constraints.md`](./references/compliance-constraints.md).