-
Notifications
You must be signed in to change notification settings - Fork 3
183 lines (169 loc) · 8.03 KB
/
Copy pathimplementation.yml
File metadata and controls
183 lines (169 loc) · 8.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
---
name: Implementation (Oz)
run-name: "Implementation (Oz) — Issue #${{ github.event.issue.number || inputs.issue_number }}"
on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to implement"
required: true
type: string
concurrency:
group: oz-impl-${{ github.event.issue.number || inputs.issue_number }}
cancel-in-progress: false
permissions:
contents: write
issues: write
pull-requests: write
jobs:
implement:
name: "Implement spec for Issue #${{ github.event.issue.number || inputs.issue_number }}"
runs-on: ubuntu-latest
# For labeled events, only trigger on the spec-approved label, skip if
# skip-oz is set, and require a trusted issue author. workflow_dispatch
# is subject to the same checks at runtime via the eligibility step below.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.label.name == 'spec-approved' &&
!contains(github.event.issue.labels.*.name, 'skip-oz') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'),
github.event.issue.author_association))
steps:
- name: OpenTofu - Setup OpenTofu
uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2.0.2
# Credentials are intentionally persisted so the Oz agent can push the
# implementation branch back to origin. zizmor's artipacked warning is
# acknowledged.
- name: Checkout # zizmor: ignore[artipacked]
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Guard - require WARP_API_KEY
env:
WARP_API_KEY: ${{ secrets.WARP_API_KEY }} # zizmor: ignore[secrets-outside-env]
run: |
if [ -z "${WARP_API_KEY}" ]; then
echo "::error::WARP_API_KEY secret is not configured for this repository."
exit 1
fi
- name: Resolve and validate issue number
id: ctx
env:
ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issue_number }}
run: |
# Reject anything that is not a bare positive integer (>= 1) to
# prevent GITHUB_OUTPUT injection (e.g. newlines, '=') when the
# value comes from a free-form workflow_dispatch input. GitHub
# issue numbers start at 1, so disallow 0 explicitly.
if ! printf '%s' "${ISSUE_NUMBER}" | grep -Eq '^[1-9][0-9]*$'; then
echo "::error::issue_number must be a positive integer (>= 1); got: ${ISSUE_NUMBER}"
exit 1
fi
echo "issue_number=${ISSUE_NUMBER}" >> "${GITHUB_OUTPUT}"
- name: Verify issue eligibility on manual dispatch
# The job-level `if` only enforces skip-oz and author_association for
# the `labeled` trigger because `github.event.issue` is unavailable
# for workflow_dispatch. Re-check both on the target issue here so
# manual dispatch respects the documented skip-oz override and the
# same trust gate that protects the labeled path.
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.ctx.outputs.issue_number }}
run: |
# gh issue view does not expose authorAssociation; use the REST
# API directly so we can read .author_association (snake_case).
issue=$(gh api "/repos/${{ github.repository }}/issues/${ISSUE_NUMBER}")
labels=$(printf '%s' "${issue}" | jq -r '.labels[].name')
assoc=$(printf '%s' "${issue}" | jq -r '.author_association')
if printf '%s\n' "${labels}" | grep -Fxq 'skip-oz'; then
echo "::notice::Issue #${ISSUE_NUMBER} has the skip-oz label; aborting per AGENTS.md."
exit 1
fi
case "${assoc}" in
OWNER|MEMBER|COLLABORATOR) ;;
*)
echo "::error::Issue #${ISSUE_NUMBER} author_association=${assoc} is not in {OWNER,MEMBER,COLLABORATOR}; refusing to run the Oz agent on untrusted issue content."
exit 1
;;
esac
- name: Locate spec file
id: spec
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.ctx.outputs.issue_number }}
run: |
set -e
shopt -s nullglob
matches=( .github/specs/issue-"${ISSUE_NUMBER}"-*.md )
if [ ${#matches[@]} -eq 0 ]; then
echo "::error::No spec file found at .github/specs/issue-${ISSUE_NUMBER}-*.md"
exit 1
fi
if [ ${#matches[@]} -gt 1 ]; then
echo "::warning::Multiple spec files found; using ${matches[0]}"
fi
echo "path=${matches[0]}" >> "${GITHUB_OUTPUT}"
- name: Mark issue implementation-in-progress
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.ctx.outputs.issue_number }}
run: |
gh issue edit "${ISSUE_NUMBER}" \
--repo "${{ github.repository }}" \
--add-label implementation-in-progress
- name: Run Oz implementation agent
uses: warpdotdev/oz-agent-action@8ba4e142060cca997ddf354d8c40da7511110510 # v1.0.23
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
warp_api_key: ${{ secrets.WARP_API_KEY }} # zizmor: ignore[secrets-outside-env]
profile: ${{ vars.WARP_AGENT_PROFILE || '' }}
# Canonical implementation instructions live in
# .warp/skills/spec-implementation/SKILL.md and are referenced via `skill:`.
# Pass only per-run context through `prompt:` below.
skill: zachreborn/terraform-modules:spec-implementation
prompt: |
## Run context
Issue number: #${{ steps.ctx.outputs.issue_number }}
Repository: ${{ github.repository }}
Spec file path (already on `main`): ${{ steps.spec.outputs.path }}
- name: Mark implementation PR ready-for-review
# The Oz agent action tends to open PRs as drafts when invoked from
# automation, which suppresses GitHub's CODEOWNERS auto-request.
# Find the freshly-created draft implementation PR (branch prefix
# `feat/issue-N-` or `fix/issue-N-`, isDraft=true,
# most-recently-created) and flip it to ready-for-review.
# Filtering on `isDraft == true` ensures we never re-flip a stale
# ready PR; sorting by `createdAt` ensures we target the current
# run's PR even if PR numbers raced.
if: success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.ctx.outputs.issue_number }}
run: |
pr_number=$(gh pr list \
--repo "${{ github.repository }}" \
--state open \
--json number,headRefName,isDraft,createdAt \
--jq "[.[] | select(.isDraft == true and ((.headRefName | startswith(\"feat/issue-${ISSUE_NUMBER}-\")) or (.headRefName | startswith(\"fix/issue-${ISSUE_NUMBER}-\"))))] | sort_by(.createdAt) | last | .number // empty")
if [ -z "${pr_number}" ]; then
echo "::notice::No open *draft* implementation PR matching 'feat/issue-${ISSUE_NUMBER}-*' or 'fix/issue-${ISSUE_NUMBER}-*' found; nothing to flip."
exit 0
fi
echo "::notice::Marking implementation PR #${pr_number} ready-for-review."
gh pr ready "${pr_number}" --repo "${{ github.repository }}" || true
- name: On failure - drop implementation-in-progress
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.ctx.outputs.issue_number }}
run: |
gh issue edit "${ISSUE_NUMBER}" \
--repo "${{ github.repository }}" \
--remove-label implementation-in-progress || true
gh issue comment "${ISSUE_NUMBER}" \
--repo "${{ github.repository }}" \
--body "Implementation run failed. See: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"