@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.
@@ -288,14 +288,47 @@ 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 }} \
303
+ --environment uat --category planning \
304
+ --git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
305
+
306
+ - name: Generate and upload bundled changes (REQ releases only)
307
+ if: env.DEVAUDIT_BASE_URL != ''
308
+ run: |
309
+ VERSION="${{ steps.version.outputs.version }}"
310
+ # Only generate bundled changes for REQ-tagged releases —
311
+ # housekeeping (bare-date) releases don't bundle other housekeeping.
312
+ if [[ "$VERSION" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
313
+ echo "Version ${VERSION} is housekeeping — skipping bundled changes."
314
+ exit 0
315
+ fi
316
+ chmod +x scripts/generate-bundled-changes.sh 2>/dev/null || true
317
+ chmod +x scripts/upload-evidence.sh 2>/dev/null || true
318
+ # Find the previous release tag to scan from. Fall back to last 50
319
+ # commits if no prior tag exists.
320
+ SINCE_REF=$(git tag --sort=-creatordate | head -1 || true)
321
+ if [ -z "$SINCE_REF" ]; then
322
+ SINCE_REF="HEAD~50"
323
+ fi
324
+ echo "Scanning bundled changes since ${SINCE_REF} for ${VERSION}"
325
+ BUNDLED_FILE="compliance/pending-releases/BUNDLED-CHANGES-${VERSION}.md"
326
+ mkdir -p compliance/pending-releases
327
+ bash scripts/generate-bundled-changes.sh "$SINCE_REF" "$VERSION" > "$BUNDLED_FILE"
328
+ # Upload as bundled_changes evidence against the REQ release.
329
+ bash scripts/upload-evidence.sh \
330
+ {{PROJECT_SLUG}} _compliance-docs bundled_changes "$BUNDLED_FILE" \
331
+ --release "$VERSION" \
299
332
  --environment uat --category planning \
300
333
  --git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
301
334
 
@@ -409,6 +442,11 @@ jobs:
409
442
  if: env.DEVAUDIT_BASE_URL != ''
410
443
  run: |
411
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)
412
450
  FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
413
451
  FLAGS="${FLAGS} --release ${{ needs.register-release.outputs.version }} --create-release-if-missing"
414
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."