@@ -73,153 +73,146 @@ jobs:
7373 if : steps.guard.outputs.skip != 'true'
7474 uses : ./.github/actions/setup
7575
76- - name : Install Claude Code
76+ - name : ' Phase 1 — Analyze & Write Failing Test (Opus)'
77+ id : phase1
7778 if : steps.guard.outputs.skip != 'true'
78- run : pnpm add -g --ignore-scripts=false @anthropic-ai/claude-code
79+ continue-on-error : true
80+ uses : anthropics/claude-code-action@5d5c10a4f389689f992ea10bb14dcb6fcc83146d # v1
81+ with :
82+ anthropic_api_key : ${{ secrets.ANTHROPIC_API_KEY }}
83+ prompt : |
84+ Read .claude/skills/bug-fix-tdd/SKILL.md and follow it.
85+ Read issue-body.md for the bug report.
86+
87+ Your task (Phase 1 — Analysis & Test):
88+ 1. Analyze the bug report: understand description, steps to reproduce, expected vs actual behavior.
89+ 2. Search the codebase to find the relevant source code.
90+ 3. Write a unit test that reproduces the bug — it MUST FAIL when you run it.
91+ 4. Run the test with: pnpm run test:nonInteractive -- <test-file-path>
92+ 5. Verify the test fails FOR THE RIGHT REASON (not import errors or unrelated failures).
93+ 6. If the test passes (bug not reproduced), try a different approach (max 3 attempts).
94+
95+ Output:
96+ - The test file in the correct __tests__/ directory.
97+ - bug-analysis.md with your findings (follow the format in the skill).
98+
99+ Do NOT modify any source files. Only create/edit test files and bug-analysis.md.
100+ claude_args : >-
101+ --model opus
102+ --max-turns 50
103+ --allowedTools "Read,Grep,Glob,Edit,Write,Bash(pnpm run test:nonInteractive *),Bash(cat *),Bash(ls *)"
104+
105+ - name : ' Hard gate — Verify test fails'
106+ id : gate
107+ if : steps.guard.outputs.skip != 'true' && steps.phase1.outcome == 'success'
108+ run : |
109+ if [ ! -f bug-analysis.md ]; then
110+ echo "::warning::bug-analysis.md not found"
111+ echo "test_fails=false" >> $GITHUB_OUTPUT
112+ exit 0
113+ fi
79114
80- - name : ' Bug Fix (up to 3 attempts)'
81- id : tdd
82- if : steps.guard.outputs.skip != 'true'
115+ TEST_FILE=$(grep "^Test file:" bug-analysis.md | sed 's/^Test file: //' || true)
116+ if [ -z "$TEST_FILE" ]; then
117+ echo "::warning::No 'Test file:' line found in bug-analysis.md"
118+ echo "test_fails=false" >> $GITHUB_OUTPUT
119+ exit 0
120+ fi
121+
122+ echo "test_file=$TEST_FILE" >> $GITHUB_OUTPUT
123+
124+ if pnpm run test:nonInteractive -- "$TEST_FILE" 2>&1; then
125+ echo "::warning::Test passed (bug not reproduced)"
126+ echo "test_fails=false" >> $GITHUB_OUTPUT
127+ else
128+ echo "::notice::Test fails as expected"
129+ echo "test_fails=true" >> $GITHUB_OUTPUT
130+ fi
131+
132+ - name : ' Phase 2 — TDD Fix (Sonnet)'
133+ id : phase2
134+ if : >-
135+ steps.guard.outputs.skip != 'true'
136+ && steps.gate.outputs.test_fails == 'true'
137+ continue-on-error : true
138+ uses : anthropics/claude-code-action@5d5c10a4f389689f992ea10bb14dcb6fcc83146d # v1
139+ env :
140+ BUG_TEST_FILE : ${{ steps.gate.outputs.test_file }}
141+ BUG_ISSUE_NUMBER : ${{ inputs.issue-number }}
142+ with :
143+ anthropic_api_key : ${{ secrets.ANTHROPIC_API_KEY }}
144+ prompt : |
145+ Read .claude/skills/bug-fix-tdd/SKILL.md and bug-analysis.md.
146+
147+ Your task (Phase 2 — Fix):
148+ 1. Read the failing test and understand what it expects.
149+ 2. Apply the MINIMUM fix to make the test pass.
150+ 3. Run the test to verify it passes: pnpm run test:nonInteractive -- ${{ steps.gate.outputs.test_file }}
151+ 4. Run the full test suite: pnpm run test:nonInteractive
152+ 5. Run pnpm run lint and pnpm run type-check.
153+ 6. If any check fails, adjust the fix (max 5 attempts).
154+ 7. Write pr-body.md (include 'Fixes #${{ inputs.issue-number }}') and fix-title.txt.
155+
156+ Do NOT run git, gh, or modify .env files.
157+ Do NOT over-engineer — apply the smallest change that fixes the bug.
158+ claude_args : >-
159+ --model sonnet
160+ --max-turns 150
161+ --allowedTools "Read,Grep,Glob,Edit,Write,Bash(pnpm run test:nonInteractive *),Bash(pnpm run lint),Bash(pnpm run type-check),Bash(cat *),Bash(ls *)"
162+
163+ - name : ' Phase 2b — Direct Fix without TDD (Sonnet)'
164+ id : phase2b
165+ if : >-
166+ steps.guard.outputs.skip != 'true'
167+ && steps.phase1.outcome == 'success'
168+ && steps.gate.outputs.test_fails != 'true'
83169 continue-on-error : true
170+ uses : anthropics/claude-code-action@5d5c10a4f389689f992ea10bb14dcb6fcc83146d # v1
84171 env :
85- ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
86172 BUG_ISSUE_NUMBER : ${{ inputs.issue-number }}
173+ with :
174+ anthropic_api_key : ${{ secrets.ANTHROPIC_API_KEY }}
175+ prompt : |
176+ Read .claude/skills/bug-fix-tdd/SKILL.md and bug-analysis.md.
177+ Read issue-body.md for the original bug report.
178+
179+ The bug could NOT be reproduced in a unit test.
180+ bug-analysis.md contains the analysis of the bug from Phase 1.
181+
182+ Your task (Phase 2b — Direct Fix):
183+ 1. Read the analysis and understand the root cause.
184+ 2. Apply the MINIMUM fix to resolve the bug.
185+ 3. If you CAN write a regression test, do so — but it is not required.
186+ 4. Run the full test suite: pnpm run test:nonInteractive
187+ 5. Run pnpm run lint and pnpm run type-check.
188+ 6. If any check fails, adjust the fix (max 5 attempts).
189+ 7. Write pr-body.md (include 'Fixes #${{ inputs.issue-number }}').
190+ Note in the PR body that no regression test was possible.
191+ 8. Write fix-title.txt.
192+
193+ Do NOT run git, gh, or modify .env files.
194+ Do NOT over-engineer — apply the smallest change that fixes the bug.
195+ claude_args : >-
196+ --model sonnet
197+ --max-turns 150
198+ --allowedTools "Read,Grep,Glob,Edit,Write,Bash(pnpm run test:nonInteractive *),Bash(pnpm run lint),Bash(pnpm run type-check),Bash(cat *),Bash(ls *)"
199+
200+ - name : ' Hard gate — Verify all checks pass'
201+ id : verify
202+ if : >-
203+ steps.guard.outputs.skip != 'true'
204+ && (steps.phase2.outcome == 'success' || steps.phase2b.outcome == 'success')
87205 run : |
88- MAX_ATTEMPTS=3
89- SUCCESS=false
90-
91- for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
92- echo "::group::Attempt $ATTEMPT of $MAX_ATTEMPTS"
93-
94- # Clean up previous attempt artifacts
95- rm -f bug-analysis.md pr-body.md fix-title.txt
96- git checkout -- . 2>/dev/null || true
97- git clean -fd 2>/dev/null || true
98-
99- # --- Phase 1: Analyze & Write Failing Test (Opus) ---
100- echo "::notice::Phase 1 — Writing failing test (attempt $ATTEMPT)"
101- if ! claude -p --model opus \
102- --dangerously-skip-permissions \
103- --allowedTools "Read,Grep,Glob,Edit,Write,Bash(pnpm run test:nonInteractive *),Bash(cat *),Bash(ls *)" \
104- --max-turns 50 \
105- "Read .claude/skills/bug-fix-tdd/SKILL.md and follow it.
106- Read issue-body.md for the bug report.
107-
108- Your task (Phase 1 — Analysis & Test):
109- 1. Analyze the bug report: understand description, steps to reproduce, expected vs actual behavior.
110- 2. Search the codebase to find the relevant source code.
111- 3. Write a unit test that reproduces the bug — it MUST FAIL when you run it.
112- 4. Run the test with: pnpm run test:nonInteractive -- <test-file-path>
113- 5. Verify the test fails FOR THE RIGHT REASON (not import errors or unrelated failures).
114- 6. If the test passes (bug not reproduced), try a different approach (max 3 attempts).
115-
116- Output:
117- - The test file in the correct __tests__/ directory.
118- - bug-analysis.md with your findings (follow the format in the skill).
119-
120- Do NOT modify any source files. Only create/edit test files and bug-analysis.md."; then
121- echo "::warning::Phase 1 failed on attempt $ATTEMPT"
122- echo "::endgroup::"
123- continue
124- fi
125-
126- # --- Hard gate: verify test fails ---
127- if [ ! -f bug-analysis.md ]; then
128- echo "::warning::bug-analysis.md not found — skipping attempt $ATTEMPT"
129- echo "::endgroup::"
130- continue
131- fi
132-
133- TEST_FILE=$(grep "^Test file:" bug-analysis.md | sed 's/^Test file: //' || true)
134- if [ -z "$TEST_FILE" ]; then
135- echo "::warning::No 'Test file:' line found in bug-analysis.md — skipping attempt $ATTEMPT"
136- echo "::endgroup::"
137- continue
138- fi
139-
140- if pnpm run test:nonInteractive -- "$TEST_FILE" 2>&1; then
141- echo "::warning::Test passed (bug not reproduced) — trying direct fix"
142-
143- # --- Phase 2b: Direct fix without regression test ---
144- echo "::notice::Phase 2b — Direct fix without TDD (attempt $ATTEMPT)"
145- if ! claude -p --model sonnet \
146- --dangerously-skip-permissions \
147- --allowedTools "Read,Grep,Glob,Edit,Write,Bash(pnpm run test:nonInteractive *),Bash(pnpm run lint),Bash(pnpm run type-check),Bash(cat *),Bash(ls *)" \
148- --max-turns 150 \
149- "Read .claude/skills/bug-fix-tdd/SKILL.md and bug-analysis.md.
150- Read issue-body.md for the original bug report.
151-
152- The bug could NOT be reproduced in a unit test.
153- bug-analysis.md contains the analysis of the bug from Phase 1.
154-
155- Your task (Phase 2b — Direct Fix):
156- 1. Read the analysis and understand the root cause.
157- 2. Apply the MINIMUM fix to resolve the bug.
158- 3. If you CAN write a regression test, do so — but it is not required.
159- 4. Run the full test suite: pnpm run test:nonInteractive
160- 5. Run pnpm run lint and pnpm run type-check.
161- 6. If any check fails, adjust the fix (max 5 attempts).
162- 7. Write pr-body.md (include 'Fixes #$BUG_ISSUE_NUMBER').
163- Note in the PR body that no regression test was possible.
164- 8. Write fix-title.txt.
165-
166- Do NOT run git, gh, or modify .env files.
167- Do NOT over-engineer — apply the smallest change that fixes the bug."; then
168- echo "::warning::Phase 2b failed on attempt $ATTEMPT"
169- echo "::endgroup::"
170- continue
171- fi
172- else
173- echo "::notice::Test fails as expected — proceeding to Phase 2 (TDD)"
174-
175- # --- Phase 2: Implement Fix with TDD (Sonnet) ---
176- export BUG_TEST_FILE="$TEST_FILE"
177- echo "::notice::Phase 2 — Implementing fix (attempt $ATTEMPT)"
178- if ! claude -p --model sonnet \
179- --dangerously-skip-permissions \
180- --allowedTools "Read,Grep,Glob,Edit,Write,Bash(pnpm run test:nonInteractive *),Bash(pnpm run lint),Bash(pnpm run type-check),Bash(cat *),Bash(ls *)" \
181- --max-turns 150 \
182- "Read .claude/skills/bug-fix-tdd/SKILL.md and bug-analysis.md.
183-
184- Your task (Phase 2 — Fix):
185- 1. Read the failing test and understand what it expects.
186- 2. Apply the MINIMUM fix to make the test pass.
187- 3. Run the test to verify it passes: pnpm run test:nonInteractive -- $BUG_TEST_FILE
188- 4. Run the full test suite: pnpm run test:nonInteractive
189- 5. Run pnpm run lint and pnpm run type-check.
190- 6. If any check fails, adjust the fix (max 5 attempts).
191- 7. Write pr-body.md (include 'Fixes #$BUG_ISSUE_NUMBER') and fix-title.txt.
192-
193- Do NOT run git, gh, or modify .env files.
194- Do NOT over-engineer — apply the smallest change that fixes the bug."; then
195- echo "::warning::Phase 2 failed on attempt $ATTEMPT"
196- echo "::endgroup::"
197- continue
198- fi
199- fi
200-
201- # --- Hard gate: verify all checks pass ---
202- if pnpm run test:nonInteractive && pnpm run lint && pnpm run type-check; then
203- echo "::notice::All checks pass on attempt $ATTEMPT"
204- SUCCESS=true
205- echo "::endgroup::"
206- break
207- else
208- echo "::warning::Verification failed on attempt $ATTEMPT"
209- echo "::endgroup::"
210- continue
211- fi
212- done
213-
214- if [ "$SUCCESS" = "true" ]; then
206+ if pnpm run test:nonInteractive && pnpm run lint && pnpm run type-check; then
207+ echo "::notice::All checks pass"
215208 echo "result=success" >> $GITHUB_OUTPUT
216- echo "test_file=$TEST_FILE" >> $GITHUB_OUTPUT
217209 else
210+ echo "::warning::Verification failed"
218211 echo "result=failure" >> $GITHUB_OUTPUT
219212 fi
220213
221214 - name : Create branch and commit
222- if : steps.guard.outputs.skip != 'true' && steps.tdd .outputs.result == 'success'
215+ if : steps.guard.outputs.skip != 'true' && steps.verify .outputs.result == 'success'
223216 id : push
224217 run : |
225218 ISSUE_NUM="${{ inputs.issue-number }}"
@@ -244,7 +237,7 @@ jobs:
244237 fi
245238
246239 - name : Create Pull Request
247- if : steps.guard.outputs.skip != 'true' && steps.tdd .outputs.result == 'success' && steps.push.outputs.has_changes == 'true'
240+ if : steps.guard.outputs.skip != 'true' && steps.verify .outputs.result == 'success' && steps.push.outputs.has_changes == 'true'
248241 env :
249242 GH_TOKEN : ${{ steps.app-token.outputs.token }}
250243 run : |
@@ -268,7 +261,8 @@ jobs:
268261 if : >-
269262 always()
270263 && steps.guard.outputs.skip != 'true'
271- && steps.tdd.outputs.result == 'failure'
264+ && steps.verify.outputs.result != 'success'
265+ && steps.phase1.outcome != 'skipped'
272266 env :
273267 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
274268 run : |
@@ -278,7 +272,7 @@ jobs:
278272 ANALYSIS="The agent could not analyze this bug. The issue may be too vague or involve behavior that cannot be captured in a unit test."
279273 fi
280274
281- printf '## Bug Fix Agent — Automated Analysis\n\n%s\n\n---\n*Automated analysis by Claude Code Bug Fix Agent. All 3 attempts exhausted. A developer will review this issue manually.*\n' \
275+ printf '## Bug Fix Agent — Automated Analysis\n\n%s\n\n---\n*Automated analysis by Claude Code Bug Fix Agent. A developer will review this issue manually.*\n' \
282276 "$ANALYSIS" > /tmp/comment-body.md
283277
284278 gh issue comment "${{ inputs.issue-number }}" --body-file /tmp/comment-body.md
0 commit comments