-
Notifications
You must be signed in to change notification settings - Fork 0
ci(ai-sdlc): run the spec guards on PRs, not only inside the agent #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
edb43ec
ci(ai-sdlc): run the spec guards on PRs, not only inside the agent
coder-abt ea1b699
ci(ai-sdlc): address review findings on the PR spec-guard workflow
coder-abt 53b8547
ci(ai-sdlc): pin Node.js version to match the rest of CI
coder-abt 4c3b9db
Merge branch 'main' into ci/ai-sdlc-guards-on-prs
alex-budanov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| name: AI-SDLC Guards | ||
|
|
||
| # The scope and ownership guards used to run ONLY inside spec-agent.yml and | ||
| # impl-agent.yml, which meant they guarded the machine path and not the change. | ||
| # ci.yml runs `node --test scripts/ai-sdlc/*.test.mjs` - it tests the guards, it | ||
| # never invokes them. Consequences, all real: | ||
| # | ||
| # - the spec ratification PR (Gate 1) did not re-run ownership on the version a | ||
| # human edits and merges; | ||
| # - PR #283, the delivery for issue #269, was written by hand and therefore | ||
| # passed through none of them, as did 13bf6b3 and c0af5ec. | ||
| # | ||
| # The guardrails were attached to the machine path; the delivery went down the | ||
| # human path. This puts the spec-side guards on the PR, where the change is. | ||
| # | ||
| # Scope, stated honestly: spec-side only. The impl-side equivalent needs a | ||
| # PR-diff-level comparison that check-impl-scope.mjs explicitly does not attempt | ||
| # (see its own header) plus spec resolution from the linked issue. That is new | ||
| # logic rather than a relocation, and it is filed separately. | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
| paths: | ||
| - 'docs/specs/**' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ai-sdlc-guards-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| spec-guards: | ||
| name: Spec guards | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - name: Checkout PR merge result | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22' | ||
|
|
||
| - name: Collect spec files changed by this PR | ||
| id: specs | ||
| env: | ||
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Added, modified, or renamed only. A deleted spec has nothing to | ||
| # verify, and feeding a deleted path to the guards would fail on the | ||
| # read rather than on the rule, which is a misleading way to go red. | ||
| # R is included because a rename that also edits content (e.g. a | ||
| # spec renumbered alongside a wording fix) would otherwise be | ||
| # invisible to AM and skip both guards silently. | ||
| files=$(git diff --name-only --diff-filter=AMR "$BASE_SHA" "$HEAD_SHA" -- 'docs/specs/*.md') | ||
| if [ -z "$files" ]; then | ||
| echo "count=0" >> "$GITHUB_OUTPUT" | ||
| echo "No added, modified, or renamed spec files; nothing to check." | ||
| exit 0 | ||
| fi | ||
| echo "count=$(printf '%s\n' "$files" | wc -l)" >> "$GITHUB_OUTPUT" | ||
| # Multi-line value needs a heredoc delimiter, or the runner rejects | ||
| # the whole step. | ||
| { | ||
| echo 'files<<SPECS_EOF' | ||
| printf '%s\n' "$files" | ||
| echo 'SPECS_EOF' | ||
| } >> "$GITHUB_OUTPUT" | ||
| printf '%s\n' "$files" | sed 's/^/ /' | ||
|
|
||
| - name: Verify spec path ownership (deterministic, hard) | ||
| if: steps.specs.outputs.count != '0' | ||
| env: | ||
| SPECS: ${{ steps.specs.outputs.files }} | ||
| run: | | ||
| set -euo pipefail | ||
| failed=0 | ||
| while IFS= read -r spec; do | ||
| [ -n "$spec" ] || continue | ||
| echo "::group::verify-spec-ownership $spec" | ||
| # Run every spec before failing, so one PR touching two specs | ||
| # reports both rather than only the first. | ||
| SPEC_FILE="$spec" node scripts/ai-sdlc/verify-spec-ownership.mjs || failed=1 | ||
| echo "::endgroup::" | ||
| done <<< "$SPECS" | ||
| [ "$failed" -eq 0 ] || { | ||
| echo "::error::Spec path ownership check failed. A spec declares a new path under a component name that docs/adr/README.md attributes to a different repo. See the group log above for the specific path(s)." | ||
| exit 1 | ||
| } | ||
|
|
||
| - name: Check spec scope (advisory) | ||
| if: ${{ !cancelled() && steps.specs.outputs.count != '0' }} | ||
| env: | ||
| SPECS: ${{ steps.specs.outputs.files }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Advisory by design - the cap is uncalibrated. It runs here rather | ||
| # than in the agent job specifically so its output lands on the PR the | ||
| # reviewer is reading, instead of in a step summary nobody opens. | ||
| while IFS= read -r spec; do | ||
| [ -n "$spec" ] || continue | ||
| echo "::group::check-spec-scope $spec" | ||
| if ! SPEC_FILE="$spec" node scripts/ai-sdlc/check-spec-scope.mjs; then | ||
| echo "::warning::check-spec-scope crashed for $spec instead of reporting advisory output - this is a launch/script failure, not a scope finding" | ||
| fi | ||
| echo "::endgroup::" | ||
| done <<< "$SPECS" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.