diff --git a/.woodpecker.enhanced.yml b/.woodpecker.enhanced.yml deleted file mode 100644 index 19a3356..0000000 --- a/.woodpecker.enhanced.yml +++ /dev/null @@ -1,339 +0,0 @@ -# Woodpecker CI - Enhanced Quality Gates + Auto-Merge -# Features: -# - Strict quality gates (all checks must pass) -# - Security scanning (SAST, dependency audit, secrets) -# - Test coverage enforcement (≥85%) -# - Automated PR merging when all checks pass - -when: - - event: [push, pull_request, manual] - -variables: - - &node_image "node:20-alpine" - - &install_deps | - corepack enable - pnpm install --frozen-lockfile - - &use_deps | - corepack enable - - &kaniko_setup | - mkdir -p /kaniko/.docker - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json - -steps: - # ====================== - # PHASE 1: Setup - # ====================== - install: - image: *node_image - commands: - - *install_deps - - prisma-generate: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - commands: - - *use_deps - - pnpm --filter "@mosaic/api" prisma:generate - depends_on: - - install - - # ====================== - # PHASE 2: Security Review - # ====================== - security-audit-deps: - image: *node_image - commands: - - *use_deps - - echo "=== Dependency Security Audit ===" - - pnpm audit --audit-level=high - depends_on: - - install - failure: fail # STRICT: Block on security vulnerabilities - - security-scan-secrets: - image: alpine/git:latest - commands: - - echo "=== Secret Scanning ===" - - apk add --no-cache bash - - | - # Check for common secrets patterns - echo "Scanning for hardcoded secrets..." - if git grep -E "(password|secret|api_key|private_key)\s*=\s*['\"]" -- '*.ts' '*.tsx' '*.js' '*.jsx' ':!*test*' ':!*spec*'; then - echo "❌ Found hardcoded secrets!" - exit 1 - fi - - echo "✅ No hardcoded secrets detected" - depends_on: - - install - when: - - event: pull_request - failure: fail # STRICT: Block on secret detection - - security-scan-sast: - image: returntocorp/semgrep - commands: - - echo "=== SAST Security Scanning ===" - - | - semgrep scan \ - --config=auto \ - --error \ - --exclude='node_modules' \ - --exclude='dist' \ - --exclude='*.test.ts' \ - --exclude='*.spec.ts' \ - --metrics=off \ - --quiet \ - || true # TODO: Make strict after baseline cleanup - - echo "✅ SAST scan complete" - depends_on: - - install - when: - - event: pull_request - failure: ignore # TODO: Change to 'fail' after fixing baseline issues - - # ====================== - # PHASE 3: Code Review - # ====================== - lint: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - commands: - - *use_deps - - echo "=== Lint Check ===" - - pnpm lint - depends_on: - - install - when: - - evaluate: 'CI_PIPELINE_EVENT != "pull_request" || CI_COMMIT_BRANCH != "main"' - failure: fail # STRICT: Block on lint errors - - typecheck: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - commands: - - *use_deps - - echo "=== TypeScript Type Check ===" - - pnpm typecheck - depends_on: - - prisma-generate - failure: fail # STRICT: Block on type errors - - # ====================== - # PHASE 4: QA - # ====================== - test-unit: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - commands: - - *use_deps - - echo "=== Unit Tests ===" - - pnpm test - depends_on: - - prisma-generate - failure: fail # STRICT: Block on test failures - - test-coverage: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - commands: - - *use_deps - - echo "=== Test Coverage Check ===" - - | - pnpm test:coverage --reporter=json --reporter=text > coverage-output.txt 2>&1 || true - cat coverage-output.txt - # TODO: Parse coverage report and enforce ≥85% threshold - echo "⚠️ Coverage enforcement not yet implemented" - depends_on: - - prisma-generate - when: - - event: pull_request - failure: ignore # TODO: Change to 'fail' after implementing coverage parser - - # ====================== - # PHASE 5: Build Verification - # ====================== - build: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - NODE_ENV: "production" - commands: - - *use_deps - - echo "=== Production Build ===" - - pnpm build - depends_on: - - typecheck - - security-audit-deps - - prisma-generate - failure: fail # STRICT: Block on build failures - - # ====================== - # PHASE 6: Auto-Merge (PR only) - # ====================== - pr-auto-merge: - image: alpine:latest - secrets: - - gitea_token - commands: - - echo "=== PR Auto-Merge Check ===" - - apk add --no-cache curl jq - - | - # Only run for PRs targeting develop - if [ "$CI_PIPELINE_EVENT" != "pull_request" ]; then - echo "⏭️ Skipping: Not a pull request" - exit 0 - fi - - # Extract PR number from CI environment - PR_NUMBER=$(echo "$CI_COMMIT_REF" | grep -oP 'pull/\K\d+' || echo "") - if [ -z "$PR_NUMBER" ]; then - echo "⏭️ Skipping: Cannot determine PR number" - exit 0 - fi - - echo "📋 Checking PR #$PR_NUMBER for auto-merge eligibility..." - - # Get PR details - PR_DATA=$(curl -s -H "Authorization: token $GITEA_TOKEN" \ - "https://git.mosaicstack.dev/api/v1/repos/mosaic/stack/pulls/$PR_NUMBER") - - # Check if PR is mergeable - IS_MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false') - BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref // ""') - PR_STATE=$(echo "$PR_DATA" | jq -r '.state // ""') - - if [ "$PR_STATE" != "open" ]; then - echo "⏭️ Skipping: PR is not open (state: $PR_STATE)" - exit 0 - fi - - if [ "$BASE_BRANCH" != "develop" ]; then - echo "⏭️ Skipping: PR does not target develop (targets: $BASE_BRANCH)" - exit 0 - fi - - if [ "$IS_MERGEABLE" != "true" ]; then - echo "❌ PR is not mergeable (conflicts or other issues)" - exit 0 - fi - - # All checks passed - merge the PR - echo "✅ All quality gates passed - attempting auto-merge..." - MERGE_RESULT=$(curl -s -X POST \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"Do":"merge","MergeMessageField":"","MergeTitleField":"","delete_branch_after_merge":true,"force_merge":false,"merge_when_checks_succeed":false}' \ - "https://git.mosaicstack.dev/api/v1/repos/mosaic/stack/pulls/$PR_NUMBER/merge") - - if echo "$MERGE_RESULT" | jq -e '.merged' > /dev/null 2>&1; then - echo "🎉 PR #$PR_NUMBER successfully merged to develop!" - else - ERROR_MSG=$(echo "$MERGE_RESULT" | jq -r '.message // "Unknown error"') - echo "❌ Failed to merge PR: $ERROR_MSG" - exit 1 - fi - depends_on: - - build - - test-unit - - lint - - typecheck - - security-audit-deps - when: - - event: pull_request - evaluate: 'CI_COMMIT_TARGET_BRANCH == "develop"' - failure: ignore # Don't fail pipeline if auto-merge fails - - # ====================== - # PHASE 7: Docker Build & Push (develop/main only) - # ====================== - docker-build-api: - image: gcr.io/kaniko-project/executor:debug - environment: - GITEA_USER: - from_secret: gitea_username - GITEA_TOKEN: - from_secret: gitea_token - CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} - CI_COMMIT_TAG: ${CI_COMMIT_TAG} - CI_COMMIT_SHA: ${CI_COMMIT_SHA} - commands: - - *kaniko_setup - - | - DESTINATIONS="--destination git.mosaicstack.dev/mosaic/api:${CI_COMMIT_SHA:0:8}" - if [ "$CI_COMMIT_BRANCH" = "main" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/api:latest" - elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/api:dev" - fi - if [ -n "$CI_COMMIT_TAG" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/api:$CI_COMMIT_TAG" - fi - /kaniko/executor --context . --dockerfile apps/api/Dockerfile $DESTINATIONS - when: - - branch: [main, develop] - event: [push, manual, tag] - depends_on: - - build - - docker-build-web: - image: gcr.io/kaniko-project/executor:debug - environment: - GITEA_USER: - from_secret: gitea_username - GITEA_TOKEN: - from_secret: gitea_token - CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} - CI_COMMIT_TAG: ${CI_COMMIT_TAG} - CI_COMMIT_SHA: ${CI_COMMIT_SHA} - commands: - - *kaniko_setup - - | - DESTINATIONS="--destination git.mosaicstack.dev/mosaic/web:${CI_COMMIT_SHA:0:8}" - if [ "$CI_COMMIT_BRANCH" = "main" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/web:latest" - elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/web:dev" - fi - if [ -n "$CI_COMMIT_TAG" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/web:$CI_COMMIT_TAG" - fi - /kaniko/executor --context . --dockerfile apps/web/Dockerfile --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS - when: - - branch: [main, develop] - event: [push, manual, tag] - depends_on: - - build - - docker-build-postgres: - image: gcr.io/kaniko-project/executor:debug - environment: - GITEA_USER: - from_secret: gitea_username - GITEA_TOKEN: - from_secret: gitea_token - CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} - CI_COMMIT_TAG: ${CI_COMMIT_TAG} - CI_COMMIT_SHA: ${CI_COMMIT_SHA} - commands: - - *kaniko_setup - - | - DESTINATIONS="--destination git.mosaicstack.dev/mosaic/postgres:${CI_COMMIT_SHA:0:8}" - if [ "$CI_COMMIT_BRANCH" = "main" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/postgres:latest" - elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/postgres:dev" - fi - if [ -n "$CI_COMMIT_TAG" ]; then - DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/postgres:$CI_COMMIT_TAG" - fi - /kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile $DESTINATIONS - when: - - branch: [main, develop] - event: [push, manual, tag] - depends_on: - - build diff --git a/docs/AUTOMATED-PR-MERGE.md b/docs/AUTOMATED-PR-MERGE.md deleted file mode 100644 index c279d3f..0000000 --- a/docs/AUTOMATED-PR-MERGE.md +++ /dev/null @@ -1,309 +0,0 @@ -# Automated PR Merge System - -## Overview - -The Mosaic Stack automated PR merge system ensures all pull requests meet strict quality, security, and testing standards before being merged to `develop`. PRs are automatically merged when all quality gates pass, eliminating manual intervention while maintaining high code quality. - -## Quality Gates - -All quality gates must pass before a PR can be auto-merged: - -### 1. Code Review ✅ - -- **Lint:** ESLint with strict rules, no warnings allowed -- **Type Safety:** TypeScript strict mode, no type errors -- **Build:** Production build must succeed -- **Pre-commit:** Automated via lint-staged (already strict) - -### 2. Security Review 🔒 - -- **Dependency Audit:** `pnpm audit` with high severity threshold -- **Secret Scanning:** Detects hardcoded passwords, API keys, tokens -- **SAST:** Static analysis security testing (Semgrep) -- **License Compliance:** (Planned) - -### 3. Quality Assurance 🧪 - -- **Unit Tests:** All tests must pass -- **Test Coverage:** ≥85% coverage requirement (enforced) -- **Integration Tests:** (Planned) -- **E2E Tests:** (Planned) - -## How It Works - -### 1. Developer Creates PR - -```bash -# Create feature branch -git checkout -b feature/my-feature develop - -# Make changes, commit -git add . -git commit -m "feat: add new feature" - -# Push and create PR -git push -u origin feature/my-feature -tea pr create --base develop --title "feat: add new feature" -``` - -### 2. CI Pipeline Runs - -Woodpecker CI automatically runs all quality gates: - -```mermaid -graph TD - A[PR Created] --> B[Install Dependencies] - B --> C[Security Audit] - B --> D[Secret Scanning] - B --> E[SAST Scanning] - B --> F[Lint Check] - B --> G[Type Check] - B --> H[Unit Tests] - H --> I[Coverage Check] - C --> J{All Checks Pass?} - D --> J - E --> J - F --> J - G --> J - I --> J - J -->|Yes| K[Build Verification] - K --> L[Auto-Merge to develop] - J -->|No| M[Block Merge] -``` - -### 3. Automatic Merge - -If all checks pass: - -- ✅ PR automatically merges to `develop` -- ✅ Feature branch automatically deleted -- ✅ Developer notified of successful merge - -If any check fails: - -- ❌ PR blocked from merging -- ❌ Developer notified of failure -- ❌ Must fix issues before retry - -## Configuration - -### Woodpecker CI - -The enhanced Woodpecker CI configuration is in `.woodpecker.enhanced.yml`. Key features: - -```yaml -# Strict quality gates (all must pass) -lint: - failure: fail # Block on any lint error/warning - -typecheck: - failure: fail # Block on any type error - -test-unit: - failure: fail # Block on any test failure - -# Security scanning -security-audit-deps: - failure: fail # Block on high/critical vulnerabilities - -security-scan-secrets: - failure: fail # Block on hardcoded secrets - -# Auto-merge step (runs after all checks pass) -pr-auto-merge: - when: - - event: pull_request - evaluate: 'CI_COMMIT_TARGET_BRANCH == "develop"' - depends_on: - - build - - test-unit - - lint - - typecheck - - security-audit-deps -``` - -### Required Secrets - -Configure these in Gitea settings → Secrets: - -- `gitea_token` - API token with repo write access -- `gitea_username` - Gitea username for Docker registry - -### Branch Protection - -Recommended Gitea branch protection for `develop`: - -```json -{ - "branch_name": "develop", - "enable_push": false, - "enable_push_whitelist": false, - "enable_merge_whitelist": false, - "enable_status_check": true, - "required_status_checks": [ - "ci/woodpecker/pr/lint", - "ci/woodpecker/pr/typecheck", - "ci/woodpecker/pr/test-unit", - "ci/woodpecker/pr/security-audit-deps", - "ci/woodpecker/pr/build" - ], - "enable_approvals_whitelist": false, - "required_approvals": 0 -} -``` - -## Manual Auto-Merge - -You can manually trigger auto-merge for a specific PR: - -```bash -# Set Gitea API token -export GITEA_TOKEN="your-token-here" - -# Merge PR #123 to develop -./scripts/ci/auto-merge-pr.sh 123 - -# Dry run (check without merging) -DRY_RUN=true ./scripts/ci/auto-merge-pr.sh 123 -``` - -## Quality Gate Strictness Levels - -### Current (Enhanced) Configuration - -| Check | Status | Blocking | Notes | -| ---------------- | --------- | -------- | ---------------------------------------- | -| Dependency Audit | ✅ Active | Yes | Blocks on high+ vulnerabilities | -| Secret Scanning | ✅ Active | Yes | Blocks on hardcoded secrets | -| SAST (Semgrep) | ⚠️ Active | No\* | \*TODO: Enable after baseline cleanup | -| Lint | ✅ Active | Yes | Zero warnings/errors | -| TypeScript | ✅ Active | Yes | Strict mode, no errors | -| Unit Tests | ✅ Active | Yes | All tests must pass | -| Test Coverage | ⚠️ Active | No\* | \*TODO: Enable after implementing parser | -| Build | ✅ Active | Yes | Production build must succeed | - -### Migration Plan - -**Phase 1: Current State** - -- Lint and tests non-blocking (`|| true`) -- Basic security audit -- Manual PR merging - -**Phase 2: Enhanced (This PR)** ← WE ARE HERE - -- All checks strict and blocking -- Security scanning (deps, secrets, SAST) -- Auto-merge enabled for clean PRs - -**Phase 3: Future Enhancements** - -- SAST fully enforced (after baseline cleanup) -- Test coverage threshold enforced (≥85%) -- Integration and E2E tests -- License compliance checking -- Performance regression testing - -## Troubleshooting - -### PR Not Auto-Merging - -Check these common issues: - -1. **Merge Conflicts** - - ```bash - # Rebase on develop - git fetch origin develop - git rebase origin/develop - git push --force-with-lease - ``` - -2. **Failed Quality Gates** - - ```bash - # Check CI logs - woodpecker pipeline ls mosaic/stack - woodpecker log show mosaic/stack - ``` - -3. **Missing Status Checks** - - Ensure all required checks are configured - - Verify Woodpecker CI is running - - Check webhook configuration - -### Bypassing Auto-Merge - -In rare cases where manual merge is needed: - -```bash -# Manually merge via CLI (requires admin access) -tea pr merge --style merge -``` - -**⚠️ WARNING:** Manual merges bypass quality gates. Only use in emergencies. - -## Best Practices - -### For Developers - -1. **Run checks locally before pushing:** - - ```bash - pnpm lint - pnpm typecheck - pnpm test - pnpm build - ``` - -2. **Keep PRs focused:** - - One feature/fix per PR - - Smaller PRs merge faster - - Easier to review and debug - -3. **Write tests first (TDD):** - - Tests before implementation - - Maintains ≥85% coverage - - Catches issues early - -4. **Check CI status:** - - Monitor pipeline progress - - Fix failures immediately - - Don't stack PRs on failing ones - -### For Reviewers - -1. **Trust the automation:** - - If CI passes, code meets standards - - Focus on architecture and design - - Don't duplicate automated checks - -2. **Review promptly:** - - PRs auto-merge when checks pass - - Review before auto-merge if needed - - Use Gitea's review features - -3. **Provide constructive feedback:** - - Suggest improvements - - Link to documentation - - Explain reasoning - -## Metrics & Monitoring - -Track these metrics to measure effectiveness: - -- **Auto-merge rate:** % of PRs merged automatically -- **Average time to merge:** From PR creation to merge -- **Quality gate failures:** Which checks fail most often -- **Rollback rate:** % of merges that need revert - -## References - -- [Quality Rails Status](docs/quality-rails-status.md) -- [Woodpecker CI Documentation](https://woodpecker-ci.org/docs) -- [Gitea API Documentation](https://docs.gitea.io/en-us/api-usage/) -- [Design Principles](docs/DESIGN-PRINCIPLES.md) - ---- - -**Questions?** Contact the platform team or create an issue. diff --git a/docs/MIGRATION-AUTO-MERGE.md b/docs/MIGRATION-AUTO-MERGE.md deleted file mode 100644 index 16e5e44..0000000 --- a/docs/MIGRATION-AUTO-MERGE.md +++ /dev/null @@ -1,366 +0,0 @@ -# Migration Guide: Enhanced CI/CD with Auto-Merge - -## Overview - -This guide walks through migrating from the current Woodpecker CI configuration to the enhanced version with strict quality gates and automated PR merging. - -## Pre-Migration Checklist - -Before activating the enhanced pipeline, ensure: - -- [ ] **All existing PRs are merged or closed** - - Enhanced pipeline has strict gates that may block old PRs - - Review and clean up pending PRs first - -- [ ] **Baseline quality metrics recorded** - - ```bash - # Run on clean develop branch - pnpm lint 2>&1 | tee baseline-lint.txt - pnpm typecheck 2>&1 | tee baseline-typecheck.txt - pnpm test 2>&1 | tee baseline-tests.txt - ``` - -- [ ] **Gitea API token created** - - Go to Settings → Applications → Generate New Token - - Scopes: `repo` (full control) - - Save token securely - -- [ ] **Woodpecker secrets configured** - - ```bash - # Add gitea_token secret - woodpecker secret add \ - --repository mosaic/stack \ - --name gitea_token \ - --value "your-token-here" - ``` - -- [ ] **Team notified of change** - - Announce strict quality gates - - Share this migration guide - - Schedule migration during low-activity period - -## Migration Steps - -### Step 1: Backup Current Configuration - -```bash -# Backup current .woodpecker.yml -cp .woodpecker.yml .woodpecker.yml.backup - -# Backup current git state -git branch backup-pre-migration -git push origin backup-pre-migration -``` - -### Step 2: Activate Enhanced Configuration - -```bash -# Replace .woodpecker.yml with enhanced version -cp .woodpecker.enhanced.yml .woodpecker.yml - -# Review changes -git diff .woodpecker.yml.backup .woodpecker.yml -``` - -Key changes: - -- ✅ Removed `|| true` from lint step (now strict) -- ✅ Removed `|| true` from test step (now strict) -- ✅ Added security scanning steps -- ✅ Added test coverage step -- ✅ Added pr-auto-merge step - -### Step 3: Test with a Dry-Run PR - -Create a test PR to verify the enhanced pipeline: - -```bash -# Create test branch -git checkout -b test/enhanced-ci develop - -# Make a trivial change -echo "# CI Test" >> README.md -git add README.md -git commit -m "test: verify enhanced CI pipeline" - -# Push and create PR -git push -u origin test/enhanced-ci -tea pr create \ - --base develop \ - --title "test: Verify enhanced CI pipeline" \ - --description "Test PR to verify all quality gates work correctly" -``` - -Monitor the pipeline: - -```bash -# Watch pipeline status -woodpecker pipeline ls mosaic/stack - -# View logs if needed -woodpecker log show mosaic/stack -``` - -Expected behavior: - -- ✅ All quality gates run -- ✅ Security scans complete -- ✅ Tests and coverage checks run -- ✅ PR auto-merges if all checks pass - -### Step 4: Configure Branch Protection - -Set up branch protection for `develop`: - -**Option A: Via Gitea Web UI** - -1. Go to Settings → Branches -2. Add branch protection rule for `develop` -3. Enable: "Enable Status Check" -4. Add required checks: - - `ci/woodpecker/pr/lint` - - `ci/woodpecker/pr/typecheck` - - `ci/woodpecker/pr/test-unit` - - `ci/woodpecker/pr/security-audit-deps` - - `ci/woodpecker/pr/build` - -**Option B: Via API** - -```bash -curl -X POST "https://git.mosaicstack.dev/api/v1/repos/mosaic/stack/branch_protections" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "branch_name": "develop", - "enable_push": false, - "enable_status_check": true, - "status_check_contexts": [ - "ci/woodpecker/pr/lint", - "ci/woodpecker/pr/typecheck", - "ci/woodpecker/pr/test-unit", - "ci/woodpecker/pr/security-audit-deps", - "ci/woodpecker/pr/build" - ] - }' -``` - -### Step 5: Gradual Rollout - -**Phase 1: Monitor (Week 1)** - -- Enhanced CI active, auto-merge disabled -- Monitor quality gate failures -- Collect metrics on pass/fail rates - -```yaml -# In .woodpecker.yml, set auto-merge to dry-run: -pr-auto-merge: - commands: - - export DRY_RUN=true - - ./scripts/ci/auto-merge-pr.sh -``` - -**Phase 2: Enable Auto-Merge (Week 2)** - -- Remove DRY_RUN flag -- Enable auto-merge for clean PRs -- Monitor merge success rate - -**Phase 3: Enforce Coverage (Week 3)** - -- Enable test coverage threshold -- Set minimum to 85% -- Block PRs below threshold - -**Phase 4: Full Enforcement (Week 4)** - -- Enable SAST as blocking -- Enforce all quality gates -- Remove any remaining fallbacks - -### Step 6: Cleanup - -After successful migration: - -```bash -# Remove backup files -rm .woodpecker.yml.backup -git branch -D backup-pre-migration -git push origin --delete backup-pre-migration - -# Remove old test PR -tea pr close -``` - -## Rollback Plan - -If issues arise during migration: - -### Immediate Rollback - -```bash -# Restore original configuration -cp .woodpecker.yml.backup .woodpecker.yml -git add .woodpecker.yml -git commit -m "rollback: Restore original CI configuration" -git push origin develop -``` - -### Partial Rollback - -If only specific gates are problematic: - -```yaml -# Make specific checks non-blocking temporarily -lint: - commands: - - pnpm lint || true # Non-blocking during stabilization - failure: ignore -``` - -## Post-Migration Verification - -After migration, verify: - -- [ ] **All quality gates run on PRs** - - ```bash - # Check recent PR pipelines - tea pr list --state all --limit 10 - ``` - -- [ ] **Auto-merge works correctly** - - Create test PR with passing checks - - Verify auto-merge occurs - - Check branch deletion - -- [ ] **Failures block correctly** - - Create test PR with lint errors - - Verify PR is blocked - - Verify error messages are clear - -- [ ] **Metrics tracked** - - Auto-merge rate - - Average time to merge - - Quality gate failure rate - -## Troubleshooting - -### Issue: PRs not auto-merging - -**Diagnosis:** - -```bash -# Check if pr-auto-merge step ran -woodpecker log show mosaic/stack | grep "pr-auto-merge" - -# Check Gitea token permissions -curl -H "Authorization: token $GITEA_TOKEN" \ - https://git.mosaicstack.dev/api/v1/user -``` - -**Solutions:** - -1. Verify `gitea_token` secret is configured -2. Check token has `repo` scope -3. Ensure PR targets `develop` -4. Verify all dependencies passed - -### Issue: Quality gates failing unexpectedly - -**Diagnosis:** - -```bash -# Run checks locally -pnpm lint -pnpm typecheck -pnpm test - -# Compare with baseline -diff baseline-lint.txt <(pnpm lint 2>&1) -``` - -**Solutions:** - -1. Fix actual code issues -2. Update baseline if needed -3. Temporarily make check non-blocking -4. Investigate CI environment differences - -### Issue: Security scans too strict - -**Diagnosis:** - -```bash -# Run security scan locally -pnpm audit --audit-level=high - -# Check specific vulnerability -pnpm audit --json | jq '.vulnerabilities' -``` - -**Solutions:** - -1. Update dependencies: `pnpm update` -2. Add audit exceptions if false positive -3. Lower severity threshold temporarily -4. Fix actual vulnerabilities - -## Success Criteria - -Migration is successful when: - -- ✅ **100% of clean PRs auto-merge** - - No manual intervention needed - - Merge within 5 minutes of CI completion - -- ✅ **Zero false-positive blocks** - - All blocked PRs have actual issues - - No spurious failures - -- ✅ **Developer satisfaction high** - - Fast feedback loops - - Clear error messages - - Minimal friction - -- ✅ **Quality maintained or improved** - - No increase in bugs reaching develop - - Test coverage ≥85% - - Security vulnerabilities caught early - -## Next Steps - -After successful migration: - -1. **Monitor and optimize** - - Track metrics weekly - - Identify bottlenecks - - Optimize slow steps - -2. **Expand coverage** - - Add integration tests - - Add E2E tests - - Add performance tests - -3. **Enhance security** - - Enable SAST fully - - Add license compliance - - Add container scanning - -4. **Improve developer experience** - - Add pre-push hooks - - Create quality dashboard - - Automate changelog generation - -## Support - -- **Documentation:** [docs/AUTOMATED-PR-MERGE.md](AUTOMATED-PR-MERGE.md) -- **Issues:** https://git.mosaicstack.dev/mosaic/stack/issues -- **Team Chat:** #engineering on Mattermost - ---- - -**Migration Owner:** Platform Team -**Last Updated:** 2026-02-03 diff --git a/scripts/ci/auto-merge-pr.sh b/scripts/ci/auto-merge-pr.sh deleted file mode 100755 index aa0f7df..0000000 --- a/scripts/ci/auto-merge-pr.sh +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env bash -# -# Auto-Merge PR Script -# -# Automatically merges a PR to develop if all quality gates pass. -# This script can be called from Woodpecker CI or manually. -# -# Usage: -# auto-merge-pr.sh -# -# Environment variables: -# GITEA_TOKEN - Gitea API token (required) -# GITEA_URL - Gitea instance URL (default: https://git.mosaicstack.dev) -# REPO_OWNER - Repository owner (default: mosaic) -# REPO_NAME - Repository name (default: stack) -# TARGET_BRANCH - Target branch for auto-merge (default: develop) -# DRY_RUN - If set, only check eligibility without merging (default: false) - -set -euo pipefail - -# Configuration -GITEA_URL="${GITEA_URL:-https://git.mosaicstack.dev}" -REPO_OWNER="${REPO_OWNER:-mosaic}" -REPO_NAME="${REPO_NAME:-stack}" -TARGET_BRANCH="${TARGET_BRANCH:-develop}" -DRY_RUN="${DRY_RUN:-false}" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Functions -log_info() { - echo -e "${BLUE}ℹ️ $1${NC}" -} - -log_success() { - echo -e "${GREEN}✅ $1${NC}" -} - -log_warning() { - echo -e "${YELLOW}⚠️ $1${NC}" -} - -log_error() { - echo -e "${RED}❌ $1${NC}" -} - -# Check requirements -if [ -z "${GITEA_TOKEN:-}" ]; then - log_error "GITEA_TOKEN environment variable is required" - exit 1 -fi - -if [ $# -lt 1 ]; then - log_error "Usage: $0 " - exit 1 -fi - -PR_NUMBER="$1" -API_BASE="$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME" - -log_info "Checking PR #$PR_NUMBER for auto-merge eligibility..." - -# Fetch PR details -PR_DATA=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \ - "$API_BASE/pulls/$PR_NUMBER" || { - log_error "Failed to fetch PR #$PR_NUMBER" - exit 1 -}) - -# Extract PR information -PR_STATE=$(echo "$PR_DATA" | jq -r '.state // ""') -PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged // false') -BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref // ""') -HEAD_BRANCH=$(echo "$PR_DATA" | jq -r '.head.ref // ""') -IS_MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false') -HAS_CONFLICTS=$(echo "$PR_DATA" | jq -r '.mergeable_state // "unknown"') -PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // "Unknown"') - -log_info "PR #$PR_NUMBER: $PR_TITLE" -log_info " State: $PR_STATE" -log_info " Base: $BASE_BRANCH ← Head: $HEAD_BRANCH" -log_info " Mergeable: $IS_MERGEABLE ($HAS_CONFLICTS)" - -# Check if PR is already merged -if [ "$PR_MERGED" = "true" ]; then - log_success "PR is already merged" - exit 0 -fi - -# Check if PR is open -if [ "$PR_STATE" != "open" ]; then - log_warning "PR is not open (state: $PR_STATE)" - exit 0 -fi - -# Check if PR targets the correct branch -if [ "$BASE_BRANCH" != "$TARGET_BRANCH" ]; then - log_warning "PR does not target $TARGET_BRANCH (targets: $BASE_BRANCH)" - exit 0 -fi - -# Check if PR is mergeable -if [ "$IS_MERGEABLE" != "true" ]; then - log_error "PR is not mergeable (state: $HAS_CONFLICTS)" - exit 1 -fi - -# Fetch CI status checks -log_info "Checking CI status..." -STATUS_DATA=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \ - "$API_BASE/statuses/$(echo "$PR_DATA" | jq -r '.head.sha')" || echo "[]") - -# Count status check results -TOTAL_CHECKS=$(echo "$STATUS_DATA" | jq 'length') -SUCCESS_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "success")] | length') -PENDING_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "pending")] | length') -FAILURE_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "failure" or .state == "error")] | length') - -log_info " Total checks: $TOTAL_CHECKS" -log_info " Success: $SUCCESS_CHECKS" -log_info " Pending: $PENDING_CHECKS" -log_info " Failed: $FAILURE_CHECKS" - -# Check if there are any failures -if [ "$FAILURE_CHECKS" -gt 0 ]; then - log_error "PR has $FAILURE_CHECKS failed status checks" - echo "$STATUS_DATA" | jq -r '.[] | select(.state == "failure" or .state == "error") | " - \(.context): \(.description)"' - exit 1 -fi - -# Check if there are pending checks -if [ "$PENDING_CHECKS" -gt 0 ]; then - log_warning "PR has $PENDING_CHECKS pending status checks - waiting..." - exit 0 -fi - -# Check if all required checks passed -if [ "$TOTAL_CHECKS" -eq 0 ]; then - log_warning "No status checks found - proceeding with caution" -elif [ "$SUCCESS_CHECKS" -ne "$TOTAL_CHECKS" ]; then - log_warning "Not all status checks are successful ($SUCCESS_CHECKS/$TOTAL_CHECKS)" -fi - -# All checks passed - ready to merge -log_success "All quality gates passed!" - -if [ "$DRY_RUN" = "true" ]; then - log_info "DRY RUN: Would merge PR #$PR_NUMBER to $TARGET_BRANCH" - exit 0 -fi - -# Perform the merge -log_info "Merging PR #$PR_NUMBER to $TARGET_BRANCH..." -MERGE_RESULT=$(curl -sf -X POST \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "Do": "merge", - "MergeMessageField": "", - "MergeTitleField": "", - "delete_branch_after_merge": true, - "force_merge": false, - "merge_when_checks_succeed": false - }' \ - "$API_BASE/pulls/$PR_NUMBER/merge" || { - log_error "Failed to merge PR #$PR_NUMBER" - exit 1 -}) - -# Check if merge was successful -if echo "$MERGE_RESULT" | jq -e '.merged' > /dev/null 2>&1; then - MERGE_SHA=$(echo "$MERGE_RESULT" | jq -r '.sha // "unknown"') - log_success "Successfully merged PR #$PR_NUMBER to $TARGET_BRANCH" - log_success " Merge commit: $MERGE_SHA" - log_success " Branch $HEAD_BRANCH deleted" -else - ERROR_MSG=$(echo "$MERGE_RESULT" | jq -r '.message // "Unknown error"') - log_error "Merge failed: $ERROR_MSG" - exit 1 -fi