@metasession.co/devaudit-cli 0.3.11 → 0.3.13

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 (68) hide show
  1. package/dist/index.js +94 -6
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/upload-evidence.sh +55 -1
  5. package/sdlc/ai-rules/SDLC_RULES.md +27 -7
  6. package/sdlc/files/_common/0-project-setup.md +13 -7
  7. package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +5 -5
  8. package/sdlc/files/_common/README_TEMPLATE.md +8 -8
  9. package/sdlc/files/_common/Test_Architecture.md +15 -15
  10. package/sdlc/files/_common/Test_Plan_TEMPLATE.md +13 -13
  11. package/sdlc/files/_common/Test_Policy.md +2 -2
  12. package/sdlc/files/_common/Test_Strategy.md +10 -10
  13. package/sdlc/files/_common/governance/ai-disclosure.md.template +3 -3
  14. package/sdlc/files/_common/governance/dpia.md.template +3 -3
  15. package/sdlc/files/_common/governance/incident-report.md.template +3 -3
  16. package/sdlc/files/_common/governance/nil-incident-report.md.template +1 -1
  17. package/sdlc/files/_common/governance/periodic-review.md.template +3 -3
  18. package/sdlc/files/_common/governance/risk-register.md.template +1 -1
  19. package/sdlc/files/_common/governance/ropa.md.template +2 -2
  20. package/sdlc/files/_common/joining-an-existing-project.md +4 -4
  21. package/sdlc/files/_common/scripts/check-host-deployment.sh +107 -0
  22. package/sdlc/files/_common/scripts/check-host-deployment.test.sh +119 -0
  23. package/sdlc/files/_common/scripts/check-release-pr-scope.sh +95 -0
  24. package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +137 -0
  25. package/sdlc/files/_common/scripts/close-out-release.sh +175 -54
  26. package/sdlc/files/_common/scripts/close-out-release.test.sh +90 -0
  27. package/sdlc/files/_common/scripts/extract-release-metadata.sh +65 -0
  28. package/sdlc/files/_common/scripts/extract-release-metadata.test.sh +38 -0
  29. package/sdlc/files/_common/scripts/generate-bundled-changes.sh +307 -58
  30. package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +128 -78
  31. package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.sh +18 -9
  32. package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.test.sh +66 -0
  33. package/sdlc/files/_common/scripts/generate-security-summary.sh +14 -5
  34. package/sdlc/files/_common/scripts/generate-security-summary.test.sh +57 -0
  35. package/sdlc/files/_common/scripts/prepare-release-pr.sh +44 -4
  36. package/sdlc/files/_common/scripts/render-test-cycles.sh +227 -0
  37. package/sdlc/files/_common/scripts/render-test-cycles.test.sh +107 -0
  38. package/sdlc/files/_common/scripts/report-test-cycle.sh +344 -0
  39. package/sdlc/files/_common/scripts/submit-bundle-manifest.sh +100 -0
  40. package/sdlc/files/_common/scripts/submit-bundle-manifest.test.sh +138 -0
  41. package/sdlc/files/_common/scripts/upload-evidence.sh +55 -1
  42. package/sdlc/files/_common/scripts/upload-evidence.test.sh +119 -0
  43. package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +169 -2
  44. package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +169 -0
  45. package/sdlc/files/_common/scripts/validate-test-summary.test.sh +4 -4
  46. package/sdlc/files/_common/skills/adr-author/SKILL.md +2 -2
  47. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +2 -2
  48. package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +5 -5
  49. package/sdlc/files/_common/skills/governance-doc-author/references/incident-classification.md +1 -1
  50. package/sdlc/files/_common/skills/requirements-aligner/SKILL.md +4 -4
  51. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +57 -3
  52. package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +1 -1
  53. package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +7 -14
  54. package/sdlc/files/ci/check-release-approval.yml.template +18 -28
  55. package/sdlc/files/ci/ci.yml.template +95 -35
  56. package/sdlc/files/ci/close-out-release.yml.template +7 -4
  57. package/sdlc/files/ci/compliance-evidence.yml.template +234 -49
  58. package/sdlc/files/ci/feature-e2e.yml.template +84 -1
  59. package/sdlc/files/ci/post-deploy-prod.yml.template +59 -4
  60. package/sdlc/files/ci/python/ci.yml.template +60 -12
  61. package/sdlc/files/ci/quality-gates-provenance.yml.template +123 -28
  62. package/sdlc/package.json +1 -1
  63. package/sdlc/src/bin/devaudit-sdlc.js +44 -2
  64. package/sdlc/src/blueprints/1-plan-requirement.raw.md +6 -1
  65. package/sdlc/src/blueprints/2-implement-and-test.raw.md +1 -1
  66. package/sdlc/src/blueprints/3-compile-evidence.raw.md +102 -20
  67. package/sdlc/src/blueprints/4-submit-for-review.raw.md +3 -3
  68. package/sdlc/src/blueprints/5-deploy-main.raw.md +2 -2
@@ -6,8 +6,9 @@
6
6
  # Single consolidated job — on a self-hosted runner, parallel jobs run
7
7
  # sequentially anyway. One checkout + one cached npm ci = fast.
8
8
  #
9
- # PRs to develop run Quality Gates before integration. PRs to main are verified
10
- # by quality-gates-provenance.yml (same-SHA provenance check, not heavy CI rerun).
9
+ # PRs to {{INTEGRATION_BRANCH}} run Quality Gates before integration. PRs to
10
+ # {{RELEASE_BRANCH}} are handled by quality-gates-provenance.yml: provenance-only
11
+ # for release PRs, full CI rerouted for hotfix/* PRs.
11
12
  # Compliance validation runs separately on PRs (compliance-validation.yml).
12
13
 
13
14
  name: CI Pipeline
@@ -15,9 +16,9 @@ name: CI Pipeline
15
16
  on:
16
17
  workflow_dispatch:
17
18
  pull_request:
18
- branches: [develop]
19
+ branches: [{{INTEGRATION_BRANCH}}]
19
20
  push:
20
- branches: [develop]
21
+ branches: [{{INTEGRATION_BRANCH}}]
21
22
  paths-ignore:
22
23
  {{PATHS_IGNORE}} - 'sdlc-config.json'
23
24
 
@@ -31,6 +32,7 @@ jobs:
31
32
  # ──────────────────────────────────────────────
32
33
  quality-gates:
33
34
  name: Quality Gates
35
+ if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'chore/close-out-') }}
34
36
  runs-on: {{RUNNER}}
35
37
 
36
38
  services:
@@ -238,7 +240,7 @@ jobs:
238
240
  # PRs to develop should report Quality Gates without mutating DevAudit
239
241
  # releases or evidence. Release registration remains a develop-push /
240
242
  # manual-dispatch side effect.
241
- if: ${{ github.event_name != 'pull_request' }}
243
+ if: ${{ github.event_name != 'pull_request' && github.ref_name == '{{INTEGRATION_BRANCH}}' }}
242
244
  outputs:
243
245
  version: ${{ steps.version.outputs.version }}
244
246
  env:
@@ -295,18 +297,16 @@ jobs:
295
297
  echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
296
298
  echo "Release version: ${VERSION}"
297
299
 
298
- - name: Check release exists (no creation — deferred to upload-evidence)
300
+ - name: Ensure release exists for gate-cycle lineage
299
301
  if: env.DEVAUDIT_BASE_URL != '' && steps.version.outputs.version != 'skip'
300
302
  run: |
301
- # Release creation is deferred to the upload-evidence job (which
302
- # runs after gates pass) so the portal never shows a release from
303
- # a push where gates weren't verified (devaudit-installer#226).
304
- # This step only checks whether the release already exists (e.g.
305
- # created earlier by sdlc-implementer Phase 1 step 3).
303
+ # First-class cycle APIs need an exact release row before the
304
+ # stage-2 cycle can start. This keeps failed gate runs visible even
305
+ # when later evidence generation fails.
306
306
  chmod +x scripts/upload-evidence.sh 2>/dev/null || true
307
307
  bash scripts/upload-evidence.sh \
308
308
  {{PROJECT_SLUG}} _compliance-docs release_registration README.md \
309
- --release ${{ steps.version.outputs.version }} \
309
+ --release ${{ steps.version.outputs.version }} --create-release-if-missing \
310
310
  --environment uat --category planning \
311
311
  --git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
312
312
 
@@ -338,8 +338,19 @@ jobs:
338
338
  fi
339
339
  echo "Scanning bundled changes since ${SINCE_REF} for ${VERSION}"
340
340
  BUNDLED_FILE="compliance/pending-releases/BUNDLED-CHANGES-${VERSION}.md"
341
+ BUNDLED_MANIFEST="compliance/pending-releases/BUNDLED-CHANGES-${VERSION}.json"
341
342
  mkdir -p compliance/pending-releases
342
- bash scripts/generate-bundled-changes.sh "$SINCE_REF" "$VERSION" > "$BUNDLED_FILE"
343
+ chmod +x scripts/submit-bundle-manifest.sh 2>/dev/null || true
344
+ bash scripts/generate-bundled-changes.sh \
345
+ "$SINCE_REF" "$VERSION" --json-out "$BUNDLED_MANIFEST" > "$BUNDLED_FILE"
346
+ MEMBER_COUNT=$(jq -r '(.members // []) | length' "$BUNDLED_MANIFEST" 2>/dev/null || echo 0)
347
+ WORK_ITEM_COUNT=$(jq -r '(.nonReleaseWorkItems // []) | length' "$BUNDLED_MANIFEST" 2>/dev/null || echo 0)
348
+ if [ "$MEMBER_COUNT" = "0" ] && [ "$WORK_ITEM_COUNT" = "0" ]; then
349
+ echo "No bundled predecessor releases or non-release work detected for ${VERSION}."
350
+ rm -f "$BUNDLED_FILE" "$BUNDLED_MANIFEST"
351
+ exit 0
352
+ fi
353
+ bash scripts/submit-bundle-manifest.sh {{PROJECT_SLUG}} "$VERSION" "$BUNDLED_MANIFEST"
343
354
  # Upload as bundled_changes evidence against the REQ release.
344
355
  bash scripts/upload-evidence.sh \
345
356
  {{PROJECT_SLUG}} _compliance-docs bundled_changes "$BUNDLED_FILE" \
@@ -407,7 +418,7 @@ jobs:
407
418
  # evidence — `status=failed` is itself the audit trail. `!cancelled()`
408
419
  # still guards against partial state on operator-cancel.
409
420
  # DevAudit-Installer#96.
410
- if: ${{ always() && !cancelled() && needs.register-release.result == 'success' }}
421
+ if: ${{ always() && !cancelled() && github.ref_name == '{{INTEGRATION_BRANCH}}' && needs.register-release.result == 'success' }}
411
422
  env:
412
423
  DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
413
424
  DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
@@ -444,17 +455,33 @@ jobs:
444
455
  - name: Generate and upload gate evidence
445
456
  if: env.DEVAUDIT_BASE_URL != ''
446
457
  run: |
447
- chmod +x scripts/upload-evidence.sh 2>/dev/null || true
448
- # --create-release-if-missing is intentionally here (post-gate) rather
449
- # than in register-release. This ensures the portal release record only
450
- # appears after gates have run — even if gates failed, the release
451
- # record is created here so failure evidence attaches to it.
452
- # (devaudit-installer#226)
458
+ chmod +x scripts/upload-evidence.sh scripts/report-test-cycle.sh 2>/dev/null || true
453
459
  FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
454
460
  FLAGS="${FLAGS} --release ${{ needs.register-release.outputs.version }} --create-release-if-missing"
455
461
  FLAGS="${FLAGS} --environment uat"
456
462
  FLAGS="${FLAGS} --sdlc-stage 2"
457
- FLAGS="${FLAGS} --test-cycle ${{ github.run_id }}"
463
+ PRIMARY_LINEAGE_FLAGS=(--test-cycle "${{ github.run_id }}")
464
+ CYCLE_OUT="$(mktemp)"
465
+ bash scripts/report-test-cycle.sh start \
466
+ --project-slug {{PROJECT_SLUG}} \
467
+ --release "${{ needs.register-release.outputs.version }}" \
468
+ --sdlc-stage 2 \
469
+ --environment uat \
470
+ --cycle-kind quality_gate \
471
+ --provider github_actions \
472
+ --external-run-id "${{ github.run_id }}" \
473
+ --external-run-attempt "${{ github.run_attempt }}" \
474
+ --idempotency-key "github:${{ github.repository }}:quality-gates.quality_gate:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.register-release.outputs.version }}" \
475
+ --commit-sha "${{ github.sha }}" \
476
+ --branch "${{ github.ref_name }}" \
477
+ --workflow-name "Quality Gates" \
478
+ --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
479
+ --output-file "$CYCLE_OUT"
480
+ . "$CYCLE_OUT"
481
+ rm -f "$CYCLE_OUT"
482
+ if [ "${cycle_supported:-false}" = "true" ] && [ -n "${cycle_record_id:-}" ]; then
483
+ PRIMARY_LINEAGE_FLAGS+=(--evidence-scope cycle --test-cycle-record-id "${cycle_record_id}")
484
+ fi
458
485
 
459
486
  # Track failures across all uploads so we can fail the job at the
460
487
  # end with the full picture. Previously each upload used
@@ -494,7 +521,7 @@ jobs:
494
521
  STATUS_BUILD=$(gate_status "$(jq -r '.build // "skipped"' ci-evidence/gate-outcomes.json)")
495
522
  upload gate-outcomes.json \
496
523
  {{PROJECT_SLUG}} _compliance-docs gate_outcome ci-evidence/gate-outcomes.json \
497
- --category ci_pipeline ${FLAGS}
524
+ --category ci_pipeline ${FLAGS} "${PRIMARY_LINEAGE_FLAGS[@]}"
498
525
  fi
499
526
 
500
527
  # Re-generate gate evidence as fallback if artifact download failed
@@ -519,7 +546,7 @@ jobs:
519
546
  if [ -f ci-evidence/sast-results.json ]; then
520
547
  upload sast-results.json \
521
548
  {{PROJECT_SLUG}} _compliance-docs sast_report ci-evidence/sast-results.json \
522
- --category security_scan --gate-status "$STATUS_SAST" ${FLAGS}
549
+ --category security_scan --gate-status "$STATUS_SAST" ${FLAGS} "${PRIMARY_LINEAGE_FLAGS[@]}"
523
550
  fi
524
551
 
525
552
  # Upload dependency audit — precise evidence_type=dependency_audit
@@ -527,14 +554,14 @@ jobs:
527
554
  if [ -f ci-evidence/dependency-audit.json ]; then
528
555
  upload dependency-audit.json \
529
556
  {{PROJECT_SLUG}} _compliance-docs dependency_audit ci-evidence/dependency-audit.json \
530
- --category security_scan --gate-status "$STATUS_DEPAUDIT" ${FLAGS}
557
+ --category security_scan --gate-status "$STATUS_DEPAUDIT" ${FLAGS} "${PRIMARY_LINEAGE_FLAGS[@]}"
531
558
  fi
532
559
 
533
560
  # Upload E2E test results (e2e_result category)
534
561
  if [ -f ci-evidence/e2e-results.json ]; then
535
562
  upload e2e-results.json \
536
563
  {{PROJECT_SLUG}} _compliance-docs e2e_result ci-evidence/e2e-results.json \
537
- --category e2e_result ${FLAGS}
564
+ --category e2e_result ${FLAGS} "${PRIMARY_LINEAGE_FLAGS[@]}"
538
565
  fi
539
566
 
540
567
  # Upload Playwright HTML report (e2e_report — run context, NOT
@@ -546,7 +573,7 @@ jobs:
546
573
  if [ -f ci-evidence/playwright-report.zip ]; then
547
574
  upload playwright-report.zip \
548
575
  {{PROJECT_SLUG}} _compliance-docs e2e_report ci-evidence/playwright-report.zip \
549
- --category e2e_report ${FLAGS}
576
+ --category e2e_report ${FLAGS} "${PRIMARY_LINEAGE_FLAGS[@]}"
550
577
  fi
551
578
  fi
552
579
 
@@ -557,7 +584,7 @@ jobs:
557
584
  if [ -f ci-evidence/coverage/coverage-summary.json ]; then
558
585
  upload coverage-summary.json \
559
586
  {{PROJECT_SLUG}} _compliance-docs coverage_report ci-evidence/coverage/coverage-summary.json \
560
- --category coverage_report ${FLAGS}
587
+ --category coverage_report ${FLAGS} "${PRIMARY_LINEAGE_FLAGS[@]}"
561
588
  fi
562
589
 
563
590
  # Upload project-level Test Summary Report as a baseline
@@ -571,7 +598,9 @@ jobs:
571
598
  if [ -f "compliance/test-summary-report.md" ]; then
572
599
  upload test-summary-report.md \
573
600
  {{PROJECT_SLUG}} _compliance-docs compliance_document compliance/test-summary-report.md \
574
- --category planning ${FLAGS}
601
+ --category planning --git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }} \
602
+ --release ${{ needs.register-release.outputs.version }} --create-release-if-missing \
603
+ --environment uat --sdlc-stage 2
575
604
  fi
576
605
 
577
606
  # devaudit-installer#200 Fix 4 — Per-REQ gate evidence fan-out.
@@ -793,9 +822,9 @@ jobs:
793
822
 
794
823
  # devaudit-installer#237 — check for test-execution-summary.md
795
824
  # as a secondary signal. This is a human-authored document and
796
- # weaker than machine-generated evidence, so it only converts
797
- # a hard error to a warning when combined with unit test files
798
- # on disk never alone.
825
+ # weaker than machine-generated evidence, but it is still the
826
+ # designated evidence for test-maintenance REQs that update an
827
+ # existing tagged spec for a different requirement.
799
828
  HAS_SUMMARY=false
800
829
  if [ -f "compliance/evidence/${REQ}/test-execution-summary.md" ]; then
801
830
  HAS_SUMMARY=true
@@ -804,10 +833,16 @@ jobs:
804
833
  if [ "$SPECS_ON_DISK" -gt 0 ]; then
805
834
  # Scenario B: E2E specs exist on disk but outside the smoke tier
806
835
  echo "::warning::Evidence completeness gate: ${REQ} has zero tagged tests in this smoke run and zero per-AC screenshots, but ${SPECS_ON_DISK} spec file(s) on disk reference it (outside the smoke project). Evidence exists but was not executed in this CI tier. Consider running --project=critical or --grep \"${REQ}\" to capture evidence."
807
- elif [ "$UNIT_TESTS_ON_DISK" -gt 0 ] && [ "$HAS_SUMMARY" = "true" ]; then
808
- # Scenario C: unit test files exist with @requirement annotations
809
- # and a test-execution-summary.md documents coverage
810
- echo "::warning::Evidence completeness gate: ${REQ} has zero E2E tagged tests and zero screenshots, but ${UNIT_TESTS_ON_DISK} unit test file(s) on disk reference it and a test-execution-summary.md exists. Unit test evidence found but execution was not verified in this run. If this is an API-only or backend-only change, this is expected."
836
+ elif [ "$HAS_SUMMARY" = "true" ]; then
837
+ # Scenario C: a test-execution-summary.md documents coverage.
838
+ # This covers both API/backend-only REQs with unit tests on disk
839
+ # and test-maintenance REQs whose evidence is an existing spec
840
+ # tagged to an earlier feature requirement.
841
+ if [ "$UNIT_TESTS_ON_DISK" -gt 0 ]; then
842
+ echo "::warning::Evidence completeness gate: ${REQ} has zero E2E tagged tests and zero screenshots, but ${UNIT_TESTS_ON_DISK} unit test file(s) on disk reference it and a test-execution-summary.md exists. Unit test evidence found but execution was not verified in this run. If this is an API-only or backend-only change, this is expected."
843
+ else
844
+ echo "::warning::Evidence completeness gate: ${REQ} has zero tagged tests and zero screenshots, but a test-execution-summary.md exists documenting coverage. Accepted as evidence for test-maintenance or execution-only REQs."
845
+ fi
811
846
  else
812
847
  # Scenario A: genuinely no evidence — no E2E specs, no unit tests,
813
848
  # no test-execution-summary. Hard error preserved (#237 Constraint 3).
@@ -844,5 +879,30 @@ jobs:
844
879
  exit 1
845
880
  fi
846
881
 
882
+ - name: Complete primary quality-gate cycle
883
+ if: always() && env.DEVAUDIT_BASE_URL != ''
884
+ run: |
885
+ case "${{ job.status }}" in
886
+ success) OUTCOME=passed ;;
887
+ cancelled) OUTCOME=cancelled ;;
888
+ *) OUTCOME=failed ;;
889
+ esac
890
+ chmod +x scripts/report-test-cycle.sh 2>/dev/null || true
891
+ bash scripts/report-test-cycle.sh complete \
892
+ --project-slug {{PROJECT_SLUG}} \
893
+ --release "${{ needs.register-release.outputs.version }}" \
894
+ --sdlc-stage 2 \
895
+ --environment uat \
896
+ --cycle-kind quality_gate \
897
+ --provider github_actions \
898
+ --external-run-id "${{ github.run_id }}" \
899
+ --external-run-attempt "${{ github.run_attempt }}" \
900
+ --idempotency-key "github:${{ github.repository }}:quality-gates.quality_gate:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.register-release.outputs.version }}" \
901
+ --commit-sha "${{ github.sha }}" \
902
+ --branch "${{ github.ref_name }}" \
903
+ --workflow-name "Quality Gates" \
904
+ --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
905
+ --outcome "$OUTCOME"
906
+
847
907
  - name: Summary
848
908
  run: echo "Evidence uploaded for ${{ needs.register-release.outputs.version }} (UAT)"
@@ -11,10 +11,11 @@
11
11
  # - workflow_dispatch — manual catch-up for an already-released requirement.
12
12
  #
13
13
  # It merges origin/main into a branch from develop (devaudit#281), runs
14
- # scripts/close-out-release.sh for the requirement (flip ticket + RTM, move
15
- # ticket to approved-releases/), then opens one PR to develop containing both
16
- # the main→develop sync and the compliance-tree close-out. The script is
17
- # idempotent, so a no-op produces no PR.
14
+ # scripts/close-out-release.sh for the requirement (flip the governing ticket +
15
+ # RTM, move the governing ticket to approved-releases/, and move any absorbed
16
+ # predecessor tickets to superseded-releases/), then opens one PR to develop
17
+ # containing both the main→develop sync and the compliance-tree close-out. The
18
+ # script is idempotent, so a no-op produces no PR.
18
19
 
19
20
  name: Release Close-out
20
21
 
@@ -133,6 +134,7 @@ jobs:
133
134
  - release ticket moved to approved-releases/
134
135
  - release ticket status → RELEASED
135
136
  - RTM row → RELEASED
137
+ - absorbed predecessor tickets → superseded-releases/ (if the bundle manifest listed any)
136
138
 
137
139
  Ref: ${REQ}"
138
140
  git push -u origin "$BRANCH" --force-with-lease
@@ -144,6 +146,7 @@ jobs:
144
146
  - **main → develop reconciliation** — merges released code back into develop
145
147
  - **release ticket** moved to \`approved-releases/\`, status \`RELEASED\`
146
148
  - **RTM row** flipped to \`RELEASED\`
149
+ - **absorbed predecessor tickets** moved to \`superseded-releases/\` when the release bundle lists them
147
150
 
148
151
  Review + merge to develop."
149
152
  gh pr create --base develop --head "$BRANCH" \