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}