From 03eb0935093f49c8d778d1868f5a8e47b7c3a9a7 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Tue, 14 Apr 2026 15:29:26 -0400 Subject: [PATCH] jira-agent: add commit restructure phase and improve PR failure handling Add Phase 4 (Restructure Commits) which reorganizes branch commits into component-based groups (API, Vendor, CLI, HO, CPO, E2E, Docs) using the hypershift restructure-commits command. Renumber PR creation to Phase 5 with a GitHub App token refresh before it. The new phase has full token tracking and appears in the HTML cost report with duration, tokens, cost, text output, tool calls, and errors. Also treat PR creation failure as an issue failure so the issue is retried on the next run instead of being marked as successfully processed. Co-Authored-By: Claude Opus 4.6 --- .../hypershift/jira-agent/README.md | 15 +- .../hypershift-jira-agent-process-commands.sh | 225 +++++++++++++----- .../hypershift-jira-agent-process-ref.yaml | 7 +- .../hypershift-jira-agent-report-commands.sh | 16 +- 4 files changed, 191 insertions(+), 72 deletions(-) diff --git a/ci-operator/step-registry/hypershift/jira-agent/README.md b/ci-operator/step-registry/hypershift/jira-agent/README.md index 922e253a27a83..6bf38971893cb 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/README.md +++ b/ci-operator/step-registry/hypershift/jira-agent/README.md @@ -7,7 +7,12 @@ Automated periodic job that processes Jira issues labeled with `issue-for-agent` This workflow implements a fully automated system for processing HyperShift Jira issues: 1. **Query**: Searches Jira for unresolved issues in OCPBUGS and CNTRLPLANE projects with label `issue-for-agent` (excluding those with `agent-processed`) -2. **Process**: For each issue, runs the `/jira-solve` command from the HyperShift repository non-interactively +2. **Process**: For each issue, runs a five-phase pipeline: + - Phase 1: Solve (implement changes via `/jira-solve`) + - Phase 2: Pre-commit quality review + - Phase 3: Address review findings + - Phase 4: Restructure commits into component groups + - Phase 5: Create pull request 3. **Track**: Adds `agent-processed` label to successfully processed issues to prevent reprocessing ## Data Flow Diagram @@ -107,8 +112,12 @@ flowchart TD - Copies jira-solve command to `.claude/commands/` - Configures git and generates GitHub App tokens (JWT auth) - Queries Jira API for labeled issues (excluding those with `agent-processed`) -- Runs jira-solve for each issue using Claude Code CLI with `--system-prompt` -- Pushes branches to fork, creates PRs to upstream openshift/hypershift +- For each issue, runs a five-phase pipeline: + 1. **Solve**: Runs jira-solve to implement changes and push the branch + 2. **Review**: Pre-commit code quality review (read-only) + 3. **Fix**: Addresses review findings by editing code, committing, and pushing + 4. **Restructure**: Reorganizes commits into component-based groups (API, Vendor, CLI, HO, CPO, E2E, Docs) + 5. **PR**: Creates a pull request to upstream openshift/hypershift - Implements rate limiting (60s between issues) - Adds `agent-processed` label to successfully processed issues diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh index de45e94cb6dbc..7fb3b047493a0 100755 --- a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh +++ b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh @@ -309,6 +309,7 @@ while IFS= read -r line; do BRANCH_NAME=$(git branch --show-current) HAS_CODE_CHANGES=false PR_URL="" + ISSUE_FAILED=false if [ "$BRANCH_NAME" != "main" ] && [ "$BRANCH_NAME" != "master" ] && [ -n "$BRANCH_NAME" ]; then DIFF_FILES=$(git diff main...HEAD --name-only 2>/dev/null || echo "") @@ -453,10 +454,10 @@ IMPORTANT: echo "Phase 3 duration: ${PHASE3_DURATION}s" echo "$PHASE3_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-fix-duration.txt" - # Regenerate GitHub App tokens before push/PR operations. + # Regenerate GitHub App tokens before restructure/PR operations. # Installation tokens expire after 1 hour, and phases 1-3 can - # easily exceed that. Refreshing here ensures push and PR - # creation use a valid token. + # easily exceed that. Refreshing here ensures subsequent phases + # use a valid token. echo "Refreshing GitHub App tokens before push/PR..." GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then @@ -474,14 +475,106 @@ IMPORTANT: echo "Upstream token refreshed" fi - # === Phase 4: Create Pull Request === + # === Phase 4: Restructure Commits === echo "" echo "==========================================" - echo "Phase 4: Creating Pull Request for $ISSUE_KEY" + echo "Phase 4: Restructure Commits for $ISSUE_KEY" echo "==========================================" PHASE4_START=$(date +%s) + # Load the restructure-commits command from the hypershift repo + RESTRUCTURE_CMD_FILE="/tmp/hypershift/.claude/commands/restructure-commits.md" + if [ -f "$RESTRUCTURE_CMD_FILE" ]; then + RESTRUCTURE_CONTENT=$(cat "$RESTRUCTURE_CMD_FILE") + + RESTRUCTURE_PROMPT="Restructure the commits on the current branch following the instructions below. The base branch is 'main'. + +After restructuring: +- Verify git status is clean +- Force push to origin with --force-with-lease (do NOT ask for confirmation — this is an automated CI pipeline) + +IMPORTANT: +- SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v' or 'git remote get-url origin'. +- ${SUBAGENT_PROMPT} + +RESTRUCTURE INSTRUCTIONS: +${RESTRUCTURE_CONTENT}" + + set +e + claude -p "$RESTRUCTURE_PROMPT" \ + --allowedTools "Bash Read Write Edit Grep Glob" \ + --max-turns 50 \ + --model "$CLAUDE_MODEL" \ + --verbose \ + --output-format stream-json \ + 2> "/tmp/claude-${ISSUE_KEY}-restructure.log" \ + | tee "/tmp/claude-${ISSUE_KEY}-restructure.json" + RESTRUCTURE_EXIT_CODE=$? + set -e + + jq -j 'select(.type == "assistant") | .message.content[]? | select(.type == "text") | .text // empty' "/tmp/claude-${ISSUE_KEY}-restructure.json" > "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-text.txt" 2>/dev/null || true + jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | "\(.name): \(.input | keys | join(", "))"' "/tmp/claude-${ISSUE_KEY}-restructure.json" 2>/dev/null | sort | uniq -c | sort -rn > "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-tools.txt" 2>/dev/null || true + jq -r 'select(.type == "user") | .tool_use_result | select(type == "string") | select(startswith("Error:")) | gsub("\n"; "⏎")' "/tmp/claude-${ISSUE_KEY}-restructure.json" 2>/dev/null | sort | uniq -c | sort -rn | sed 's/⏎/\n/g' > "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-errors.txt" 2>/dev/null || true + # Extract token usage for Phase 4 + grep '"type":"result"' "/tmp/claude-${ISSUE_KEY}-restructure.json" \ + | head -1 \ + | jq '{ + total_cost_usd: (.total_cost_usd // 0), + duration_ms: (.duration_ms // 0), + num_turns: (.num_turns // 0), + input_tokens: (.usage.input_tokens // 0), + output_tokens: (.usage.output_tokens // 0), + cache_read_input_tokens: (.usage.cache_read_input_tokens // 0), + cache_creation_input_tokens: (.usage.cache_creation_input_tokens // 0), + model_usage: (.modelUsage // {}), + model: ((.modelUsage // {} | keys | first) // "unknown") + }' > "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-tokens.json" 2>/dev/null \ + || echo '{"total_cost_usd":0,"duration_ms":0,"num_turns":0,"input_tokens":0,"output_tokens":0,"cache_read_input_tokens":0,"cache_creation_input_tokens":0,"model_usage":{},"model":"unknown"}' > "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-tokens.json" + echo "Phase 4 tokens: $(cat "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-tokens.json")" + + if [ $RESTRUCTURE_EXIT_CODE -eq 0 ]; then + echo "✅ Phase 4 (restructure commits) completed for $ISSUE_KEY" + else + echo "⚠️ Phase 4 (restructure commits) failed (exit code: $RESTRUCTURE_EXIT_CODE)" + echo "Continuing with PR creation..." + fi + else + echo "Skipping Phase 4: restructure-commits command not found at $RESTRUCTURE_CMD_FILE" + fi + + PHASE4_END=$(date +%s) + PHASE4_DURATION=$((PHASE4_END - PHASE4_START)) + echo "Phase 4 duration: ${PHASE4_DURATION}s" + echo "$PHASE4_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-duration.txt" + + # Refresh GitHub App tokens before PR creation + # Phase 4 may have consumed additional time, causing token expiration + echo "Refreshing GitHub App tokens before PR creation..." + GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") + if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then + echo "ERROR: Failed to refresh GitHub App token for fork" + else + git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f" + echo "Fork token refreshed" + fi + + GITHUB_TOKEN_UPSTREAM=$(generate_github_token "$INSTALLATION_ID_UPSTREAM") + if [ -z "$GITHUB_TOKEN_UPSTREAM" ] || [ "$GITHUB_TOKEN_UPSTREAM" = "null" ]; then + echo "ERROR: Failed to refresh GitHub App token for upstream" + else + export GITHUB_TOKEN="$GITHUB_TOKEN_UPSTREAM" + echo "Upstream token refreshed" + fi + + # === Phase 5: Create Pull Request === + echo "" + echo "==========================================" + echo "Phase 5: Creating Pull Request for $ISSUE_KEY" + echo "==========================================" + + PHASE5_START=$(date +%s) + PR_PROMPT="Create a pull request for the changes on branch '${BRANCH_NAME}'. Details: - Jira issue: ${ISSUE_KEY} - Jira summary: ${ISSUE_SUMMARY} @@ -511,7 +604,7 @@ IMPORTANT: jq -j 'select(.type == "assistant") | .message.content[]? | select(.type == "text") | .text // empty' "/tmp/claude-${ISSUE_KEY}-pr.json" > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-text.txt" 2>/dev/null || true jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | "\(.name): \(.input | keys | join(", "))"' "/tmp/claude-${ISSUE_KEY}-pr.json" 2>/dev/null | sort | uniq -c | sort -rn > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-tools.txt" 2>/dev/null || true jq -r 'select(.type == "user") | .tool_use_result | select(type == "string") | select(startswith("Error:")) | gsub("\n"; "⏎")' "/tmp/claude-${ISSUE_KEY}-pr.json" 2>/dev/null | sort | uniq -c | sort -rn | sed 's/⏎/\n/g' > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-errors.txt" 2>/dev/null || true - # Extract token usage for Phase 4 + # Extract token usage for Phase 5 grep '"type":"result"' "/tmp/claude-${ISSUE_KEY}-pr.json" \ | head -1 \ | jq '{ @@ -526,23 +619,25 @@ IMPORTANT: model: ((.modelUsage // {} | keys | first) // "unknown") }' > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-tokens.json" 2>/dev/null \ || echo '{"total_cost_usd":0,"duration_ms":0,"num_turns":0,"input_tokens":0,"output_tokens":0,"cache_read_input_tokens":0,"cache_creation_input_tokens":0,"model_usage":{},"model":"unknown"}' > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-tokens.json" - echo "Phase 4 tokens: $(cat "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-tokens.json")" + echo "Phase 5 tokens: $(cat "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-tokens.json")" - PHASE4_END=$(date +%s) - PHASE4_DURATION=$((PHASE4_END - PHASE4_START)) - echo "Phase 4 duration: ${PHASE4_DURATION}s" - echo "$PHASE4_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-duration.txt" + PHASE5_END=$(date +%s) + PHASE5_DURATION=$((PHASE5_END - PHASE5_START)) + echo "Phase 5 duration: ${PHASE5_DURATION}s" + echo "$PHASE5_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-duration.txt" + PR_URL="" if [ $PR_EXIT_CODE -eq 0 ]; then PR_URL=$(grep -o 'https://github.com/openshift/hypershift/pull/[0-9]*' "/tmp/claude-${ISSUE_KEY}-pr.json" | head -1 || echo "") if [ -n "$PR_URL" ]; then echo "✅ PR created: $PR_URL" else - echo "⚠️ Phase 4 completed but no PR URL found in output" + echo "⚠️ Phase 5 completed but no PR URL found in output" + ISSUE_FAILED=true fi else - echo "❌ Phase 4 (PR creation) failed for $ISSUE_KEY (exit code: $PR_EXIT_CODE)" - PR_URL="" + echo "❌ Phase 5 (PR creation) failed for $ISSUE_KEY (exit code: $PR_EXIT_CODE)" + ISSUE_FAILED=true fi # Append report link to PR description @@ -575,60 +670,66 @@ ${REPORT_SECTION}" echo "No code changes detected for $ISSUE_KEY, skipping review and PR creation" fi - # Add 'agent-processed' label to mark issue as handled - if [ -n "$JIRA_AUTH" ]; then - echo "Adding 'agent-processed' label to $ISSUE_KEY..." - LABEL_RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ - "https://redhat.atlassian.net/rest/api/3/issue/$ISSUE_KEY" \ - -H "Authorization: Basic $JIRA_AUTH" \ - -H "Content-Type: application/json" \ - -d '{"update":{"labels":[{"add":"agent-processed"}]}}') - HTTP_CODE=$(echo "$LABEL_RESPONSE" | tail -1) - if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then - echo " Label added successfully" - else - echo " Warning: Failed to add label (HTTP $HTTP_CODE)" - fi + if [ "$ISSUE_FAILED" = true ]; then + echo "⚠️ PR creation failed for $ISSUE_KEY — skipping post-processing (will retry next run)" + FAILED_COUNT=$((FAILED_COUNT + 1)) + echo "$ISSUE_KEY $TIMESTAMP ${PR_URL:--} FAILED" >> "$STATE_FILE" + else + # Add 'agent-processed' label to mark issue as handled + if [ -n "$JIRA_AUTH" ]; then + echo "Adding 'agent-processed' label to $ISSUE_KEY..." + LABEL_RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ + "https://redhat.atlassian.net/rest/api/3/issue/$ISSUE_KEY" \ + -H "Authorization: Basic $JIRA_AUTH" \ + -H "Content-Type: application/json" \ + -d '{"update":{"labels":[{"add":"agent-processed"}]}}') + HTTP_CODE=$(echo "$LABEL_RESPONSE" | tail -1) + if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then + echo " Label added successfully" + else + echo " Warning: Failed to add label (HTTP $HTTP_CODE)" + fi - # Transition issue to appropriate status based on project - if [[ "$ISSUE_KEY" == OCPBUGS-* ]]; then - TARGET_STATUS="ASSIGNED" - else - TARGET_STATUS="Code Review" - fi + # Transition issue to appropriate status based on project + if [[ "$ISSUE_KEY" == OCPBUGS-* ]]; then + TARGET_STATUS="ASSIGNED" + else + TARGET_STATUS="Code Review" + fi - echo "Transitioning $ISSUE_KEY to '$TARGET_STATUS'..." - if transition_issue "$ISSUE_KEY" "$TARGET_STATUS"; then - echo " Transition successful" - else - echo " Transition failed or not available" - fi + echo "Transitioning $ISSUE_KEY to '$TARGET_STATUS'..." + if transition_issue "$ISSUE_KEY" "$TARGET_STATUS"; then + echo " Transition successful" + else + echo " Transition failed or not available" + fi - # Set assignee to hypershift-team automation (Cloud requires accountId, look it up by display name) - echo "Looking up accountId for 'hypershift-team automation'..." - ASSIGNEE_ACCOUNT_ID=$(curl -s -G \ - "https://redhat.atlassian.net/rest/api/3/user/search" \ - -H "Authorization: Basic $JIRA_AUTH" \ - --data-urlencode "query=hypershift-automation" \ - | jq -r '[.[] | select(.displayName == "hypershift-team automation")] | .[0].accountId // empty') - if [ -n "$ASSIGNEE_ACCOUNT_ID" ]; then - echo "Setting assignee to account ID '${ASSIGNEE_ACCOUNT_ID}'..." - ASSIGNEE_RESPONSE=$(set_assignee "$ISSUE_KEY" "$ASSIGNEE_ACCOUNT_ID") - else - echo " Warning: Could not find accountId for 'hypershift-team automation', skipping assignee" - ASSIGNEE_RESPONSE="skipped + # Set assignee to hypershift-team automation (Cloud requires accountId, look it up by display name) + echo "Looking up accountId for 'hypershift-team automation'..." + ASSIGNEE_ACCOUNT_ID=$(curl -s -G \ + "https://redhat.atlassian.net/rest/api/3/user/search" \ + -H "Authorization: Basic $JIRA_AUTH" \ + --data-urlencode "query=hypershift-automation" \ + | jq -r '[.[] | select(.displayName == "hypershift-team automation")] | .[0].accountId // empty') + if [ -n "$ASSIGNEE_ACCOUNT_ID" ]; then + echo "Setting assignee to account ID '${ASSIGNEE_ACCOUNT_ID}'..." + ASSIGNEE_RESPONSE=$(set_assignee "$ISSUE_KEY" "$ASSIGNEE_ACCOUNT_ID") + else + echo " Warning: Could not find accountId for 'hypershift-team automation', skipping assignee" + ASSIGNEE_RESPONSE="skipped 200" + fi + HTTP_CODE=$(echo "$ASSIGNEE_RESPONSE" | tail -1) + if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then + echo " Assignee set successfully" + else + echo " Warning: Failed to set assignee (HTTP $HTTP_CODE)" + fi fi - HTTP_CODE=$(echo "$ASSIGNEE_RESPONSE" | tail -1) - if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then - echo " Assignee set successfully" - else - echo " Warning: Failed to set assignee (HTTP $HTTP_CODE)" - fi - fi - PROCESSED_COUNT=$((PROCESSED_COUNT + 1)) - echo "$ISSUE_KEY $TIMESTAMP $PR_URL SUCCESS" >> "$STATE_FILE" + PROCESSED_COUNT=$((PROCESSED_COUNT + 1)) + echo "$ISSUE_KEY $TIMESTAMP $PR_URL SUCCESS" >> "$STATE_FILE" + fi else # Log failure but don't mark as processed (will be retried next run) echo "❌ Failed to process $ISSUE_KEY" diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml index e7e226482494c..b230395a3fca8 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml +++ b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml @@ -54,14 +54,15 @@ ref: mount_path: /var/run/claude-code-service-account documentation: |- Process step for the HyperShift Jira agent periodic job. - This step runs a four-phase pipeline for each issue: + This step runs a five-phase pipeline for each issue: Phase 1 - Solve: Runs /jira-solve to implement changes, commit, and push the branch (no PR created) Phase 2 - Review: Runs /code-review:pre-commit-review to review code quality (read-only) Phase 3 - Fix: Addresses review findings by editing code, committing, and pushing fixes - Phase 4 - PR Creation: Creates a draft PR via gh CLI after review is complete + Phase 4 - Restructure: Reorganizes commits into component-based groups (API, Vendor, CLI, HO, CPO, E2E, Docs) + Phase 5 - PR Creation: Creates a PR via gh CLI after all reviews and restructuring are complete Token usage (input/output) is extracted per phase and saved for reporting. Post-processing: Adds 'agent-processed' label, transitions issue status, sets assignee - Queries Jira for issues with label 'issue-for-agent' (excluding 'agent-processed') - Failed issues are retried on subsequent runs - - If the review skill is unavailable or fails, the PR is still created + - If any review/restructure phase fails, the PR is still created - Uses Vertex AI for Claude authentication via GCP service account diff --git a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh b/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh index ed1bebcd86cea..d638039d3d453 100755 --- a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh +++ b/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh @@ -113,7 +113,7 @@ while IFS= read -r line; do # Debug: verify token files exist and jq is available echo "Processing issue $ISSUE_KEY (status=$STATUS)" echo " Token files check:" - for phase in solve review fix pr; do + for phase in solve review fix restructure pr; do tf="${SHARED_DIR}/claude-${ISSUE_KEY}-${phase}-tokens.json" if [ -f "$tf" ]; then echo " ${phase}: $(cat "$tf" | tr -d '\n' | cut -c1-120)" @@ -141,16 +141,19 @@ while IFS= read -r line; do SOLVE_TEXT=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-output-text.txt" | html_escape) REVIEW_TEXT=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-review-text.txt" | html_escape) FIX_TEXT=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-fix-text.txt" | html_escape) + RESTRUCTURE_TEXT=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-text.txt" | html_escape) PR_TEXT=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-text.txt" | html_escape) SOLVE_TOOLS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-output-tools.txt" | html_escape) REVIEW_TOOLS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-review-tools.txt" | html_escape) FIX_TOOLS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-fix-tools.txt" | html_escape) + RESTRUCTURE_TOOLS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-tools.txt" | html_escape) PR_TOOLS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-tools.txt" | html_escape) SOLVE_ERRORS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-output-errors.txt" | html_escape) REVIEW_ERRORS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-review-errors.txt" | html_escape) FIX_ERRORS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-fix-errors.txt" | html_escape) + RESTRUCTURE_ERRORS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-restructure-errors.txt" | html_escape) PR_ERRORS=$(read_extracted "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-errors.txt" | html_escape) # Read token usage per phase @@ -164,7 +167,7 @@ while IFS= read -r line; do ISSUE_TOTAL_DURATION=0 - for phase_info in "solve:Phase 1: Solve" "review:Phase 2: Review" "fix:Phase 3: Fix" "pr:Phase 4: PR"; do + for phase_info in "solve:Phase 1: Solve" "review:Phase 2: Review" "fix:Phase 3: Fix" "restructure:Phase 4: Restructure" "pr:Phase 5: PR"; do PHASE_KEY="${phase_info%%:*}" PHASE_LABEL="${phase_info#*:}" TOKEN_FILE="${SHARED_DIR}/claude-${ISSUE_KEY}-${PHASE_KEY}-tokens.json" @@ -200,7 +203,7 @@ while IFS= read -r line; do # Build per-model breakdown rows from aggregated model_usage across phases MODEL_BREAKDOWN_ROWS="" MODEL_FILES="" - for phase_key in solve review fix pr; do + for phase_key in solve review fix restructure pr; do tf="${SHARED_DIR}/claude-${ISSUE_KEY}-${phase_key}-tokens.json" if [ -f "$tf" ]; then MODEL_FILES="$MODEL_FILES $tf" @@ -278,7 +281,12 @@ while IFS= read -r line; do
Tool calls
${FIX_TOOLS}
Tool errors
${FIX_ERRORS}
-

Phase 4: PR Creation

+

Phase 4: Restructure Commits

+
${RESTRUCTURE_TEXT}
+
Tool calls
${RESTRUCTURE_TOOLS}
+
Tool errors
${RESTRUCTURE_ERRORS}
+ +

Phase 5: PR Creation

${PR_TEXT}
Tool calls
${PR_TOOLS}
Tool errors
${PR_ERRORS}