Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/issue-auto-implement/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
.commit_msg
.pr_title
.pr_body
# PR comment body when implement makes no code changes (no-change rationale or request for clarification; workflow reads, then discards)
.comment_body

# Local env (secrets); do not commit
.env
9 changes: 8 additions & 1 deletion .github/actions/issue-auto-implement/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ From the workflow event payload, derive:
- **Issue number:** For `issues` or `issue_comment`: `event.issue.number`. For `pull_request_review` or `pull_request_review_comment`: parse PR body for "Closes #N" or "Fixes #N", or PR head branch for `auto-implement-issue-<N>`.
- **PR exists for issue (issue_comment only):** Check whether an open PR exists for that issue (e.g. head branch `auto-implement-issue-<N>` or body "Closes #<N>").

## Request more info vs comment body from implement

- **Request more info (assess):** The **assess** step decides there is **not enough information** to implement. It returns `action: request_info` and a `comment_body`. The workflow posts that on the issue (or on the PR when the trigger was a PR review/comment), adds the `needs-info` label, and **exits without running implement**.
- **Comment body from implement (no change or need clarification):** The assess step said **implement**. The **implement** step ran (Claude Code CLI with full repo context). Claude **chose not to make code changes** — e.g. the feedback is a question, the current approach is preferred, or it needs clarification. It writes `.comment_body` with the text to post on the PR (one or two sentences). The workflow posts that on the **PR** (review iteration path). Use for: (a) no-change scenarios (thank the reviewer, briefly explain), or (b) when more information is requested (e.g. "Could you clarify whether you want X or Y?").

If a reviewer's comment is ambiguous, assess might still return **implement** (optimistic). Then implement runs; Claude can either make a best-effort change, or write `.comment_body` asking for clarification. That clarification is posted on the PR.

## Assess script

- **Path:** `assess/src/index.ts` (TypeScript), run with `npx tsx src/index.ts` from the assess directory (no build).
Expand All @@ -37,7 +44,7 @@ From the workflow event payload, derive:

- **Path:** `assess/src/implement.ts`, run with `npx tsx src/implement.ts` from the assess directory.
- **Env:** `ISSUE_NUMBER`, `GITHUB_REPOSITORY`, `GITHUB_TOKEN`, `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` (required); `VERIFICATION_NOTES`, `GITHUB_WORKSPACE`, `CONTEXT_FILES`, `IMPLEMENT_COMMIT_MSG_FILE`, `PREVIOUS_VERIFY_OUTPUT` (optional).
- **Flow:** Fetches issue title/body from GitHub API, loads context files, then runs **Claude Code CLI** (`claude` on PATH) in the repo root with a prompt; the script passes `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` to the CLI as `ANTHROPIC_API_KEY`. The CLI implements in-repo (Read/Edit/Bash), then the script ensures commit/PR meta files exist (`.commit_msg`, `.pr_title`, `.pr_body` under the action dir). When `PREVIOUS_VERIFY_OUTPUT` is set (e.g. after a failed verify run), it is included in the prompt so the CLI can fix the implementation. In CI, the action installs the CLI (`npm install -g @anthropic-ai/claude-code`) before the implement step when the assess outcome is `implement`.
- **Flow:** Fetches issue title/body from GitHub API, loads context files, then runs **Claude Code CLI** (`claude` on PATH) in the repo root with a prompt; the script passes `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` to the CLI as `ANTHROPIC_API_KEY`. The CLI implements in-repo (Read/Edit/Bash). When Claude makes code changes it writes `.commit_msg`, `.pr_title`, `.pr_body`; when it makes no code changes it writes `.comment_body` (no-change rationale or request for clarification). The script ensures commit/PR meta files exist only when Claude did not write `.comment_body`. When `PREVIOUS_VERIFY_OUTPUT` is set (e.g. after a failed verify run), it is included in the prompt so the CLI can fix the implementation. In CI, the action installs the CLI (`npm install -g @anthropic-ai/claude-code`) before the implement step when the assess outcome is `implement`.

## Implement–verify loop

Expand Down
21 changes: 17 additions & 4 deletions .github/actions/issue-auto-implement/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,27 +214,30 @@ runs:
COMMIT_MSG_FILE=".github/actions/issue-auto-implement/.commit_msg"
BRANCH="auto-implement-issue-${ISSUE_NUMBER}"
VERIFY_FAILURE=""
CHANGES_PUSHED="false"
for i in $(seq 1 "$MAX"); do
echo "Implement–verify attempt $i of $MAX"
export PREVIOUS_VERIFY_OUTPUT="$VERIFY_FAILURE"
cd .github/actions/issue-auto-implement/assess && npx tsx src/implement.ts
cd "$GITHUB_WORKSPACE"
git add -A
git reset -- "$COMMIT_MSG_FILE" ".github/actions/issue-auto-implement/.pr_title" ".github/actions/issue-auto-implement/.pr_body" 2>/dev/null || true
git reset -- "$COMMIT_MSG_FILE" ".github/actions/issue-auto-implement/.pr_title" ".github/actions/issue-auto-implement/.pr_body" ".github/actions/issue-auto-implement/.comment_body" 2>/dev/null || true
if ! git diff --staged --quiet; then
git commit -F "$COMMIT_MSG_FILE"
rm -f "$COMMIT_MSG_FILE"
git push -u origin "$BRANCH"
CHANGES_PUSHED="true"
else
echo "No changes to commit (attempt $i)."
fi
VERIFY_OUTPUT=$(eval "$VERIFY_CMDS" 2>&1); VERIFY_EXIT=$?
if [ "$VERIFY_EXIT" -eq 0 ]; then
# Pass PR meta to Create PR step via outputs (files are Claude handoff only; do not re-read in next step)
PR_DIR=".github/actions/issue-auto-implement"
if [ -f "$PR_DIR/.pr_title" ]; then echo "pr_title<<EOF" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_title" >> $GITHUB_OUTPUT; echo "EOF" >> $GITHUB_OUTPUT; else echo "pr_title=Implement issue #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi
if [ -f "$PR_DIR/.pr_body" ]; then echo "pr_body<<EOF" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_body" >> $GITHUB_OUTPUT; echo "EOF" >> $GITHUB_OUTPUT; else echo "pr_body=Closes #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi
if [ -f "$PR_DIR/.pr_title" ]; then echo "pr_title<<PR_TITLE_DELIM" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_title" >> $GITHUB_OUTPUT; echo "PR_TITLE_DELIM" >> $GITHUB_OUTPUT; else echo "pr_title=Implement issue #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi
if [ -f "$PR_DIR/.pr_body" ]; then echo "pr_body<<PR_BODY_DELIM" >> $GITHUB_OUTPUT; cat "$PR_DIR/.pr_body" >> $GITHUB_OUTPUT; echo "PR_BODY_DELIM" >> $GITHUB_OUTPUT; else echo "pr_body=Closes #${ISSUE_NUMBER}" >> $GITHUB_OUTPUT; fi
echo "verified=true" >> $GITHUB_OUTPUT
echo "changes_pushed=$CHANGES_PUSHED" >> $GITHUB_OUTPUT
exit 0
fi
VERIFY_FAILURE="$VERIFY_OUTPUT"
Expand Down Expand Up @@ -273,8 +276,18 @@ runs:
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
HEAD_REF: ${{ github.event.pull_request.head.ref || format('auto-implement-issue-{0}', steps.assess.outputs.issue_number) }}
CHANGES_PUSHED: ${{ steps.implement_verify_loop.outputs.changes_pushed }}
run: |
BODY="Addressed review feedback. New commit(s) pushed; verification passed."
if [ "$CHANGES_PUSHED" = "true" ]; then
BODY="Addressed review feedback. New commit(s) pushed; verification passed."
else
COMMENT_BODY_FILE=".github/actions/issue-auto-implement/.comment_body"
if [ -f "$COMMENT_BODY_FILE" ]; then
BODY="$(cat "$COMMENT_BODY_FILE"). Verification passed."
else
BODY="Thanks for the suggestion. We reviewed it and are keeping the current implementation; no code changes were made. Verification passed."
fi
fi
curl -s -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
Expand Down
13 changes: 10 additions & 3 deletions .github/actions/issue-auto-implement/assess/src/implement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const ACTION_DIR = '.github/actions/issue-auto-implement';
const COMMIT_MSG_FILE = process.env.IMPLEMENT_COMMIT_MSG_FILE || resolve(REPO_ROOT, ACTION_DIR + '/.commit_msg');
const PR_TITLE_FILE = resolve(REPO_ROOT, ACTION_DIR + '/.pr_title');
const PR_BODY_FILE = resolve(REPO_ROOT, ACTION_DIR + '/.pr_body');
/** When implement makes no code changes, Claude writes the PR comment body here (no-change rationale or request for clarification). */
const COMMENT_BODY_FILE = resolve(REPO_ROOT, ACTION_DIR + '/.comment_body');

async function fetchIssue(owner: string, repo: string, issueNumber: number, token: string): Promise<{ title: string; body: string }> {
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`;
Expand Down Expand Up @@ -68,12 +70,14 @@ function buildClaudeCliPrompt(
'Rules:',
'- Only change what is necessary to implement the issue. Preserve existing exported symbols and call sites unless the issue explicitly asks to remove or replace them.',
'- Consider the broader codebase—other code may depend on the files you edit; make minimal, targeted edits and keep the public API intact.',
'- When you are done, you MUST write three files (create the directory if needed):',
'- When you MAKE code changes, you MUST write three files (create the directory if needed):',
` 1. ${metaDir}/.commit_msg — one line, conventional commit message (e.g. "fix: correct version comparison for beta").`,
` 2. ${metaDir}/.pr_title — one-line PR title.`,
` 3. ${metaDir}/.pr_body — markdown body: brief problem summary, then "How it was solved" or "Solution". Do NOT include "Closes #N" (it will be appended).`,
` These files are workflow-only inputs (consumed by the action to create the commit and PR). Do NOT add or commit them to the repository.`,
'',
`- When you decide NOT to make any code changes, do NOT write .commit_msg. Instead write the PR comment body to ${metaDir}/.comment_body — one or two sentences. Use this for: (a) no-change scenarios (e.g. the feedback is a question or the current approach is preferred; thank the reviewer and briefly explain), or (b) when more information is needed (e.g. "Could you clarify whether you want X or Y?"). This file will be posted on the PR.`,
'',
'Issue title:',
issueTitle,
'',
Expand All @@ -98,7 +102,7 @@ function buildClaudeCliPrompt(
if (contextBlock) {
parts.push('', contextBlock);
}
parts.push('', `After implementing, write ${metaDir}/.commit_msg, .pr_title, and .pr_body as above.`);
parts.push('', 'After implementing, write the appropriate files: .commit_msg, .pr_title, and .pr_body if you made code changes; or .comment_body if you made no code changes (no-change rationale or request for clarification).');
return parts.join('\n');
}

Expand Down Expand Up @@ -134,10 +138,13 @@ function runClaudeCli(prompt: string): void {
}
}

/** Ensure commit message and PR meta files exist; write defaults if missing. */
/** Ensure commit message and PR meta files exist when Claude made code changes; skip defaults if Claude wrote .comment_body. */
function ensureMetaFiles(issueNumber: number): void {
const metaDir = dirname(COMMIT_MSG_FILE);
if (!existsSync(metaDir)) mkdirSync(metaDir, { recursive: true });
if (existsSync(COMMENT_BODY_FILE)) {
return;
}
if (!existsSync(COMMIT_MSG_FILE)) {
writeFileSync(COMMIT_MSG_FILE, `fix: implement issue #${issueNumber}`, 'utf-8');
}
Expand Down
Loading