Repository navigation
feat(cli): implement runtime rule execution #148
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: PR OpenSpec Archive Check | |
| # A change is archived exactly once, when ALL of its work has landed. In a | |
| # stack of PRs, only the tip carries the archived change; the PRs below it still | |
| # carry the in-flight change directory by design. So this check runs only on the | |
| # tip of a stack (or a standalone PR) and is skipped on PRs that still have work | |
| # stacked on top of them. Tip = no other OPEN PR targets this PR's head branch | |
| # as its base. | |
| on: | |
| pull_request: | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| stack-position: | |
| name: Detect stack position | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is_tip: ${{ steps.detect.outputs.is_tip }} | |
| steps: | |
| - name: Determine whether this PR is the tip of its stack | |
| id: detect | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| # Count OPEN PRs that target this PR's head branch as their base. | |
| # Any such PR means work is still stacked on top → not the tip. | |
| # Do NOT guess on failure: if stack position can't be read, fail loudly | |
| # rather than silently defaulting to "tip" (which runs the gate on a | |
| # mid-stack PR) or "not tip" (which skips archiving on a real tip). | |
| if ! children=$(gh pr list --repo "$REPO" --state open --base "$HEAD_REF" \ | |
| --json number --jq 'length'); then | |
| echo "::error::Could not determine stack position (gh pr list failed). Re-run once the API is reachable." | |
| exit 1 | |
| fi | |
| children=${children:-0} | |
| if [ "$children" -gt 0 ]; then | |
| echo "is_tip=false" >> "$GITHUB_OUTPUT" | |
| echo "This PR has $children open PR(s) stacked on top — changes still in flight." | |
| echo "The OpenSpec archive check is skipped until this PR is the tip of the stack." | |
| else | |
| echo "is_tip=true" >> "$GITHUB_OUTPUT" | |
| echo "No PRs are stacked on top — this PR is the tip (or standalone); the archive check will run." | |
| fi | |
| check-openspec-archived: | |
| needs: stack-position | |
| if: needs.stack-position.outputs.is_tip == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Fail if any unarchived OpenSpec change exists | |
| run: | | |
| set -euo pipefail | |
| if [ ! -d openspec/changes ]; then | |
| echo "openspec/changes does not exist — nothing to check." | |
| exit 0 | |
| fi | |
| UNARCHIVED=$(find openspec/changes -mindepth 1 -maxdepth 1 -type d ! -name archive) | |
| if [ -n "$UNARCHIVED" ]; then | |
| echo "::error::Unarchived OpenSpec change directories found under openspec/changes/:" | |
| echo "$UNARCHIVED" | sed 's|^| - |' | |
| echo "" | |
| echo "This PR is the tip of its stack (no open PRs stacked on top), so its OpenSpec change must be archived before merge." | |
| echo "Move it under openspec/changes/archive/ (e.g. /openspec-archive-change <name>) and commit." | |
| exit 1 | |
| fi | |
| echo "No unarchived OpenSpec changes — OK." |