diff --git a/.github/actions/issue-auto-implement/.gitignore b/.github/actions/issue-auto-implement/.gitignore index 03a41ca..ca66210 100644 --- a/.github/actions/issue-auto-implement/.gitignore +++ b/.github/actions/issue-auto-implement/.gitignore @@ -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 diff --git a/.github/actions/issue-auto-implement/AGENTS.md b/.github/actions/issue-auto-implement/AGENTS.md index e5bfdcd..852c629 100644 --- a/.github/actions/issue-auto-implement/AGENTS.md +++ b/.github/actions/issue-auto-implement/AGENTS.md @@ -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-`. - **PR exists for issue (issue_comment only):** Check whether an open PR exists for that issue (e.g. head branch `auto-implement-issue-` or body "Closes #"). +## 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). @@ -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 diff --git a/.github/actions/issue-auto-implement/action.yml b/.github/actions/issue-auto-implement/action.yml index 80d3790..6f0f04c 100644 --- a/.github/actions/issue-auto-implement/action.yml +++ b/.github/actions/issue-auto-implement/action.yml @@ -214,17 +214,19 @@ 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 @@ -232,9 +234,10 @@ runs: 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<> $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<> $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<> $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<> $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" @@ -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" \ diff --git a/.github/actions/issue-auto-implement/assess/src/implement.ts b/.github/actions/issue-auto-implement/assess/src/implement.ts index cabf036..33602e9 100644 --- a/.github/actions/issue-auto-implement/assess/src/implement.ts +++ b/.github/actions/issue-auto-implement/assess/src/implement.ts @@ -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}`; @@ -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, '', @@ -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'); } @@ -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'); }