@metasession.co/devaudit-cli 0.1.45 → 0.1.49

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.45",
3
+ "version": "0.1.49",
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.45",
36
+ "@metasession.co/devaudit-plugin-sdk": "^0.1.49",
37
37
  "commander": "^12.1.0",
38
38
  "consola": "^3.2.3",
39
39
  "env-paths": "^3.0.0",
@@ -184,13 +184,33 @@ fi
184
184
  # Issue: devaudit#263.
185
185
  SUCCEEDED=0
186
186
  FAILED=0
187
+ # devaudit#133 — central stub guard. Any file still carrying the
188
+ # DevAudit starter banner ("STARTER TEMPLATE — REPLACE BEFORE
189
+ # COMMITTING" / "...BEFORE GOING TO PRODUCTION" — both phrasings)
190
+ # is skipped before the upload attempt so unedited placeholders
191
+ # can't flip a clause to COVERED off a stub. The check is binary-
192
+ # safe (-a) so it doesn't choke on PNGs or other non-text files.
193
+ SKIPPED=0
187
194
  TOTAL_SIZE=0
188
195
  UPLOAD_URL="${DEVAUDIT_BASE_URL}/api/evidence/upload"
189
196
  MAX_ATTEMPTS=${UPLOAD_MAX_ATTEMPTS:-5}
190
197
  INITIAL_BACKOFF_SECONDS=${UPLOAD_INITIAL_BACKOFF_SECONDS:-1}
191
198
 
199
+ is_unedited_starter_stub() {
200
+ # Match BOTH banner phrasings the SDLC has shipped (v0.1.36 changed
201
+ # the wording from "...GOING TO PRODUCTION" to "...COMMITTING").
202
+ # -a forces binary→text so we don't error on PNGs/PDFs; the regex
203
+ # won't match either of those formats by accident.
204
+ grep -aqE 'STARTER TEMPLATE.+REPLACE BEFORE' "$1"
205
+ }
206
+
192
207
  for FILE in "${FILES[@]}"; do
193
208
  FILENAME=$(basename "$FILE")
209
+ if is_unedited_starter_stub "$FILE"; then
210
+ echo "SKIPPED ${FILENAME} — unedited starter stub (replace the STARTER TEMPLATE banner to upload)"
211
+ SKIPPED=$((SKIPPED + 1))
212
+ continue
213
+ fi
194
214
  FILE_SIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE")
195
215
  echo -n "Uploading ${FILENAME}... "
196
216
  CURL_ARGS=(
@@ -267,8 +287,10 @@ done
267
287
  # --- Summary ---
268
288
  echo ""
269
289
  echo "=== Upload Summary ==="
270
- echo "Files: ${SUCCEEDED} succeeded, ${FAILED} failed (${#FILES[@]} total)"
290
+ echo "Files: ${SUCCEEDED} succeeded, ${FAILED} failed, ${SKIPPED} skipped (${#FILES[@]} total)"
271
291
  echo "Total size: $((TOTAL_SIZE / 1024)) KB"
292
+ # Skipped stubs are intentional (devaudit#133) — they don't fail the
293
+ # run. Only true upload failures bump the exit code.
272
294
  if [ "$FAILED" -gt 0 ]; then
273
295
  exit 1
274
296
  fi
@@ -29,9 +29,9 @@ last_reviewed_at: "REPLACE — YYYY-MM-DD"
29
29
 
30
30
  ## Uploading this artefact
31
31
 
32
- - **File path:** `compliance/governance/incident-report.md` (the template) or `compliance/governance/incident-report-<id>.md` (per-incident — recommended; the `incident-export.yml` workflow auto-produces these from closed GitHub issues labelled `incident`)
33
- - **Upload trigger:** automatic — on every push to `develop` that touches `compliance/**`, `compliance-evidence.yml` uploads this file as `incident_report` evidence via the `upload_governance` helper.
34
- - **Verify after merge:** open `/projects/<slug>/compliance`. `ISO29119.3.5.4` always flips to COVERED (baseline). `SOC2.CC7.2`, `GDPR.Art-33`, `GDPR.Art-34` flip only when the relevant attribution sections below are non-stub.
32
+ - **File path:** `compliance/governance/incident-report-<id>.md` (per-incident — recommended; the `incident-export.yml` workflow auto-produces these from closed GitHub issues labelled `incident`). The bare `incident-report.md` is the unedited starter — kept on disk as a reference but **skipped by the uploader** until you replace the STARTER TEMPLATE banner.
33
+ - **Upload trigger:** automatic — on every push to `develop` that touches `compliance/**`, `compliance-evidence.yml` globs `incident-report*.md` under both layouts and uploads each non-stub file as `incident_report` evidence via the `upload_governance` helper. The starter stub is filtered out centrally by `upload-evidence.sh` (devaudit#133).
34
+ - **Verify after merge:** open `/projects/<slug>/compliance`. `ISO29119.3.5.4` flips to COVERED only when a non-stub `incident-report*.md` lands. `SOC2.CC7.2`, `GDPR.Art-33`, `GDPR.Art-34` flip only when the relevant attribution sections below are non-stub.
35
35
  - **Refresh cadence:** none — incidents are point-in-time. Authoring is event-driven.
36
36
 
37
37
  ## Framework attribution — which clauses THIS incident closes
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bash
2
+ # update-sdlc-status.sh — Post or update the canonical SDLC status
3
+ # sticky comment on a REQ tracking issue (devaudit#131).
4
+ #
5
+ # Purpose: long-running SDLC issues accumulate dozens of comments.
6
+ # The operator scrolling the thread can't find "where are we right
7
+ # now" without re-reading. This helper writes a marker-tagged comment
8
+ # at a predictable shape; subsequent calls find + edit the existing
9
+ # comment instead of stacking new ones, so the latest status always
10
+ # lives in exactly one place on the issue.
11
+ #
12
+ # Idempotent — find-or-create. The marker is HTML-commented so it
13
+ # doesn't show up in the rendered issue UI but is greppable via the
14
+ # API. Subsequent invocations on the same issue replace the body
15
+ # without dropping the marker.
16
+ #
17
+ # Usage:
18
+ # ./scripts/update-sdlc-status.sh <issue-number> "<last-step>" "<next-step>" [--repo owner/name] [--dry-run]
19
+ #
20
+ # Examples:
21
+ # ./scripts/update-sdlc-status.sh 322 \
22
+ # "Phase 2 complete — feat branch landed on develop" \
23
+ # "Phase 3 — sdlc-implementer auto-continuing"
24
+ #
25
+ # ./scripts/update-sdlc-status.sh 322 \
26
+ # "Phase 4 — release PR #455 opened" \
27
+ # "Operator action — review + merge develop→main when ready" \
28
+ # --repo metasession-dev/wawagardenbar-app
29
+ #
30
+ # Required:
31
+ # - `gh` CLI authenticated (uses GITHUB_TOKEN or the current `gh auth` session)
32
+ # - The issue must exist
33
+ #
34
+ # Optional flags:
35
+ # --repo owner/name Override repo (defaults to the cwd's git remote)
36
+ # --dry-run Print the body + the gh command that would run,
37
+ # without making any API calls. Used by the test
38
+ # suite + safe for operator inspection.
39
+
40
+ set -euo pipefail
41
+
42
+ if [ "$#" -lt 3 ]; then
43
+ cat <<'USAGE' >&2
44
+ Usage: update-sdlc-status.sh <issue-number> "<last-step>" "<next-step>" [--repo owner/name] [--dry-run]
45
+ USAGE
46
+ exit 1
47
+ fi
48
+
49
+ ISSUE_NUM="$1"
50
+ LAST_STEP="$2"
51
+ NEXT_STEP="$3"
52
+ shift 3
53
+
54
+ REPO=""
55
+ DRY_RUN=false
56
+ while [ "$#" -gt 0 ]; do
57
+ case "$1" in
58
+ --repo)
59
+ REPO="$2"
60
+ shift 2
61
+ ;;
62
+ --dry-run)
63
+ DRY_RUN=true
64
+ shift
65
+ ;;
66
+ *)
67
+ echo "Unknown flag: $1" >&2
68
+ exit 1
69
+ ;;
70
+ esac
71
+ done
72
+
73
+ # Validate issue number is numeric early so we don't make bogus API
74
+ # calls when the caller fat-fingers the arg order.
75
+ if ! [[ "$ISSUE_NUM" =~ ^[1-9][0-9]*$ ]]; then
76
+ echo "Error: issue number must be a positive integer, got: $ISSUE_NUM" >&2
77
+ exit 1
78
+ fi
79
+
80
+ MARKER='<!-- sdlc-implementer:status -->'
81
+
82
+ # Body shape — keep this compact and load-bearing. The marker MUST be
83
+ # the first line so the find-existing pass can use startswith() in
84
+ # the gh JSON filter without false positives.
85
+ BODY=$(cat <<EOF
86
+ $MARKER
87
+
88
+ **🟢 LAST STEP** — $LAST_STEP
89
+
90
+ **🔵 NEXT STEP** — $NEXT_STEP
91
+
92
+ ---
93
+
94
+ _Updated by \`sdlc-implementer\` on every stage transition. The full SDLC trail lives in the comments below; this comment is the always-current pointer._
95
+ EOF
96
+ )
97
+
98
+ REPO_FLAG=""
99
+ if [ -n "$REPO" ]; then
100
+ REPO_FLAG="--repo $REPO"
101
+ fi
102
+
103
+ if [ "$DRY_RUN" = "true" ]; then
104
+ echo "[dry-run] would update sticky on issue #$ISSUE_NUM${REPO:+ in $REPO}"
105
+ echo "----- body -----"
106
+ echo "$BODY"
107
+ echo "----- end body -----"
108
+ exit 0
109
+ fi
110
+
111
+ # Find an existing status sticky on this issue. We grep through the
112
+ # comments looking for the canonical marker; if found, edit it; if
113
+ # not, create a fresh one.
114
+ #
115
+ # gh's --jq filter handles the lookup server-side so we don't drag
116
+ # every comment back to local. `startswith` is the right matcher
117
+ # because the marker is always the first line.
118
+ EXISTING_ID=""
119
+ # Build the api endpoint. Without --repo, gh resolves from the current
120
+ # git remote — same as `gh issue …` does elsewhere in the framework.
121
+ if [ -n "$REPO" ]; then
122
+ EXISTING_ID=$(gh api "repos/$REPO/issues/$ISSUE_NUM/comments" --paginate \
123
+ --jq '.[] | select(.body | startswith("'"$MARKER"'")) | .id' | head -1)
124
+ else
125
+ EXISTING_ID=$(gh api "repos/{owner}/{repo}/issues/$ISSUE_NUM/comments" --paginate \
126
+ --jq '.[] | select(.body | startswith("'"$MARKER"'")) | .id' | head -1)
127
+ fi
128
+
129
+ if [ -n "$EXISTING_ID" ]; then
130
+ echo "Updating existing SDLC status sticky (comment id: $EXISTING_ID)"
131
+ if [ -n "$REPO" ]; then
132
+ gh api "repos/$REPO/issues/comments/$EXISTING_ID" -X PATCH \
133
+ --field "body=$BODY" >/dev/null
134
+ else
135
+ gh api "repos/{owner}/{repo}/issues/comments/$EXISTING_ID" -X PATCH \
136
+ --field "body=$BODY" >/dev/null
137
+ fi
138
+ else
139
+ echo "Posting new SDLC status sticky on issue #$ISSUE_NUM"
140
+ # shellcheck disable=SC2086 # REPO_FLAG must split on space
141
+ gh issue comment "$ISSUE_NUM" $REPO_FLAG --body "$BODY" >/dev/null
142
+ fi
143
+
144
+ echo "SDLC status updated."
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ # update-sdlc-status.test.sh — Tests for the SDLC status sticky helper
3
+ # (devaudit#131). Exercises --dry-run so no real API call is needed.
4
+ #
5
+ # Usage:
6
+ # ./scripts/update-sdlc-status.test.sh
7
+
8
+ set -euo pipefail
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
+ HELPER="$SCRIPT_DIR/update-sdlc-status.sh"
12
+ [ -x "$HELPER" ] || chmod +x "$HELPER"
13
+
14
+ PASS=0
15
+ FAIL=0
16
+ ok() { echo " ✓ $1"; PASS=$((PASS + 1)); }
17
+ no() { echo " ✗ $1"; FAIL=$((FAIL + 1)); }
18
+
19
+ case_missing_args() {
20
+ echo "case: missing args exits non-zero with a usage line"
21
+ local out exit_code
22
+ out=$("$HELPER" 2>&1) && exit_code=0 || exit_code=$?
23
+ if [ "$exit_code" -ne 0 ]; then
24
+ ok "exit code non-zero ($exit_code)"
25
+ else
26
+ no "expected non-zero exit on missing args"
27
+ fi
28
+ if printf '%s\n' "$out" | grep -q "Usage:"; then
29
+ ok "stderr includes Usage line"
30
+ else
31
+ no "stderr missing Usage; got:\n$out"
32
+ fi
33
+ }
34
+
35
+ case_non_numeric_issue() {
36
+ echo "case: non-numeric issue number fails fast"
37
+ local out exit_code
38
+ out=$("$HELPER" "abc" "last" "next" --dry-run 2>&1) && exit_code=0 || exit_code=$?
39
+ if [ "$exit_code" -ne 0 ]; then
40
+ ok "exit code non-zero"
41
+ else
42
+ no "expected failure on non-numeric issue number"
43
+ fi
44
+ if printf '%s\n' "$out" | grep -q "must be a positive integer"; then
45
+ ok "error message names the problem"
46
+ else
47
+ no "wrong error message:\n$out"
48
+ fi
49
+ }
50
+
51
+ case_dry_run_emits_body() {
52
+ echo "case: --dry-run prints the body without invoking gh"
53
+ local out exit_code
54
+ out=$("$HELPER" 42 "Phase 1 complete — plan written" "Phase 2 — implement" --dry-run 2>&1) && exit_code=0 || exit_code=$?
55
+ if [ "$exit_code" -eq 0 ]; then
56
+ ok "exit code 0"
57
+ else
58
+ no "expected exit 0, got $exit_code"
59
+ return
60
+ fi
61
+ if printf '%s\n' "$out" | grep -q '<!-- sdlc-implementer:status -->'; then
62
+ ok "body includes marker comment"
63
+ else
64
+ no "body missing marker; got:\n$out"
65
+ fi
66
+ if printf '%s\n' "$out" | grep -qE '\*\*🟢 LAST STEP\*\* — Phase 1 complete'; then
67
+ ok "body includes LAST STEP line"
68
+ else
69
+ no "LAST STEP line missing or wrong format; got:\n$out"
70
+ fi
71
+ if printf '%s\n' "$out" | grep -qE '\*\*🔵 NEXT STEP\*\* — Phase 2 — implement'; then
72
+ ok "body includes NEXT STEP line"
73
+ else
74
+ no "NEXT STEP line missing or wrong format; got:\n$out"
75
+ fi
76
+ if printf '%s\n' "$out" | grep -q 'would update sticky on issue #42'; then
77
+ ok "dry-run header names the issue"
78
+ else
79
+ no "dry-run header missing issue number; got:\n$out"
80
+ fi
81
+ }
82
+
83
+ case_dry_run_repo_flag() {
84
+ echo "case: --repo flag is reflected in the dry-run header"
85
+ local out
86
+ out=$("$HELPER" 5 "a" "b" --repo metasession-dev/example --dry-run 2>&1)
87
+ if printf '%s\n' "$out" | grep -q 'in metasession-dev/example'; then
88
+ ok "dry-run header includes repo"
89
+ else
90
+ no "dry-run header missing repo; got:\n$out"
91
+ fi
92
+ }
93
+
94
+ case_unknown_flag_rejected() {
95
+ echo "case: unknown flag rejected"
96
+ local out exit_code
97
+ out=$("$HELPER" 1 "a" "b" --bogus 2>&1) && exit_code=0 || exit_code=$?
98
+ if [ "$exit_code" -ne 0 ] && printf '%s\n' "$out" | grep -q 'Unknown flag'; then
99
+ ok "unknown flag rejected with message"
100
+ else
101
+ no "expected unknown-flag rejection; got exit $exit_code, output:\n$out"
102
+ fi
103
+ }
104
+
105
+ case_marker_is_first_line() {
106
+ echo "case: marker is the FIRST line of the body (find-existing relies on startswith)"
107
+ local out
108
+ out=$("$HELPER" 1 "a" "b" --dry-run 2>&1)
109
+ # Extract just the body between the markers we print
110
+ local body
111
+ body=$(printf '%s\n' "$out" | awk '/^----- body -----$/,/^----- end body -----$/')
112
+ local first
113
+ first=$(printf '%s\n' "$body" | sed -n '2p') # line 1 is the "----- body -----" header; line 2 is the body's first line
114
+ if printf '%s\n' "$first" | grep -q '<!-- sdlc-implementer:status -->'; then
115
+ ok "marker is the body's first line"
116
+ else
117
+ no "marker not on first line; first line was: '$first'"
118
+ fi
119
+ }
120
+
121
+ case_missing_args
122
+ case_non_numeric_issue
123
+ case_dry_run_emits_body
124
+ case_dry_run_repo_flag
125
+ case_unknown_flag_rejected
126
+ case_marker_is_first_line
127
+
128
+ echo ""
129
+ echo "=== update-sdlc-status.test.sh ==="
130
+ echo "PASS: $PASS FAIL: $FAIL"
131
+ [ "$FAIL" -eq 0 ]
@@ -41,8 +41,80 @@ The orchestrator MUST invoke `e2e-test-engineer` for end-to-end and visual-regre
41
41
  - Never transcribe `e2e-test-engineer`'s six-phase workflow into this skill's body.
42
42
  - Call via the standard Claude Code Skill mechanism (`Skill(name: "e2e-test-engineer", …)`).
43
43
 
44
+ **Structural enforcement (devaudit#132):** the contract is backed by two gates inside Phase 2 — a literal pre-test-work declaration before any `e2e/**/*.spec.ts` edit (step 3) and a mandatory self-audit before Phase 3 (step 9). Both are scripts the orchestrator follows, not prose it can rationalise around. If you find yourself about to skip either, that's the inertia trap the gates exist to interrupt — STOP, run the gate, and you'll usually find the delegation path is obvious from there.
45
+
44
46
  Unit-test and integration-test work stays with this skill until a counterpart unit-test skill ships. The full sub-skill call graph lives at [`references/call-graph.md`](./references/call-graph.md).
45
47
 
48
+ ## SDLC navigability — LAST/NEXT status sticky (devaudit#131)
49
+
50
+ Long-running SDLC issues accumulate dozens of comments across multiple Claude Code sessions. The operator returning to the thread should be able to answer two questions in under five seconds:
51
+
52
+ 1. **What just happened?** — the most recent stage completion
53
+ 2. **What is the immediate next step?** — the single action the operator (or this skill on resume) should take next
54
+
55
+ Two surfaces, one convention. Both are mandatory:
56
+
57
+ ### 1. Sticky comment on the REQ issue
58
+
59
+ At **every stage transition** AND **every operator-action handoff** (waiting for review, waiting for merge, waiting for prod apply), invoke the helper:
60
+
61
+ ```bash
62
+ bash scripts/update-sdlc-status.sh "$ISSUE_NUM" \
63
+ "<one sentence describing the step just completed>" \
64
+ "<one sentence describing the immediate next step + actor>"
65
+ ```
66
+
67
+ The helper is idempotent — finds the marker-tagged comment and edits it, or creates one if none exists. So calling it on every transition keeps the same comment current; it never spawns duplicates.
68
+
69
+ LAST sentence rules:
70
+
71
+ - One sentence. Name the phase / artefact / outcome.
72
+ - Include load-bearing identifiers — PR numbers, file paths, gate names — so the operator can act without re-scrolling.
73
+ - Past tense.
74
+
75
+ NEXT sentence rules:
76
+
77
+ - One sentence. **Always name the actor** (operator action / `sdlc-implementer` auto-continues / waiting for CI / waiting for review).
78
+ - Include the artefact to act on — issue number, PR number, migration path, command to run.
79
+ - If the next step is operator-only and we're paused: say so explicitly. "Operator action — apply Prisma migration 13 to prod, then merge develop→main PR #458."
80
+
81
+ Examples:
82
+
83
+ ```
84
+ LAST: Phase 1 complete — implementation plan written to compliance/plans/REQ-074/implementation-plan.md (risk class MEDIUM)
85
+ NEXT: Phase 2 — sdlc-implementer auto-continuing
86
+ ```
87
+
88
+ ```
89
+ LAST: Phase 4 — release PR #455 opened against develop, CI running
90
+ NEXT: Operator action — review PR #455 + merge when CI green; sdlc-implementer halts here until you ping resume REQ-074
91
+ ```
92
+
93
+ ```
94
+ LAST: Phase 5 complete — release v1.2.0 marked Released; post-deploy smoke evidence uploaded
95
+ NEXT: Done — close issue + retire feature branch (sdlc-implementer halts)
96
+ ```
97
+
98
+ ### 2. In-chat LAST/NEXT line (Claude Code surface)
99
+
100
+ Lead every substantive turn with the same two-line shape so the operator can `Ctrl-F NEXT:` in the chat transcript to find the current pointer without re-reading:
101
+
102
+ ```
103
+ **LAST:** <one sentence>
104
+ **NEXT:** <one sentence with actor>
105
+ ```
106
+
107
+ Skip it for trivial turns (acknowledging a "merged" / one-line confirmations / chitchat). It's for SDLC work, not every message. The two surfaces (sticky comment + chat line) should always agree — if they diverge, the comment is canonical (it's what the operator scrolling the issue sees).
108
+
109
+ ### When to update
110
+
111
+ - After every Phase transition (Phase 0 → 1, 1 → 2, …, 5 → done)
112
+ - On every operator-action handoff (paused for review, paused for merge, paused for prod apply, paused for migration)
113
+ - On the change-request loop (Phase 5 rejection → re-enter Phase 2)
114
+ - On error halt (gate failure exhausted retries, operator-only decision needed)
115
+
116
+ Do **not** update on every internal step within a phase — that just spams the sticky. The transition + handoff cadence is the right frequency.
117
+
46
118
  ## The workflow
47
119
 
48
120
  A triage step (Phase 0) routes the issue, then up to five phases for tracked work. Phase 0 plus Phases 1–4 run in one Claude Code session; Phase 5 is invoked separately by the user after UAT. The off-ramps from Phase 0 (housekeeping / trivial / doc-only) don't enter Phase 1 — they run the **Lightweight path** (below), which the skill drives to merge.
@@ -146,6 +218,7 @@ Reached from Phase 0 for non-tracked change-types. The skill drives this end-to-
146
218
 
147
219
  Reached only on the **tracked** route from Phase 0 (the issue is already fetched and classified).
148
220
 
221
+ 0. **Initialise SDLC status sticky** on the issue: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 0 complete — classified as tracked SDLC work" "Phase 1 — sdlc-implementer authoring implementation plan"`. From now until the issue closes, the sticky is the always-current pointer to "what's next" — the operator scans it on every return to the issue.
149
222
  1. **Confirm the issue scope.** Re-read the `gh issue view <N>` output from Phase 0 — title, body, all comments — with implementation in mind.
150
223
  2. **Classify risk** per `Test_Policy.md` §Risk-Based Testing. Emit a one-paragraph rationale citing the signals you used (auth surface, financial calc, data egress, RBAC, AI decisioning, etc.).
151
224
  3. **Assign REQ-XXX.** Inspect `compliance/RTM.md` for existing entries; take the next free number. If the issue references an existing REQ, use that instead.
@@ -169,6 +242,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
169
242
  9. **Update `compliance/RTM.md`** with the new entry: REQ-XXX, title, risk class, linked issue, linked test cases (placeholder).
170
243
  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.
171
244
  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.
245
+ 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).
172
246
 
173
247
  ### Phase 2 — Implement and test (SDLC stage 2)
174
248
 
@@ -178,7 +252,13 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
178
252
  - MEDIUM — unit + integration; e2e for any UI-facing change.
179
253
  - HIGH — unit + integration + e2e for every user-visible path + at least one negative/abuse test.
180
254
  - CRITICAL — HIGH plus targeted security tests (authz bypass attempts, input fuzzing where applicable).
181
- 3. **For any e2e or visual-regression test work in this step, invoke `e2e-test-engineer`** do not author e2e tests directly. The orchestrator passes the implementation plan + the diff so far to the e2e-test-engineer skill, which derives scenarios, reconciles with the existing pack, and runs the suite.
255
+ 3. **E2E delegation gate pre-test-work declaration (devaudit#132).** Before creating or editing **any** `e2e/**/*.spec.ts` file in this phase, follow these three steps in order. The literal script exists because the "MUST invoke" prose alone has been bypassed by inertia in past runs; the declaration is the structural defence.
256
+
257
+ a. Output the single literal line, verbatim: `Delegating e2e test work to e2e-test-engineer.`
258
+ b. Immediately invoke `Skill(name: "e2e-test-engineer", args: "<the change summary + plan pointer>")`. The change summary is one sentence; the plan pointer is `compliance/plans/REQ-XXX/implementation-plan.md`.
259
+ c. **Do not author or edit any `e2e/**/*.spec.ts` file in this skill's own tool calls.** The e2e-test-engineer skill owns spec authoring end-to-end — including the "this AC needs no e2e" decision. If you feel the urge to write a spec inline, that's the inertia trap — STOP and re-invoke the skill.
260
+
261
+ When in doubt about whether work qualifies: visual-regression tests, screenshot diffs, browser-driven flows, any file ending in `.spec.ts` under `e2e/`, and any `playwright.config.ts`/`evidence/`/`baselines/` directory all qualify. Unit/integration tests under `tests/unit/`, `tests/integration/`, or stack-equivalent paths stay with this orchestrator.
182
262
  4. **Implement against the plan.** Reference `compliance/plans/REQ-XXX/implementation-plan.md` as you go. Any deviation from the plan must be noted in the plan itself under a `## Plan deviation` section — never silently diverge.
183
263
  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.
184
264
 
@@ -198,6 +278,17 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
198
278
  - **If `$INTEGRATION_BRANCH` ≠ `$RELEASE_BRANCH`** (develop-first): 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.
199
279
  - **If `$INTEGRATION_BRANCH` = `$RELEASE_BRANCH`** (trunk-only): do **not** merge to the protected branch here — leave the work on the feature branch; it becomes the release PR's head in Phase 4.
200
280
 
281
+ 9. **E2E delegation self-audit — mandatory before Phase 3 (devaudit#132).** Run `git diff "$INTEGRATION_BRANCH"...HEAD --name-only` and walk the file list. For **every** entry matching `e2e/**/*.spec.ts`, state out loud one of:
282
+
283
+ - _"Authored via `e2e-test-engineer` skill invocation on turn N."_ — with the turn pointer the operator can verify from the chat transcript.
284
+ - _"Pre-existing file; only mechanical edits (path renames, import fixes, lint-only) applied directly. No scenario / assertion / selector changes."_ — applies only to non-substantive sweeps where the e2e-test-engineer skill would have nothing to contribute.
285
+
286
+ If you cannot place a spec file in either category — STOP. Do not proceed to Phase 3. Revert the direct edits (`git checkout "$INTEGRATION_BRANCH" -- <file>`) and re-do the work via `Skill(name: "e2e-test-engineer", …)`. The audit must be honest: omitting a file or fabricating a turn pointer is worse than the original delegation gap because it pollutes the audit trail with a false attribution.
287
+
288
+ This is the post-hoc check that catches anything step 3 missed. If both gates fire (declaration before the spec edit + audit before Phase 3) and you still see a direct authoring path, that's evidence the gates need to be stronger and worth a follow-up issue.
289
+
290
+ 10. **Update SDLC status sticky** before exiting Phase 2: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 2 complete — feat branch landed on $INTEGRATION_BRANCH; all gates green" "Phase 3 — sdlc-implementer auto-continuing (evidence compile)"`.
291
+
201
292
  ### Phase 3 — Compile evidence (SDLC stage 3)
202
293
 
203
294
  1. **Invoke `requirements-aligner` to drop the per-REQ SRS-alignment artefact.** The skill's Phase 2 produces `compliance/evidence/REQ-XXX/srs-alignment.md` — the per-REQ trace from each AC to its SRS item, with an operator sign-off block. The artefact uploads with `evidence_type=srs_alignment` (visible in Documents tab + audit-pack export; v1 orphan-by-design per META-COMPLY framework-registry-auditor). Call via the standard Skill mechanism; don't inline the alignment logic.
@@ -233,6 +324,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
233
324
  Evidence types: `screenshot`, `e2e_result`, `test_report`, `audit_log`, `compliance_document`, `manual_upload`, `srs_alignment` (from step 1), `architecture_decision` (from step 2), `risk_assessment` (from step 3).
234
325
  7. **Verify uploads landed.** `gh api` or `curl` against `https://devaudit.metasession.co/projects/<slug>/requirements/REQ-XXX/evidence` should show every artefact.
235
326
  8. **Update `compliance/RTM.md`** with portal links for each evidence row.
327
+ 9. **Update SDLC status sticky** before exiting Phase 3: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 3 complete — evidence uploaded; SRS-alignment + ADR + risk-assessment artefacts attached" "Phase 4 — sdlc-implementer auto-continuing (open release PR)"`.
236
328
 
237
329
  ### Phase 4 — Submit for UAT review (SDLC stage 4)
238
330
 
@@ -257,6 +349,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
257
349
  3. **Apply labels** — `awaiting-uat-review`, `risk:<class>`.
258
350
  4. **Comment on the issue**: "Implementation complete. PR #M opened. Evidence on portal: <link>. UAT review requested. Resume with `resume REQ-XXX` once UAT approval is granted on the portal."
259
351
  5. **Hard stop.** Phase 4 ends here. Do not proceed to merge; the human's next action is reviewing on the portal.
352
+ 6. **Update SDLC status sticky** before halting: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 4 — release PR #<N> opened against $RELEASE_BRANCH; CI running" "Operator action — review PR #<N> + approve UAT release on the portal; sdlc-implementer halts until you ping resume REQ-XXX"`. This is a critical handoff — the sticky must reflect that the agent has stopped + the operator is on the hook.
260
353
 
261
354
  **When an external gate hangs or fails for unrelated reasons.** A required gate may fail for reasons outside the change's scope — flaky infra, an unrelated regression test that hangs at hour-plus runtime with no log activity, a known-failing suite. When this happens:
262
355
 
@@ -278,8 +371,9 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
278
371
  - Verify production smoke evidence uploaded (`--environment production`) at `https://devaudit.metasession.co/projects/<slug>/releases/<version>`.
279
372
  - Mark release as `Released` via portal API: `PATCH /releases/<version>` with `{"status": "released"}`.
280
373
  - Comment on the issue: "Released. Production smoke evidence: <link>."
374
+ - **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)"`.
281
375
  - Close the issue.
282
- - If production smoke fails: do NOT mark as Released. File an `[INCIDENT]` defect issue, page the on-call per the project's incident playbook, follow the rollback plan from the implementation plan.
376
+ - If production smoke fails: do NOT mark as Released. File an `[INCIDENT]` defect issue, page the on-call per the project's incident playbook, follow the rollback plan from the implementation plan. **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"`.
283
377
 
284
378
  - **Changes requested** → run change-request loop:
285
379
  - Fetch change-request comments from the PR (`gh pr view <M> --comments`) and from the portal release page.
@@ -289,6 +383,7 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
289
383
  - Push to the same branch (no force-push). The PR auto-updates.
290
384
  - Re-request UAT review on the portal: `POST /api/projects/<slug>/releases/<version>/approval-requests`.
291
385
  - Comment on the issue: "Change requests addressed in commits <SHAs>. UAT re-review requested."
386
+ - **Update SDLC status sticky** for the re-review handoff: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Change-request iteration N applied; PR pushed; re-review requested" "Operator action — re-review on portal; sdlc-implementer halts until you ping resume REQ-XXX"`.
292
387
  - Hard stop again. The portal's release-approval state has reset; UAT must explicitly re-approve.
293
388
 
294
389
  - **Still pending UAT (no approval, no change-request)** → report "UAT review still pending on the portal at <link>" and stop. Do not act.
@@ -290,8 +290,19 @@ jobs:
290
290
  # (operator's choice — both layouts are common).
291
291
  upload_governance compliance/periodic-review.md periodic_review
292
292
  upload_governance compliance/governance/periodic-review.md periodic_review
293
- upload_governance compliance/incident-report.md incident_report
294
- upload_governance compliance/governance/incident-report.md incident_report
293
+ # Incident reports: glob `incident-report*.md` so per-incident
294
+ # files (e.g. `incident-report-2026-001.md`, written by
295
+ # incident-export.yml from labelled GitHub issues) all
296
+ # upload as real evidence. The unedited starter
297
+ # `incident-report.md` matches the glob too but is skipped
298
+ # by upload-evidence.sh's central stub guard — so the stub
299
+ # can never flip ISO29119.3.5.4 to COVERED on its own
300
+ # (devaudit#133). `*.md` does not match `*.md.template`.
301
+ shopt -s nullglob
302
+ for f in compliance/incident-report*.md compliance/governance/incident-report*.md; do
303
+ upload_governance "$f" incident_report
304
+ done
305
+ shopt -u nullglob
295
306
 
296
307
  # ── Audit-log export (DevAudit-Installer#98 WS2) ──────────────
297
308
  # Snapshot the portal's audit log for the rolling 90-day window