@metasession.co/devaudit-cli 0.1.69 → 0.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/index.js +12 -5
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/upload-evidence.sh +10 -1
  5. package/sdlc/CLAUDE.md +25 -2
  6. package/sdlc/SKILLS.md +1 -1
  7. package/sdlc/ai-rules/INSTRUCTIONS-SDLC.md +21 -5
  8. package/sdlc/files/_common/3-compile-evidence.md +63 -2
  9. package/sdlc/files/_common/4-submit-for-review.md +2 -1
  10. package/sdlc/files/_common/5-deploy-main.md +1 -0
  11. package/sdlc/files/_common/Test_Plan_TEMPLATE.md +1 -1
  12. package/sdlc/files/_common/Test_Strategy.md +1 -1
  13. package/sdlc/files/_common/governance/nil-incident-report.md.template +38 -0
  14. package/sdlc/files/_common/scripts/derive-release-version.sh +7 -0
  15. package/sdlc/files/_common/scripts/generate-bundled-changes.sh +88 -0
  16. package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +183 -0
  17. package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +13 -0
  18. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +12 -2
  19. package/sdlc/files/_common/skills/requirements-aligner/SKILL.md +6 -1
  20. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +138 -25
  21. package/sdlc/files/_common/skills/sdlc-implementer/references/call-graph.md +1 -1
  22. package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +27 -2
  23. package/sdlc/files/ci/ci.yml.template +112 -13
  24. package/sdlc/files/ci/compliance-evidence.yml.template +353 -53
  25. package/sdlc/files/ci/feature-e2e.yml.template +3 -2
  26. package/sdlc/files/ci/incident-export.yml.template +306 -8
  27. package/sdlc/files/ci/label-retention.yml.template +144 -0
  28. package/sdlc/files/ci/post-deploy-prod.yml.template +80 -3
  29. package/sdlc/files/ci/python/ci.yml.template +60 -16
  30. package/sdlc/files/sdlc-config.example.json +1 -1
  31. package/sdlc/.claude/settings.local.json +0 -11
@@ -238,6 +238,7 @@ jobs:
238
238
  # release. Note: upload-evidence.sh keeps the LAST --release seen.
239
239
  FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
240
240
  FLAGS="${FLAGS} --create-release-if-missing --environment uat --sdlc-stage 3"
241
+ FLAGS="${FLAGS} --test-cycle ${{ github.run_id }}"
241
242
  DERIVED_RELEASE="${{ steps.version.outputs.version }}"
242
243
 
243
244
  # Derive change_type for the bare-date (housekeeping) DERIVED_RELEASE:
@@ -249,18 +250,28 @@ jobs:
249
250
  DERIVED_META=()
250
251
  [ -n "$DERIVED_CT" ] && DERIVED_META+=(--change-type "$DERIVED_CT")
251
252
 
252
- # Upload planning docs (RTM / Test Plan / Test Cases) as
253
- # compliance_document they surface under the Documents tab.
254
- for DOC in compliance/RTM.md compliance/test-plan.md compliance/test-cases.md; do
255
- if [ -f "$DOC" ]; then
256
- echo "Uploading: $(basename "$DOC")"
257
- bash scripts/upload-evidence.sh \
258
- {{PROJECT_SLUG}} _compliance-docs compliance_document "$DOC" \
259
- --category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
260
- "${DERIVED_META[@]}" \
261
- || echo "Warning: Failed to upload $(basename "$DOC")"
262
- fi
263
- done
253
+ # Upload project-level planning docs with specific evidence_type
254
+ # so the portal's framework-coverage predicates attribute them
255
+ # correctly (DevAudit-Installer#205). Previously all three uploaded
256
+ # as compliance_document — the catch-all that satisfies no specific
257
+ # clause predicate.
258
+ #
259
+ # - RTM.md → rtm (ISO 29119 §3.3 traceability)
260
+ # - test-plan.md → test_plan (ISO 29119 §3.4 test plan)
261
+ # - test-cases.md → test_cases (ISO 29119 §3.4 test cases)
262
+ upload_project_doc() {
263
+ local DOC="$1" EVTYPE="$2"
264
+ if [ ! -f "$DOC" ]; then return 0; fi
265
+ echo "Uploading: $(basename "$DOC") (${EVTYPE})"
266
+ bash scripts/upload-evidence.sh \
267
+ {{PROJECT_SLUG}} _compliance-docs "$EVTYPE" "$DOC" \
268
+ --category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
269
+ "${DERIVED_META[@]}" \
270
+ || echo "Warning: Failed to upload $(basename "$DOC")"
271
+ }
272
+ upload_project_doc compliance/RTM.md rtm
273
+ upload_project_doc compliance/test-plan.md test_plan
274
+ upload_project_doc compliance/test-cases.md test_cases
264
275
 
265
276
  # Project-level Test Summary Report — a hand-authored baseline
266
277
  # describing the project's testing posture. As of v0.1.32 this is
@@ -280,6 +291,13 @@ jobs:
280
291
  || echo "Warning: Failed to upload test-summary-report.md"
281
292
  fi
282
293
 
294
+ # NOTE: test-summary-report.md stays as compliance_document because
295
+ # it is a project-level evergreen baseline, NOT per-release test
296
+ # evidence. It does not satisfy any specific clause predicate —
297
+ # the per-REQ test-execution-summary.md (test_report) does that.
298
+ # This is the one intentional compliance_document use for project-
299
+ # level docs (DevAudit-Installer#205).
300
+
283
301
  # Tier 3 per-event governance docs (devaudit#370 Phase 3a, narrowed
284
302
  # in v0.1.39). Only periodic-review and incident-report are CI-uploaded
285
303
  # — both are auto-generated by other workflows (periodic-review by the
@@ -313,9 +331,68 @@ jobs:
313
331
  # by upload-evidence.sh's central stub guard — so the stub
314
332
  # can never flip ISO29119.3.5.4 to COVERED on its own
315
333
  # (devaudit#133). `*.md` does not match `*.md.template`.
334
+ #
335
+ # DevAudit-Installer#210 §8a: also glob `nil-incident-report*.md`
336
+ # — per-release "no incidents" attestations generated by
337
+ # sdlc-implementer Phase 3 when no incident-labelled issues
338
+ # were closed. Upload as `incident_report` evidence so the
339
+ # portal flips ISO29119.3.5.4 to COVERED for clean releases.
340
+ #
341
+ # DevAudit-Installer#210 §5: completeness gate — skip upload
342
+ # if the file contains REPLACE markers in §4 (Root cause),
343
+ # §5 (Impact), or §9 (Sign-off). Incomplete incident reports
344
+ # must not flip ISO29119.3.5.4 to COVERED on the portal.
345
+ upload_incident_report() {
346
+ local FILE="$1"
347
+ if [ ! -f "$FILE" ]; then return 0; fi
348
+ # Completeness gate — check for REPLACE markers in structured sections.
349
+ # §4 Root cause, §5 Impact, §9 Sign-off (populated reports).
350
+ # For nil reports, only check the Sign-off section.
351
+ if grep -q '## 4\. Root cause' "$FILE" 2>/dev/null; then
352
+ SECTION=$(sed -n '/^## 4\. Root cause/,/^## [0-9]/p' "$FILE")
353
+ if echo "$SECTION" | grep -q 'REPLACE'; then
354
+ echo "::warning::$(basename "$FILE"): §4 (Root cause) contains REPLACE markers — skipping upload. Complete the section and push to develop."
355
+ return 0
356
+ fi
357
+ fi
358
+ if grep -q '## 5\. Impact' "$FILE" 2>/dev/null; then
359
+ SECTION=$(sed -n '/^## 5\. Impact/,/^## [0-9]/p' "$FILE")
360
+ if echo "$SECTION" | grep -q 'REPLACE'; then
361
+ echo "::warning::$(basename "$FILE"): §5 (Impact) contains REPLACE markers — skipping upload. Complete the section and push to develop."
362
+ return 0
363
+ fi
364
+ fi
365
+ if grep -q '## 9\. Sign-off' "$FILE" 2>/dev/null; then
366
+ SECTION=$(sed -n '/^## 9\. Sign-off/,/^## /p' "$FILE")
367
+ if echo "$SECTION" | grep -q 'REPLACE'; then
368
+ echo "::warning::$(basename "$FILE"): §9 (Sign-off) contains REPLACE markers — skipping upload. Complete the sign-off and push to develop."
369
+ return 0
370
+ fi
371
+ fi
372
+ # Nil reports have a simpler sign-off section — check it too.
373
+ if grep -q '## Sign-off' "$FILE" 2>/dev/null && ! grep -q '## 9\. Sign-off' "$FILE" 2>/dev/null; then
374
+ SECTION=$(sed -n '/^## Sign-off/,/^## /p' "$FILE")
375
+ if echo "$SECTION" | grep -q 'REPLACE'; then
376
+ echo "::warning::$(basename "$FILE"): Sign-off section contains REPLACE markers — skipping upload. Complete the sign-off and push to develop."
377
+ return 0
378
+ fi
379
+ fi
380
+ echo "Uploading governance: $(basename "$FILE") (type=incident_report)"
381
+ bash scripts/upload-evidence.sh \
382
+ {{PROJECT_SLUG}} _compliance-docs incident_report "$FILE" \
383
+ --category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
384
+ "${DERIVED_META[@]}" \
385
+ || echo "Warning: Failed to upload $(basename "$FILE")"
386
+ }
316
387
  shopt -s nullglob
317
388
  for f in compliance/incident-report*.md compliance/governance/incident-report*.md; do
318
- upload_governance "$f" incident_report
389
+ upload_incident_report "$f"
390
+ done
391
+ # DevAudit-Installer#210 §8a: nil incident reports (per-release
392
+ # "no incidents" attestations). Glob alongside populated reports
393
+ # and upload as incident_report evidence.
394
+ for f in compliance/nil-incident-report*.md compliance/governance/nil-incident-report*.md; do
395
+ upload_incident_report "$f"
319
396
  done
320
397
  shopt -u nullglob
321
398
 
@@ -339,7 +416,7 @@ jobs:
339
416
  echo "Uploading: audit-log.json (audit_log — 90-day window)"
340
417
  bash scripts/upload-evidence.sh \
341
418
  {{PROJECT_SLUG}} _compliance-docs audit_log "$AUDIT_LOG_FILE" \
342
- --category compliance_document ${FLAGS} --release "${DERIVED_RELEASE}" \
419
+ --category audit_log ${FLAGS} --release "${DERIVED_RELEASE}" \
343
420
  "${DERIVED_META[@]}" \
344
421
  || echo "Warning: Failed to upload audit-log.json"
345
422
  else
@@ -393,7 +470,7 @@ jobs:
393
470
  esac
394
471
  echo "Uploading: $(basename "$TICKET") -> release ${TICKET_RELEASE}"
395
472
  eval "bash scripts/upload-evidence.sh \
396
- {{PROJECT_SLUG}} \"${TICKET_OWNER}\" compliance_document \"$TICKET\" \
473
+ {{PROJECT_SLUG}} \"${TICKET_OWNER}\" release_ticket \"$TICKET\" \
397
474
  --category release_artifact ${FLAGS} --release \"${TICKET_RELEASE}\" \
398
475
  ${TICKET_META_ARGS}" \
399
476
  || echo "Warning: Failed to upload $(basename "$TICKET")"
@@ -435,44 +512,54 @@ jobs:
435
512
  for ARTIFACT in "$REQ_DIR"*.md; do
436
513
  [ -f "$ARTIFACT" ] || continue
437
514
  # Per-REQ basename → (evidence_type, evidence_category) routing.
438
- # The bare default (compliance_document, planning) is the
439
- # historical catch-all; the named cases route specific
440
- # artefacts to their dedicated evidence types so the portal's
441
- # framework-coverage matrix attributes them correctly:
515
+ # Every SDLC-expected artifact has an explicit case with a
516
+ # dedicated evidence_type so the portal's framework-coverage
517
+ # predicates attribute them to the correct clause. The `*)`
518
+ # catch-all was removed (DevAudit-Installer#205) — unrecognized
519
+ # filenames are now skipped with a warning instead of being
520
+ # silently uploaded as compliance_document/planning, which
521
+ # caused wrong artifacts to satisfy wrong framework clauses.
522
+ #
523
+ # Evidence type → framework clause mapping:
524
+ #
525
+ # - test-execution-summary.md → test_report
526
+ # ISO 29119-3 §3.5.6 Test Completion Report.
527
+ # DevAudit-Installer#101.
528
+ #
529
+ # - srs-alignment.md → srs_alignment
530
+ # Output of requirements-aligner skill at Stage 3.
531
+ # DevAudit-Installer#119.
532
+ #
533
+ # - architecture-decision.md → architecture_decision
534
+ # ISO 27001 A.8.25 (Secure SDLC).
535
+ # DevAudit-Installer#120.
536
+ #
537
+ # - risk-assessment.md → risk_assessment
538
+ # SOC 2 CC3.2 (Risk identification).
539
+ # DevAudit-Installer#121.
442
540
  #
443
- # - test-execution-summary.md / test-summary-report.md
444
- # → test_report : ISO 29119-3 §3.5.6 Test Completion
445
- # Report per release cycle. Satisfies the portal's
446
- # Test Reports gate with per-release evidence
447
- # instead of the project-level evergreen TSR (which
448
- # from v0.1.32 downgrades to compliance_document).
449
- # DevAudit-Installer#101.
541
+ # - test-scope.md test_scope
542
+ # ISO 29119 §3.3 (Test scope).
450
543
  #
451
- # - srs-alignment.md
452
- # srs_alignment : output of the requirements-aligner
453
- # skill at Stage 3. Orphan-by-design at v1 per
454
- # META-COMPLY framework-registry-auditor review;
455
- # surfaces in Documents tab + audit-pack export.
456
- # DevAudit-Installer#119.
544
+ # - test-plan.md → test_plan
545
+ # ISO 29119 §3.4 (Test plan, per-REQ).
457
546
  #
458
- # - architecture-decision.md
459
- # architecture_decision : output of the adr-author
460
- # skill at Stage 3. Closes ISO 27001 A.8.25 (Secure
461
- # development life cycle) via the dedicated type
462
- # predicate. DevAudit-Installer#120.
547
+ # - implementation-plan.md → implementation_plan
548
+ # ISO 27001 A.8.25 (Secure SDLC planning).
463
549
  #
464
- # - risk-assessment.md
465
- # risk_assessment : output of the risk-register-keeper
466
- # skill at Stage 3. Closes SOC 2 CC3.2 (Risk
467
- # identification and assessment) via the dedicated
468
- # type predicate. DevAudit-Installer#121.
550
+ # - security-summary.md → security_summary
551
+ # ISO 27001 A.8.28 / SOC2 CC6.1.
469
552
  #
470
- # Until this routing existed the new artefacts uploaded as
471
- # compliance_document, which matches the project-baseline
472
- # docs predicate but NOT the per-REQ Tier 3 clause
473
- # predicates that expect the dedicated types — so the matrix
474
- # reported MISSING / PARTIAL for SOC2.CC3.2 + ISO27001.A.8.25
475
- # despite the files being present. DevAudit-Installer#146.
553
+ # - ai-use-note.md ai_use_note
554
+ # EUAIA Art. 13 (Transparency).
555
+ #
556
+ # - ai-prompts.md ai_prompt_log
557
+ # EUAIA Art. 12 (Record-keeping).
558
+ #
559
+ # - ai-agent-handoff.md → ai_agent_handoff
560
+ # EUAIA Art. 14 (Human oversight).
561
+ #
562
+ # DevAudit-Installer#146, #205.
476
563
  BASENAME=$(basename "$ARTIFACT")
477
564
  case "$BASENAME" in
478
565
  test-execution-summary.md|test-summary-report.md)
@@ -483,8 +570,23 @@ jobs:
483
570
  EVTYPE=architecture_decision; EVCAT=planning ;;
484
571
  risk-assessment.md)
485
572
  EVTYPE=risk_assessment; EVCAT=planning ;;
573
+ test-scope.md)
574
+ EVTYPE=test_scope; EVCAT=planning ;;
575
+ test-plan.md)
576
+ EVTYPE=test_plan; EVCAT=planning ;;
577
+ implementation-plan.md)
578
+ EVTYPE=implementation_plan; EVCAT=planning ;;
579
+ security-summary.md)
580
+ EVTYPE=security_summary; EVCAT=security ;;
581
+ ai-use-note.md)
582
+ EVTYPE=ai_use_note; EVCAT=ai_governance ;;
583
+ ai-prompts.md)
584
+ EVTYPE=ai_prompt_log; EVCAT=ai_governance ;;
585
+ ai-agent-handoff.md)
586
+ EVTYPE=ai_agent_handoff; EVCAT=ai_governance ;;
486
587
  *)
487
- EVTYPE=compliance_document; EVCAT=planning ;;
588
+ echo "::warning::Unrecognized artifact ${REQ_ID}/${BASENAME} — skipping upload. Add explicit routing in compliance-evidence.yml if this is a new evidence type. (DevAudit-Installer#205)"
589
+ continue ;;
488
590
  esac
489
591
  echo "Uploading: ${REQ_ID}/${BASENAME} (${EVTYPE})"
490
592
  eval "bash scripts/upload-evidence.sh \
@@ -605,6 +707,7 @@ jobs:
605
707
  --ci-run-id ${{ github.event.workflow_run.id }} \
606
708
  --branch ${PRIOR_BRANCH}"
607
709
  [ -n "$STAGE" ] && FLAGS="${FLAGS} --sdlc-stage ${STAGE}"
710
+ FLAGS="${FLAGS} --test-cycle ${{ github.event.workflow_run.id }}"
608
711
 
609
712
  # In-scope REQs from pending release tickets at the triggering
610
713
  # SHA. Fall back to `_compliance-docs` if no tickets present so
@@ -636,7 +739,7 @@ jobs:
636
739
  if bash scripts/upload-evidence.sh \
637
740
  {{PROJECT_SLUG}} "$REQ" e2e_result \
638
741
  e2e-artifacts/e2e-regression-results.json \
639
- --category test_report ${FLAGS} --release "${DERIVED_RELEASE}" \
742
+ --category e2e_result ${FLAGS} --release "${DERIVED_RELEASE}" \
640
743
  --meta-key "tier=${TIER}"
641
744
  then
642
745
  :
@@ -645,10 +748,13 @@ jobs:
645
748
  UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
646
749
  fi
647
750
  fi
648
- # 2. Playwright HTML report indextest_report. Operator-
751
+ # 2. Playwright HTML report zipe2e_report. Operator-
649
752
  # facing artefact (the formatted run summary). Skipped if
650
753
  # the report directory is missing (e.g. an upstream
651
754
  # infrastructure failure aborted before reporting).
755
+ # Uses evidence_type=e2e_report (NOT test_report) so the
756
+ # portal can distinguish run-context bundles from per-REQ
757
+ # test completion reports (DevAudit-Installer#207).
652
758
  if [ -d e2e-artifacts/playwright-report ]; then
653
759
  (cd e2e-artifacts && zip -qr playwright-report.zip playwright-report/) 2>/dev/null || true
654
760
  fi
@@ -661,9 +767,9 @@ jobs:
661
767
  # Once the portal's presigned R2 URL flow is live (#189
662
768
  # Option C), this can be reverted to a hard fail.
663
769
  if bash scripts/upload-evidence.sh \
664
- {{PROJECT_SLUG}} "$REQ" test_report \
770
+ {{PROJECT_SLUG}} "$REQ" e2e_report \
665
771
  e2e-artifacts/playwright-report.zip \
666
- --category test_report ${FLAGS} --release "${DERIVED_RELEASE}" \
772
+ --category e2e_report ${FLAGS} --release "${DERIVED_RELEASE}" \
667
773
  --meta-key "tier=${TIER}"
668
774
  then
669
775
  :
@@ -686,3 +792,197 @@ jobs:
686
792
  exit 1
687
793
  fi
688
794
  echo "E2E ${TIER}-tier evidence uploaded for ${#REQS[@]} requirement(s)"
795
+
796
+ - name: Triage and file incidents on E2E Regression failure
797
+ if: steps.resolve.outputs.skip != 'true' && github.event.workflow_run.conclusion == 'failure'
798
+ env:
799
+ GH_TOKEN: ${{ secrets.DEVAUDIT_USER_TOKEN || github.token }}
800
+ CI_RUN_ID: ${{ github.event.workflow_run.id }}
801
+ WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}
802
+ GIT_SHA: ${{ github.event.workflow_run.head_sha }}
803
+ run: |
804
+ # DevAudit-Installer#210 §9a — triage E2E regression failures
805
+ # and file incident issues for application defects + unintended
806
+ # visual diffs. Flakes, test bugs, and seed-data gaps are NOT
807
+ # filed with the incident label (they don't produce incident_report
808
+ # evidence). Classification rationale is included in every filed
809
+ # issue body so a human can verify and reclassify.
810
+ #
811
+ # testCycleId (CI run ID) cross-references #209 so the incident
812
+ # report traces back to the specific test cycle that found the defect.
813
+
814
+ if [ ! -f e2e-artifacts/e2e-regression-results.json ]; then
815
+ echo "No e2e-regression-results.json found — cannot triage. Skipping incident filing."
816
+ exit 0
817
+ fi
818
+
819
+ # Ensure the incident + test-bug labels exist (idempotent).
820
+ gh label list --json name --jq '.[].name' | grep -qx incident || \
821
+ gh label create incident --color 'B60205' \
822
+ --description 'Operational, test, or compliance incident; close to auto-archive as portal evidence'
823
+ gh label list --json name --jq '.[].name' | grep -qx test-bug || \
824
+ gh label create test-bug --color 'FBCA04' \
825
+ --description 'Test bug or seed-data gap — not an application defect'
826
+
827
+ FLAKE_COUNT=0
828
+ TEST_BUG_COUNT=0
829
+ SEED_GAP_COUNT=0
830
+ VISUAL_INTENDED_COUNT=0
831
+ VISUAL_UNINTENDED_COUNT=0
832
+ APP_DEFECT_COUNT=0
833
+
834
+ # Parse the Playwright JSON reporter output and classify each failing test.
835
+ # Using jq to extract failing tests.
836
+ FAILING_TESTS=$(jq -r '
837
+ [.suites[]? | .specs[]? | select(.status == "failed" or .status == "timedOut" or .status == "interrupted" or .status == "flaky")]
838
+ | .[]
839
+ | [.title, .status, (.tests[0].results[-1].error.message // "no error message")] | @tsv
840
+ ' e2e-artifacts/e2e-regression-results.json 2>/dev/null || true)
841
+
842
+ if [ -z "$FAILING_TESTS" ]; then
843
+ echo "No failing tests found in JSON report — nothing to triage."
844
+ exit 0
845
+ fi
846
+
847
+ while IFS=$'\t' read -r TEST_NAME TEST_STATUS ERROR_MSG; do
848
+ [ -z "$TEST_NAME" ] && continue
849
+
850
+ # --- Classification ---
851
+ CLASSIFICATION=""
852
+ RATIONALE=""
853
+ FILE_LABEL=""
854
+
855
+ # Check for flake (Playwright status "flaky" = passed on retry).
856
+ if [ "$TEST_STATUS" = "flaky" ]; then
857
+ CLASSIFICATION="flake"
858
+ RATIONALE="Test has status 'flaky' — passed on retry. No incident filed."
859
+ FLAKE_COUNT=$((FLAKE_COUNT + 1))
860
+ echo " [FLAKE] $TEST_NAME — $RATIONALE"
861
+ continue
862
+ fi
863
+
864
+ # Check for test bug patterns.
865
+ if echo "$ERROR_MSG" | grep -qiE 'TimeoutError|locator\.fill.*strict mode|Page\.waitForSelector|TestTimeoutError'; then
866
+ CLASSIFICATION="test-bug"
867
+ RATIONALE="Error message matches selector/timeout patterns (TimeoutError, strict mode violation, waitForSelector). Likely a test bug, not an application defect."
868
+ FILE_LABEL="test-bug"
869
+ TEST_BUG_COUNT=$((TEST_BUG_COUNT + 1))
870
+ # Check for seed-data gap patterns.
871
+ elif echo "$ERROR_MSG" | grep -qiE 'toHaveCount\(0\)|toBeVisible.*empty|toHaveLength\(0\)'; then
872
+ CLASSIFICATION="seed-data-gap"
873
+ RATIONALE="Error message matches assertion on empty state. Likely a seed-data gap — the assertion is correct but the fixture doesn't satisfy it."
874
+ FILE_LABEL="test-bug"
875
+ SEED_GAP_COUNT=$((SEED_GAP_COUNT + 1))
876
+ # Check for visual diff (intended vs unintended).
877
+ elif echo "$TEST_NAME" | grep -qiE 'visual|screenshot|snapshot'; then
878
+ # Check if recent commits touch the affected area.
879
+ RECENT_FILES=$(git diff --name-only HEAD~1..HEAD 2>/dev/null || true)
880
+ if echo "$RECENT_FILES" | grep -qiE 'visual|screenshot|snapshot|component|ui'; then
881
+ CLASSIFICATION="visual-diff-intended"
882
+ RATIONALE="Spec is in a visual-regression directory and the diff is in an area recent commits touch. Likely intended — human should update the baseline."
883
+ VISUAL_INTENDED_COUNT=$((VISUAL_INTENDED_COUNT + 1))
884
+ echo " [VISUAL-INTENDED] $TEST_NAME — $RATIONALE"
885
+ continue
886
+ else
887
+ CLASSIFICATION="visual-diff-unintended"
888
+ RATIONALE="Spec is in a visual-regression directory but the diff is in an area NOT touched by recent commits. Regression — something broke visually without a deliberate change."
889
+ FILE_LABEL="incident"
890
+ VISUAL_UNINTENDED_COUNT=$((VISUAL_UNINTENDED_COUNT + 1))
891
+ fi
892
+ else
893
+ # Default: application defect (conservative).
894
+ CLASSIFICATION="application-defect"
895
+ RATIONALE="Error is an assertion failure, application error, or unrecognised pattern. Defaulting to application defect (conservative)."
896
+ FILE_LABEL="incident"
897
+ APP_DEFECT_COUNT=$((APP_DEFECT_COUNT + 1))
898
+ fi
899
+
900
+ # --- File issue ---
901
+ if [ "$FILE_LABEL" = "incident" ]; then
902
+ # Dedup: check for existing open incident issue for the same test name.
903
+ EXISTING=$(gh issue list --label incident --state open --search "$TEST_NAME" --json number --jq '.[0].number' || true)
904
+ if [ -n "$EXISTING" ]; then
905
+ echo " [DEDUP] $TEST_NAME — existing incident #$EXISTING already open. Posting comment."
906
+ gh issue comment "$EXISTING" --body "Additional failure detected in workflow run ${WORKFLOW_URL} (SHA ${GIT_SHA}). Classification: ${CLASSIFICATION}. Rationale: ${RATIONALE}"
907
+ continue
908
+ fi
909
+
910
+ echo " [INCIDENT] $TEST_NAME — filing with incident label"
911
+
912
+ ISSUE_BODY="## E2E Regression Failure — ${CLASSIFICATION}
913
+
914
+ **Test name:** ${TEST_NAME}
915
+ **Error message:**
916
+ \`\`\`
917
+ ${ERROR_MSG}
918
+ \`\`\`
919
+
920
+ **Classification:** ${CLASSIFICATION}
921
+ **Classification rationale:** ${RATIONALE}
922
+
923
+ **testCycleId:** ${CI_RUN_ID}
924
+ **Workflow run:** ${WORKFLOW_URL}
925
+ **Git SHA:** ${GIT_SHA}
926
+
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.
928
+
929
+ ### Framework attribution
930
+
931
+ This defect, once closed with the \`incident\` label, will be auto-exported as \`incident_report\` evidence and attribute to:
932
+
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
+
939
+ Once closed, the \`incident-export.yml\` workflow exports this issue's body to \`compliance/governance/incident-report-<N>.md\`.
940
+ "
941
+
942
+ gh issue create \
943
+ --title "[REGRESSION] ${TEST_NAME}" \
944
+ --label "incident,${CLASSIFICATION}" \
945
+ --body "$ISSUE_BODY"
946
+
947
+ elif [ "$FILE_LABEL" = "test-bug" ]; then
948
+ echo " [TEST-BUG] $TEST_NAME — filing with test-bug label (no incident label)"
949
+
950
+ ISSUE_BODY="## E2E Regression Failure — ${CLASSIFICATION}
951
+
952
+ **Test name:** ${TEST_NAME}
953
+ **Error message:**
954
+ \`\`\`
955
+ ${ERROR_MSG}
956
+ \`\`\`
957
+
958
+ **Classification:** ${CLASSIFICATION}
959
+ **Classification rationale:** ${RATIONALE}
960
+
961
+ **testCycleId:** ${CI_RUN_ID}
962
+ **Workflow run:** ${WORKFLOW_URL}
963
+ **Git SHA:** ${GIT_SHA}
964
+
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
+ "
967
+
968
+ gh issue create \
969
+ --title "[TEST-BUG] ${TEST_NAME}" \
970
+ --label "test-bug" \
971
+ --body "$ISSUE_BODY"
972
+ fi
973
+
974
+ done <<< "$FAILING_TESTS"
975
+
976
+ # --- 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}"
984
+
985
+ echo "$SUMMARY"
986
+ # Post summary as a workflow run comment (using gh api).
987
+ gh api "${{ github.repository }}/actions/runs/${CI_RUN_ID}/comments" \
988
+ --method POST -f body="$SUMMARY" 2>/dev/null || true
@@ -128,11 +128,12 @@ jobs:
128
128
  fi
129
129
  if [ -f playwright-report.zip ]; then
130
130
  bash scripts/upload-evidence.sh \
131
- {{PROJECT_SLUG}} "$REQ_ID" test_report playwright-report.zip \
132
- --category test_report --release "$REQ_ID" --create-release-if-missing \
131
+ {{PROJECT_SLUG}} "$REQ_ID" feature_e2e_report playwright-report.zip \
132
+ --category e2e_report --release "$REQ_ID" --create-release-if-missing \
133
133
  --environment uat --sdlc-stage 2 \
134
134
  --git-sha "${{ github.event.pull_request.head.sha }}" \
135
135
  --ci-run-id "${{ github.run_id }}" --branch "${{ github.head_ref }}" \
136
+ --test-cycle "${{ github.run_id }}" \
136
137
  --meta-key "origin=feature" \
137
138
  || echo "::warning::feature E2E report upload failed"
138
139
  fi