feat: canonical component build system — one file per component, generated outputs, drift gate #4
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: Packages Checks | |
| on: | |
| pull_request: | |
| paths: | |
| - 'packages/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| - '.github/workflows/packages-checks.yml' | |
| - '.github/dependabot.yml' | |
| - 'scripts/validation/detect-pr-changes.ts' | |
| - 'scripts/validation/detect-pr-changes.test.ts' | |
| # The canonical source, its committed output, and the drift gate's machinery. | |
| - 'content/**' | |
| - '.opencode/agent/**' | |
| - '.oac/**' | |
| - 'registry.json' | |
| - 'Makefile' | |
| - 'scripts/validation/check-build-drift.sh' | |
| permissions: | |
| contents: read | |
| jobs: | |
| check-changes: | |
| name: Detect Package Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has-packages: ${{ steps.filter.outputs.has-packages }} | |
| has-canonical: ${{ steps.filter.outputs.has-canonical }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: 1.3.14 | |
| # The detector decides which gates run, so a broken detector silently skips them — | |
| # including the drift gate. Its tests also pin the drift job's contract (that it is | |
| # blocking, gated on has-canonical, and runs the same make target contributors do). | |
| # They must execute somewhere, and nothing else in CI ran them. | |
| - name: Test change detection | |
| run: bun test scripts/validation/detect-pr-changes.test.ts | |
| - name: Check changed files | |
| id: filter | |
| run: | | |
| echo "Changed files:" >&2 | |
| git diff --name-only -z "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}" | | |
| tee >(while IFS= read -r -d '' path; do printf ' %q\n' "$path" >&2; done) | | |
| bun run scripts/validation/detect-pr-changes.ts | |
| cli-checks: | |
| name: CLI (build + test + typecheck) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: check-changes | |
| if: needs.check-changes.outputs.has-packages == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: 1.3.14 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| cache-dependency-path: 'pnpm-lock.yaml' | |
| - name: Install workspace dependencies | |
| run: pnpm install --frozen-lockfile | |
| # cli tsconfig paths point at ../compatibility-layer/dist, so build it first. | |
| - name: Build compatibility-layer dependency | |
| run: pnpm --dir packages/compatibility-layer run build | |
| - name: Typecheck | |
| run: pnpm --dir packages/cli run typecheck | |
| - name: Build | |
| run: pnpm --dir packages/cli run build | |
| # packages/cli tests use Bun-only APIs (Bun.file, Bun.spawn, import.meta.dir) | |
| # until Stage 5 removes them — they must run under `bun test`, not vitest. | |
| - name: Test | |
| run: pnpm --dir packages/cli run test | |
| compatibility-layer-checks: | |
| name: Compatibility Layer (build + test + lint) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: check-changes | |
| if: needs.check-changes.outputs.has-packages == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| cache-dependency-path: 'pnpm-lock.yaml' | |
| - name: Install workspace dependencies | |
| run: pnpm install --frozen-lockfile | |
| # build runs tsc, which is also the typecheck for this package. | |
| - name: Build (tsc typecheck) | |
| run: pnpm --dir packages/compatibility-layer run build | |
| - name: Lint | |
| run: pnpm --dir packages/compatibility-layer run lint | |
| - name: Test (vitest) | |
| run: pnpm --dir packages/compatibility-layer test | |
| # The structural guarantee the canonical refactor rests on: the generated trees stay | |
| # COMMITTED, so drift has to be impossible rather than merely discouraged. Rebuild them from | |
| # content/agents/** and fail if the build changed anything — a hand-edit to generated output | |
| # turns this red. BLOCKING by design: no continue-on-error. | |
| canonical-drift: | |
| name: Canonical Build (no drift) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: check-changes | |
| if: needs.check-changes.outputs.has-canonical == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| # `oac build` runs from packages/cli/dist, which is bundled `--target bun`. | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: 1.3.14 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| cache-dependency-path: 'pnpm-lock.yaml' | |
| - name: Install workspace dependencies | |
| run: pnpm install --frozen-lockfile | |
| # The identical target contributors run locally, so a red gate here is reproducible with | |
| # one command rather than by reading this file and reassembling it by hand. | |
| - name: Build canonical trees and check for drift | |
| run: make check-canonical-drift | |
| summary: | |
| name: Packages Checks Summary | |
| runs-on: ubuntu-latest | |
| needs: [check-changes, cli-checks, compatibility-layer-checks, canonical-drift] | |
| if: always() | |
| steps: | |
| - name: Generate summary | |
| run: | | |
| echo "## 📦 Packages Checks Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| report() { | |
| local label="$1" result="$2" | |
| case "$result" in | |
| success) echo "✅ **${label}:** Passed" >> $GITHUB_STEP_SUMMARY ;; | |
| skipped) echo "⏭️ **${label}:** Skipped (no relevant changes)" >> $GITHUB_STEP_SUMMARY ;; | |
| *) echo "❌ **${label}:** ${result}" >> $GITHUB_STEP_SUMMARY ;; | |
| esac | |
| } | |
| report "Change Detection" "${{ needs.check-changes.result }}" | |
| report "CLI" "${{ needs.cli-checks.result }}" | |
| report "Compatibility Layer" "${{ needs.compatibility-layer-checks.result }}" | |
| report "Canonical Build (no drift)" "${{ needs.canonical-drift.result }}" | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.canonical-drift.result }}" = "failure" ]; then | |
| echo "> The generated trees drift from \`content/agents/**\`." >> $GITHUB_STEP_SUMMARY | |
| echo "> Run \`make build-canonical\` and commit the result." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| FAILED=0 | |
| [ "${{ needs.check-changes.result }}" != "success" ] && FAILED=1 | |
| for result in "${{ needs.cli-checks.result }}" "${{ needs.compatibility-layer-checks.result }}" "${{ needs.canonical-drift.result }}"; do | |
| if [ "$result" != "success" ] && [ "$result" != "skipped" ]; then | |
| FAILED=1 | |
| fi | |
| done | |
| if [ $FAILED -eq 0 ]; then | |
| echo "### ✅ Required Package Checks Passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### ❌ Some Package Checks Failed" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi |