chore(release): v3.9.0 #602
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Optimized CI | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| workflow_dispatch: | |
| # On PRs: cancel stale runs when a new push arrives (old run is obsolete). | |
| # On main: never cancel — every push must run to completion for accurate CI signal. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| actions: write | |
| pull-requests: write | |
| checks: write | |
| # ─── Shared exclusions for journey test shards ─── | |
| # The original test:journeys:ci command excludes these directories: | |
| # browser, browser-integration, mcp, sync, learning, domains, llm, n8n, *.e2e.test.ts | |
| # By targeting specific subdirectories per shard, most exclusions are implicit. | |
| # Only the "remaining" shard needs explicit excludes. | |
| jobs: | |
| # ─── Journey Tests: 4 parallel shards ─── | |
| # Previously a single 25-min job that timed out. Now 4 shards of ~5-8 min each. | |
| journey-governance: | |
| name: Journey — Governance | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run governance integration tests | |
| run: timeout 480 npx vitest run tests/integration/governance/ --reporter=verbose | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=1024' | |
| journey-ruvector-coordination: | |
| name: Journey — RuVector + Coordination | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run ruvector + coordination integration tests | |
| run: bash scripts/ci-vitest-run.sh tests/integration/ruvector/ tests/integration/coordination/ --reporter=verbose | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=1024' | |
| journey-root: | |
| name: Journey — Root-level | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run root-level integration tests | |
| run: | | |
| # Run only test files directly in tests/integration/ (not in subdirectories) | |
| FILES=$(find tests/integration -maxdepth 1 -name '*.test.ts' ! -name '*.e2e.test.ts' | sort) | |
| if [ -n "$FILES" ]; then | |
| bash scripts/ci-vitest-run.sh $FILES --reporter=verbose | |
| else | |
| echo "No root-level integration tests found" | |
| fi | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=1024' | |
| journey-remaining: | |
| name: Journey — Remaining Subdirs | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run remaining integration subdirectory tests | |
| run: | | |
| # Everything in tests/integration subdirs EXCEPT the ones covered by | |
| # other shards (governance, ruvector, coordination) and the original | |
| # exclusions (browser, mcp, sync, learning, domains, llm, n8n). | |
| bash scripts/ci-vitest-run.sh \ | |
| tests/integration/adapters/ \ | |
| tests/integration/cli/ \ | |
| tests/integration/code-intelligence/ \ | |
| tests/integration/feedback/ \ | |
| tests/integration/migration/ \ | |
| tests/integration/opencode/ \ | |
| tests/integration/parsers/ \ | |
| tests/integration/planning/ \ | |
| tests/integration/protocols/ \ | |
| tests/integration/requirements-validation/ \ | |
| tests/integration/rl-suite/ \ | |
| tests/integration/rvf/ \ | |
| tests/integration/security/ \ | |
| tests/integration/templates/ \ | |
| tests/integration/test-execution/ \ | |
| tests/integration/test-generation/ \ | |
| tests/integration/validation/ \ | |
| --reporter=verbose | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=1024' | |
| # ─── Contract + Code Intelligence: separate parallel jobs ─── | |
| contract-tests: | |
| name: Contract Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run contract tests | |
| run: | | |
| if [ -d "tests/unit/domains/contract-testing" ] && [ "$(ls -A tests/unit/domains/contract-testing/*.test.ts 2>/dev/null)" ]; then | |
| timeout 480 npm run test:contracts | |
| else | |
| echo "No contract tests found" | |
| fi | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=512' | |
| code-intelligence-tests: | |
| name: Code Intelligence Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run code intelligence tests | |
| run: timeout 480 npm run test:code-intelligence | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=768' | |
| SKIP_INTEGRATION_TESTS: 'true' | |
| # ─── Infrastructure tests (unchanged, already parallel) ─── | |
| infrastructure-tests: | |
| name: Infrastructure Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run Infrastructure Tests | |
| run: | | |
| if [ -d "tests/infrastructure" ] && [ "$(ls -A tests/infrastructure/*.test.ts 2>/dev/null)" ]; then | |
| timeout 480 npm run test:infrastructure | |
| else | |
| echo "No infrastructure tests found" | |
| fi | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=768' | |
| - name: Run Regression Tests | |
| run: | | |
| if [ -d "tests/regression" ] && [ "$(find tests/regression -name '*.test.ts' 2>/dev/null | head -1)" ]; then | |
| timeout 480 npm run test:regression | |
| else | |
| echo "No regression tests found" | |
| fi | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=512' | |
| # ─── Postgres integration tests (unchanged) ─── | |
| integration-tests: | |
| name: Postgres Integration Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_DB: aqe_test | |
| POSTGRES_USER: aqe_test | |
| POSTGRES_PASSWORD: aqe_test | |
| ports: | |
| - 15432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run Postgres integration tests | |
| run: timeout 480 npm run test:integration:pg | |
| env: | |
| POSTGRES_URL: postgresql://aqe_test:aqe_test@localhost:15432/aqe_test | |
| NODE_OPTIONS: '--max-old-space-size=1024' | |
| # ─── Performance gates (after journey shards complete) ─── | |
| performance-gates: | |
| name: Performance Gates | |
| runs-on: ubuntu-latest | |
| needs: [journey-governance, journey-ruvector-coordination, journey-root, journey-remaining, contract-tests, code-intelligence-tests] | |
| if: success() | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run Performance Gates | |
| id: perf-gates | |
| run: | | |
| timeout 480 npm run performance:gate > perf-report.md 2>&1 || PERF_EXIT=$? | |
| echo "PERF_EXIT_CODE=${PERF_EXIT:-0}" >> $GITHUB_OUTPUT | |
| cat perf-report.md | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: performance-report | |
| path: perf-report.md | |
| retention-days: 30 | |
| - name: Check performance gate results | |
| run: | | |
| EXIT_CODE="${{ steps.perf-gates.outputs.PERF_EXIT_CODE }}" | |
| if [ "$EXIT_CODE" -eq 0 ]; then | |
| echo "All performance gates passed" | |
| elif [ "$EXIT_CODE" -eq 2 ]; then | |
| echo "Performance gates passed with warnings" | |
| else | |
| echo "Performance gates failed (exit code: $EXIT_CODE)" | |
| if [ "$EXIT_CODE" -eq 1 ]; then | |
| exit 1 | |
| fi | |
| fi | |
| # ─── Coverage analysis (PRs only, after journey shards) ─── | |
| coverage: | |
| name: Coverage Analysis | |
| runs-on: ubuntu-latest | |
| needs: [journey-governance, journey-ruvector-coordination, journey-root, journey-remaining] | |
| timeout-minutes: 10 | |
| if: success() && github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24', cache: 'npm' } | |
| - run: npm ci | |
| - run: npm run build | |
| - name: Run tests with coverage (excluding browser E2E) | |
| run: bash scripts/ci-vitest-run.sh --coverage --reporter=verbose | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=1024' | |
| CI: 'true' | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage-report | |
| path: coverage/ | |
| retention-days: 7 | |
| - name: Check coverage thresholds | |
| run: | | |
| if [ -f "coverage/coverage-summary.json" ]; then | |
| COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct') | |
| echo "Line coverage: ${COVERAGE}%" | |
| if (( $(echo "$COVERAGE < 80" | bc -l) )); then | |
| echo "Coverage below 80% threshold" | |
| else | |
| echo "Coverage meets threshold" | |
| fi | |
| fi | |
| # ─── Test dashboard (summary) ─── | |
| dashboard: | |
| name: Test Dashboard | |
| runs-on: ubuntu-latest | |
| needs: [journey-governance, journey-ruvector-coordination, journey-root, journey-remaining, contract-tests, code-intelligence-tests, infrastructure-tests] | |
| if: always() | |
| timeout-minutes: 2 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '24' } | |
| - name: Generate Dashboard | |
| run: node scripts/test-dashboard.cjs | |
| - name: Generate Migration Metrics | |
| run: | | |
| echo "# CI Test Metrics" > ci-metrics.md | |
| echo "" >> ci-metrics.md | |
| echo "**Date**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> ci-metrics.md | |
| echo "**Commit**: ${{ github.sha }}" >> ci-metrics.md | |
| echo "" >> ci-metrics.md | |
| total_files=$(find tests -name "*.test.ts" 2>/dev/null | wc -l || echo "0") | |
| total_lines=$(find tests -name "*.test.ts" -exec wc -l {} + 2>/dev/null | tail -1 | awk '{print $1}' || echo "0") | |
| large_files=$(find tests -name "*.test.ts" -exec wc -l {} \; 2>/dev/null | awk '$1 > 600' | wc -l || echo "0") | |
| skipped=$(grep -r "describe.skip\|it.skip\|test.skip" tests --include="*.test.ts" 2>/dev/null | wc -l || echo "0") | |
| echo "## Current State" >> ci-metrics.md | |
| echo "- Total test files: $total_files (target: 50)" >> ci-metrics.md | |
| echo "- Total lines: $total_lines (target: 40,000)" >> ci-metrics.md | |
| echo "- Files > 600 lines: $large_files (target: 0)" >> ci-metrics.md | |
| echo "- Skipped tests: $skipped (target: 0)" >> ci-metrics.md | |
| echo "" >> ci-metrics.md | |
| baseline_files=426 | |
| baseline_lines=208253 | |
| files_reduced=$((baseline_files - total_files)) | |
| lines_reduced=$((baseline_lines - total_lines)) | |
| echo "## Progress from Baseline" >> ci-metrics.md | |
| echo "- Files reduced: $files_reduced (-$((files_reduced * 100 / baseline_files))%)" >> ci-metrics.md | |
| echo "- Lines reduced: $lines_reduced (-$((lines_reduced * 100 / baseline_lines))%)" >> ci-metrics.md | |
| cat ci-metrics.md | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ci-metrics | |
| path: ci-metrics.md | |
| retention-days: 30 | |
| - name: Comment on PR | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const metrics = fs.readFileSync('ci-metrics.md', 'utf8'); | |
| const body = `## Test Suite Metrics | |
| ${metrics} | |
| --- | |
| *Generated by Optimized CI*`; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(c => | |
| c.user?.type === 'Bot' && c.body?.includes('Test Suite Metrics') | |
| ); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body | |
| }); | |
| } |