@metasession.co/devaudit-cli 0.1.71 → 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.71",
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.71",
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",
@@ -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,7 +581,15 @@ 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
 
@@ -570,6 +618,59 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
570
618
 
571
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.
572
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
+
573
674
  ## Compliance constraints
574
675
 
575
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).
@@ -288,14 +288,18 @@ jobs:
288
288
  echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
289
289
  echo "Release version: ${VERSION}"
290
290
 
291
- - name: Ensure release exists
291
+ - name: Check release exists (no creation — deferred to upload-evidence)
292
292
  if: env.DEVAUDIT_BASE_URL != ''
293
293
  run: |
294
+ # Release creation is deferred to the upload-evidence job (which
295
+ # runs after gates pass) so the portal never shows a release from
296
+ # a push where gates weren't verified (devaudit-installer#226).
297
+ # This step only checks whether the release already exists (e.g.
298
+ # created earlier by sdlc-implementer Phase 1 step 3).
294
299
  chmod +x scripts/upload-evidence.sh 2>/dev/null || true
295
- # Create the release in DevAudit (no evidence yet — just registration)
296
300
  bash scripts/upload-evidence.sh \
297
301
  {{PROJECT_SLUG}} _compliance-docs release_registration README.md \
298
- --release ${{ steps.version.outputs.version }} --create-release-if-missing \
302
+ --release ${{ steps.version.outputs.version }} \
299
303
  --environment uat --category planning \
300
304
  --git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
301
305
 
@@ -438,6 +442,11 @@ jobs:
438
442
  if: env.DEVAUDIT_BASE_URL != ''
439
443
  run: |
440
444
  chmod +x scripts/upload-evidence.sh 2>/dev/null || true
445
+ # --create-release-if-missing is intentionally here (post-gate) rather
446
+ # than in register-release. This ensures the portal release record only
447
+ # appears after gates have run — even if gates failed, the release
448
+ # record is created here so failure evidence attaches to it.
449
+ # (devaudit-installer#226)
441
450
  FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
442
451
  FLAGS="${FLAGS} --release ${{ needs.register-release.outputs.version }} --create-release-if-missing"
443
452
  FLAGS="${FLAGS} --environment uat"
@@ -909,35 +909,43 @@ jobs:
909
909
 
910
910
  echo " [INCIDENT] $TEST_NAME — filing with incident label"
911
911
 
912
- ISSUE_BODY="## E2E Regression Failure ${CLASSIFICATION}
912
+ # DevAudit-Installer#228 heredoc to keep markdown content
913
+ # inside the YAML literal block scalar (0-indent continuation
914
+ # lines were terminating the block and ** was parsed as YAML
915
+ # alias references).
916
+ ISSUE_BODY=$(cat <<EOF
917
+ ## E2E Regression Failure — ${CLASSIFICATION}
913
918
 
914
- **Test name:** ${TEST_NAME}
915
- **Error message:**
916
- \`\`\`
917
- ${ERROR_MSG}
918
- \`\`\`
919
+ **Test name:** ${TEST_NAME}
920
+ **Error message:**
921
+ \`\`\`
922
+ ${ERROR_MSG}
923
+ \`\`\`
919
924
 
920
- **Classification:** ${CLASSIFICATION}
921
- **Classification rationale:** ${RATIONALE}
925
+ **Classification:** ${CLASSIFICATION}
926
+ **Classification rationale:** ${RATIONALE}
922
927
 
923
- **testCycleId:** ${CI_RUN_ID}
924
- **Workflow run:** ${WORKFLOW_URL}
925
- **Git SHA:** ${GIT_SHA}
928
+ **testCycleId:** ${CI_RUN_ID}
929
+ **Workflow run:** ${WORKFLOW_URL}
930
+ **Git SHA:** ${GIT_SHA}
926
931
 
927
- This regression was detected and classified by the autonomous E2E Regression CI run. A human should confirm the classification, adjust severity if needed, and close once fixed. The \`incident\` label ensures an \`incident_report\` will be generated on close.
932
+ This regression was detected and classified by the autonomous E2E Regression CI run. A human should confirm the classification, adjust severity if needed, and close once fixed. The \`incident\` label ensures an \`incident_report\` will be generated on close.
928
933
 
929
- ### Framework attribution
934
+ ### Framework attribution
930
935
 
931
- This defect, once closed with the \`incident\` label, will be auto-exported as \`incident_report\` evidence and attribute to:
936
+ This defect, once closed with the \`incident\` label, will be auto-exported as \`incident_report\` evidence and attribute to:
932
937
 
933
- - [x] \`ISO29119.3.5.4\` (baseline — every incident_report)
934
- - [x] \`SOC2.CC7.2\` — ops impact: a regression in production-adjacent code is an ops concern
935
- - [ ] \`GDPR.Art-33\` — personal data scope: <REPLACE — yes/no>
936
- - [ ] \`GDPR.Art-34\` — data-subject notification required: <REPLACE — yes/no>
937
- - [ ] \`EUAIA.Art-9 / Art-14 / Art-15\` — AI failure: <REPLACE — yes/no, which article(s)>
938
+ - [x] \`ISO29119.3.5.4\` (baseline — every incident_report)
939
+ - [x] \`SOC2.CC7.2\` — ops impact: a regression in production-adjacent code is an ops concern
940
+ - [ ] \`GDPR.Art-33\` — personal data scope: <REPLACE — yes/no>
941
+ - [ ] \`GDPR.Art-34\` — data-subject notification required: <REPLACE — yes/no>
942
+ - [ ] \`EUAIA.Art-9 / Art-14 / Art-15\` — AI failure: <REPLACE — yes/no, which article(s)>
938
943
 
939
- Once closed, the \`incident-export.yml\` workflow exports this issue's body to \`compliance/governance/incident-report-<N>.md\`.
940
- "
944
+ Once closed, the \`incident-export.yml\` workflow exports this issue's body to \`compliance/governance/incident-report-<N>.md\`.
945
+ EOF
946
+ )
947
+ # Strip the 14-space heredoc indentation so the issue body is clean.
948
+ ISSUE_BODY=$(echo "$ISSUE_BODY" | sed 's/^ //')
941
949
 
942
950
  gh issue create \
943
951
  --title "[REGRESSION] ${TEST_NAME}" \
@@ -947,23 +955,27 @@ Once closed, the \`incident-export.yml\` workflow exports this issue's body to \
947
955
  elif [ "$FILE_LABEL" = "test-bug" ]; then
948
956
  echo " [TEST-BUG] $TEST_NAME — filing with test-bug label (no incident label)"
949
957
 
950
- ISSUE_BODY="## E2E Regression Failure ${CLASSIFICATION}
958
+ # DevAudit-Installer#228 heredoc (see comment above).
959
+ ISSUE_BODY=$(cat <<EOF
960
+ ## E2E Regression Failure — ${CLASSIFICATION}
951
961
 
952
- **Test name:** ${TEST_NAME}
953
- **Error message:**
954
- \`\`\`
955
- ${ERROR_MSG}
956
- \`\`\`
962
+ **Test name:** ${TEST_NAME}
963
+ **Error message:**
964
+ \`\`\`
965
+ ${ERROR_MSG}
966
+ \`\`\`
957
967
 
958
- **Classification:** ${CLASSIFICATION}
959
- **Classification rationale:** ${RATIONALE}
968
+ **Classification:** ${CLASSIFICATION}
969
+ **Classification rationale:** ${RATIONALE}
960
970
 
961
- **testCycleId:** ${CI_RUN_ID}
962
- **Workflow run:** ${WORKFLOW_URL}
963
- **Git SHA:** ${GIT_SHA}
971
+ **testCycleId:** ${CI_RUN_ID}
972
+ **Workflow run:** ${WORKFLOW_URL}
973
+ **Git SHA:** ${GIT_SHA}
964
974
 
965
- This failure appears to be a ${CLASSIFICATION} (not an application defect). A human should confirm and fix the ${CLASSIFICATION}, not the application. Filed with the \`test-bug\` label — this does NOT produce \`incident_report\` evidence.
966
- "
975
+ This failure appears to be a ${CLASSIFICATION} (not an application defect). A human should confirm and fix the ${CLASSIFICATION}, not the application. Filed with the \`test-bug\` label — this does NOT produce \`incident_report\` evidence.
976
+ EOF
977
+ )
978
+ ISSUE_BODY=$(echo "$ISSUE_BODY" | sed 's/^ //')
967
979
 
968
980
  gh issue create \
969
981
  --title "[TEST-BUG] ${TEST_NAME}" \
@@ -974,13 +986,19 @@ This failure appears to be a ${CLASSIFICATION} (not an application defect). A hu
974
986
  done <<< "$FAILING_TESTS"
975
987
 
976
988
  # --- Post summary comment on the workflow run ---
977
- SUMMARY="E2E Regression triage summary:
978
- - Flakes (not filed): ${FLAKE_COUNT}
979
- - Test bugs (filed without incident label): ${TEST_BUG_COUNT}
980
- - Seed-data gaps (filed without incident label): ${SEED_GAP_COUNT}
981
- - Visual diffs intended (not filed): ${VISUAL_INTENDED_COUNT}
982
- - Visual diffs unintended (filed as incident): ${VISUAL_UNINTENDED_COUNT}
983
- - Application defects (filed as incident): ${APP_DEFECT_COUNT}"
989
+ # DevAudit-Installer#228 heredoc to keep continuation lines
990
+ # inside the YAML literal block scalar.
991
+ SUMMARY=$(cat <<EOF
992
+ E2E Regression triage summary:
993
+ - Flakes (not filed): ${FLAKE_COUNT}
994
+ - Test bugs (filed without incident label): ${TEST_BUG_COUNT}
995
+ - Seed-data gaps (filed without incident label): ${SEED_GAP_COUNT}
996
+ - Visual diffs intended (not filed): ${VISUAL_INTENDED_COUNT}
997
+ - Visual diffs unintended (filed as incident): ${VISUAL_UNINTENDED_COUNT}
998
+ - Application defects (filed as incident): ${APP_DEFECT_COUNT}
999
+ EOF
1000
+ )
1001
+ SUMMARY=$(echo "$SUMMARY" | sed 's/^ //')
984
1002
 
985
1003
  echo "$SUMMARY"
986
1004
  # Post summary as a workflow run comment (using gh api).
@@ -198,15 +198,25 @@ jobs:
198
198
  MERGE_SHA=$(echo "$CLOSING_PRS" | jq -r '.mergeCommit.oid // .mergeCommit // "unknown"' 2>/dev/null | head -1)
199
199
  PR_TITLE=$(echo "$CLOSING_PRS" | jq -r '.title' 2>/dev/null | head -1)
200
200
  PR_NUM=$(echo "$CLOSING_PRS" | jq -r '.number' 2>/dev/null | head -1)
201
- CONTAINMENT="- **Containment actions:** auto-derived from PR #${PR_NUM}: ${PR_TITLE}
202
- - **Mitigation deployed:** [PR #${PR_NUM}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${PR_NUM}) (${MERGE_SHA})
203
- - **Recovery actions:** REPLACE — if additional recovery steps were taken
204
- - **Verification that the incident is resolved:** REPLACE — how was the fix verified (re-run E2E, manual check, etc.)"
201
+ # DevAudit-Installer#228 heredoc to keep markdown content
202
+ # inside the YAML literal block scalar.
203
+ CONTAINMENT=$(cat <<EOF
204
+ - **Containment actions:** auto-derived from PR #${PR_NUM}: ${PR_TITLE}
205
+ - **Mitigation deployed:** [PR #${PR_NUM}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${PR_NUM}) (${MERGE_SHA})
206
+ - **Recovery actions:** REPLACE — if additional recovery steps were taken
207
+ - **Verification that the incident is resolved:** REPLACE — how was the fix verified (re-run E2E, manual check, etc.)
208
+ EOF
209
+ )
210
+ CONTAINMENT=$(echo "$CONTAINMENT" | sed 's/^ //')
205
211
  else
206
- CONTAINMENT="- **Containment actions:** REPLACE — no linked PR found
207
- - **Mitigation deployed:** REPLACE
208
- - **Recovery actions:** REPLACE
209
- - **Verification that the incident is resolved:** REPLACE"
212
+ CONTAINMENT=$(cat <<EOF
213
+ - **Containment actions:** REPLACE — no linked PR found
214
+ - **Mitigation deployed:** REPLACE
215
+ - **Recovery actions:** REPLACE
216
+ - **Verification that the incident is resolved:** REPLACE
217
+ EOF
218
+ )
219
+ CONTAINMENT=$(echo "$CONTAINMENT" | sed 's/^ //')
210
220
  fi
211
221
 
212
222
  # §4g — Lessons learned: scan comments for follow-up issue references.
@@ -1,9 +1,21 @@
1
1
  #!/usr/bin/env bash
2
- # Husky pre-push hook — runs TypeScript check as a fast gate before push.
3
- # The full test suite (E2E, SAST, dependency audit) runs in CI.
2
+ # Husky pre-push hook — fast gates + E2E evidence + skill-invocation sentinel + compliance validator.
3
+ #
4
+ # Runs four checks before allowing a push:
5
+ # 1. TypeScript check (fast gate)
6
+ # 2. E2E evidence check — if UI-facing files changed, verify Playwright ran
7
+ # 3. Skill-invocation sentinel — if feat/fix/refactor/perf commits present,
8
+ # verify sdlc-implementer was invoked
9
+ # 4. Compliance artifact validation — if feat/fix/refactor/perf commits present,
10
+ # run validate-compliance-artifacts.sh to catch missing evidence files
11
+ #
12
+ # Bypass with --no-verify (last resort, not a habit).
4
13
  #
5
14
  # Install: cp this file to .husky/pre-push && chmod +x .husky/pre-push
6
15
 
16
+ set -euo pipefail
17
+
18
+ # ── 1. TypeScript check ──────────────────────────────────────────────
7
19
  echo "Pre-push: running TypeScript check..."
8
20
  npx tsc --noEmit
9
21
  if [ $? -ne 0 ]; then
@@ -13,3 +25,107 @@ if [ $? -ne 0 ]; then
13
25
  exit 1
14
26
  fi
15
27
  echo "Pre-push: TypeScript check passed."
28
+
29
+ # ── 2. E2E evidence check (devaudit-installer#226) ───────────────────
30
+ # Only fires if UI-facing files are in the push. Checks for either
31
+ # playwright-report/ directory or .e2e-gate-passed sentinel (written by
32
+ # e2e-test-engineer after a successful run).
33
+ INTEGRATION_BRANCH=$(jq -r '.integration_branch // "develop"' sdlc-config.json 2>/dev/null || echo "develop")
34
+
35
+ # Read stdin to get the pushed refs (husky pre-push protocol)
36
+ while read -r local_ref local_sha remote_ref remote_sha; do
37
+ # Check if pushing to the integration branch
38
+ if [[ "$remote_ref" != *"refs/heads/$INTEGRATION_BRANCH" ]]; then
39
+ continue
40
+ fi
41
+
42
+ # Determine the commit range being pushed
43
+ if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
44
+ RANGE="$local_sha"
45
+ else
46
+ RANGE="${remote_sha}..${local_sha}"
47
+ fi
48
+
49
+ # Check for UI-facing files in the push
50
+ UI_FILES=$(git diff --name-only "$RANGE" -- 'app/**/*.tsx' 'src/**/*.tsx' 'pages/**/*.tsx' 'app/**/*.jsx' 'src/**/*.jsx' 'pages/**/*.jsx' 2>/dev/null || true)
51
+
52
+ if [ -n "$UI_FILES" ]; then
53
+ E2E_PASSED=false
54
+ if [ -f .e2e-gate-passed ]; then
55
+ E2E_PASSED=true
56
+ elif [ -d playwright-report ] && [ "$(find playwright-report -type f -newer .git/HEAD 2>/dev/null | head -1)" ]; then
57
+ E2E_PASSED=true
58
+ fi
59
+
60
+ if [ "$E2E_PASSED" = "false" ]; then
61
+ echo ""
62
+ echo "ERROR: E2E gate was not run before pushing."
63
+ echo " UI-facing files changed in this push:"
64
+ echo "$UI_FILES" | sed 's/^/ /'
65
+ echo ""
66
+ echo " Run 'npx playwright test' (or invoke e2e-test-engineer) before pushing."
67
+ echo " Bypass with --no-verify (last resort, not a habit)."
68
+ exit 1
69
+ fi
70
+ echo "Pre-push: E2E evidence check passed."
71
+ fi
72
+ done
73
+
74
+ # ── 3. Skill-invocation sentinel (devaudit-installer#226) ────────────
75
+ # If feat/fix/refactor/perf commits are in the push, verify the
76
+ # sdlc-implementer skill was invoked (writes .sdlc-implementer-invoked).
77
+ # Housekeeping types (docs/chore/ci/build/test/revert) are exempt.
78
+ TRACKED_TYPES='^(feat|fix|refactor|perf)(\(.+\))?!?:'
79
+ HAS_TRACKED=false
80
+
81
+ while read -r local_ref local_sha remote_ref remote_sha; do
82
+ if [[ "$remote_ref" != *"refs/heads/$INTEGRATION_BRANCH" ]]; then
83
+ continue
84
+ fi
85
+ if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
86
+ RANGE="$local_sha"
87
+ else
88
+ RANGE="${remote_sha}..${local_sha}"
89
+ fi
90
+ COMMITS=$(git log "$RANGE" --format='%s' 2>/dev/null || true)
91
+ if echo "$COMMITS" | grep -qE "$TRACKED_TYPES"; then
92
+ HAS_TRACKED=true
93
+ fi
94
+ done
95
+
96
+ if [ "$HAS_TRACKED" = "true" ]; then
97
+ if [ ! -f .sdlc-implementer-invoked ]; then
98
+ echo ""
99
+ echo "ERROR: sdlc-implementer skill was not invoked."
100
+ echo " This push contains feat/fix/refactor/perf commits which require"
101
+ echo " the SDLC skill flow. Invoke sdlc-implementer to drive the process,"
102
+ echo " or use --no-verify to bypass (not recommended — CI will also check"
103
+ echo " RTM provenance via validate-commits.sh)."
104
+ exit 1
105
+ fi
106
+ echo "Pre-push: skill-invocation sentinel check passed."
107
+ fi
108
+
109
+ # ── 4. Compliance artifact validation (devaudit-installer#226) ──────
110
+ # If feat/fix/refactor/perf commits are in the push, run
111
+ # validate-compliance-artifacts.sh to catch missing test-scope.md,
112
+ # test-plan.md, implementation-plan.md in compliance/evidence/.
113
+ # Reuses HAS_TRACKED from check 3.
114
+ if [ "$HAS_TRACKED" = "true" ]; then
115
+ if [ -f scripts/validate-compliance-artifacts.sh ]; then
116
+ echo "Pre-push: running compliance artifact validation..."
117
+ if ! bash scripts/validate-compliance-artifacts.sh "origin/$INTEGRATION_BRANCH"; then
118
+ echo ""
119
+ echo "ERROR: Compliance artifact validation failed."
120
+ echo " Missing or incomplete artifacts in compliance/evidence/."
121
+ echo " Run 'bash scripts/validate-compliance-artifacts.sh' locally to see details."
122
+ echo " Bypass with --no-verify (last resort, not a habit)."
123
+ exit 1
124
+ fi
125
+ echo "Pre-push: compliance artifact validation passed."
126
+ else
127
+ echo "Pre-push: compliance artifact validation skipped (script not found)."
128
+ fi
129
+ fi
130
+
131
+ echo "Pre-push: all checks passed."