From a7c8ea04f1a8ccb177217e59a5cbe13479cf3d0d Mon Sep 17 00:00:00 2001 From: catlog22 Date: Wed, 21 Jan 2026 17:46:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E8=A7=84=E5=88=92=E5=92=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=96=B9=E6=A1=88=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/agents/issue-plan-agent.md | 106 ++++++++++++++++-- .claude/commands/issue/plan.md | 26 ++++- .../schemas/issues-jsonl-schema.json | 29 +++++ ccw/src/commands/issue.ts | 46 +++++++- .../dashboard-css/32-issue-manager.css | 87 ++++++++++++++ .../dashboard-js/views/issue-manager.js | 92 +++++++++++++++ 6 files changed, 364 insertions(+), 22 deletions(-) diff --git a/.claude/agents/issue-plan-agent.md b/.claude/agents/issue-plan-agent.md index b117d92d..13c59d29 100644 --- a/.claude/agents/issue-plan-agent.md +++ b/.claude/agents/issue-plan-agent.md @@ -56,14 +56,61 @@ Phase 4: Validation & Output (15%) ccw issue status --json ``` -**Step 2**: Analyze and classify +**Step 2**: Analyze failure history (if present) +```javascript +function analyzeFailureHistory(issue) { + if (!issue.feedback || issue.feedback.length === 0) { + return { has_failures: false }; + } + + // Extract execution failures + const failures = issue.feedback.filter(f => f.type === 'failure' && f.stage === 'execute'); + + if (failures.length === 0) { + return { has_failures: false }; + } + + // Parse failure details + const failureAnalysis = failures.map(f => { + const detail = JSON.parse(f.content); + return { + solution_id: detail.solution_id, + task_id: detail.task_id, + error_type: detail.error_type, // test_failure, compilation, timeout, etc. + message: detail.message, + stack_trace: detail.stack_trace, + timestamp: f.created_at + }; + }); + + // Identify patterns + const errorTypes = failureAnalysis.map(f => f.error_type); + const repeatedErrors = errorTypes.filter((e, i, arr) => arr.indexOf(e) !== i); + + return { + has_failures: true, + failure_count: failures.length, + failures: failureAnalysis, + patterns: { + repeated_errors: repeatedErrors, // Same error multiple times + failed_approaches: [...new Set(failureAnalysis.map(f => f.solution_id))] + } + }; +} +``` + +**Step 3**: Analyze and classify ```javascript function analyzeIssue(issue) { + const failureAnalysis = analyzeFailureHistory(issue); + return { issue_id: issue.id, requirements: extractRequirements(issue.context), scope: inferScope(issue.title, issue.context), - complexity: determineComplexity(issue) // Low | Medium | High + complexity: determineComplexity(issue), // Low | Medium | High + failure_analysis: failureAnalysis, // Failure context for planning + is_replan: failureAnalysis.has_failures // Flag for replanning } } ``` @@ -104,6 +151,41 @@ mcp__ace-tool__search_context({ #### Phase 3: Solution Planning +**Failure-Aware Planning** (when `issue.failure_analysis.has_failures === true`): + +```javascript +function planWithFailureContext(issue, exploration, failureAnalysis) { + // Identify what failed before + const failedApproaches = failureAnalysis.patterns.failed_approaches; + const rootCauses = failureAnalysis.failures.map(f => ({ + error: f.error_type, + message: f.message, + task: f.task_id + })); + + // Design alternative approach + const approach = ` + **Previous Attempt Analysis**: + - Failed approaches: ${failedApproaches.join(', ')} + - Root causes: ${rootCauses.map(r => `${r.error} (${r.task}): ${r.message}`).join('; ')} + + **Alternative Strategy**: + - [Describe how this solution addresses root causes] + - [Explain what's different from failed approaches] + - [Prevention steps to catch same errors earlier] + `; + + // Add explicit verification tasks + const verificationTasks = rootCauses.map(rc => ({ + verification_type: rc.error, + check: `Prevent ${rc.error}: ${rc.message}`, + method: `Add unit test / compile check / timeout limit` + })); + + return { approach, verificationTasks }; +} +``` + **Multi-Solution Generation**: Generate multiple candidate solutions when: @@ -303,15 +385,17 @@ Each line is a solution JSON containing tasks. Schema: `cat .claude/workflows/cl **ALWAYS**: 1. **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`) 2. Read schema first: `cat .claude/workflows/cli-templates/schemas/solution-schema.json` -2. Use ACE semantic search as PRIMARY exploration tool -3. Fetch issue details via `ccw issue status --json` -4. Quantify acceptance.criteria with testable conditions -5. Validate DAG before output -6. Evaluate each solution with `analysis` and `score` -7. Write solutions to `.workflow/issues/solutions/{issue-id}.jsonl` (append mode) -8. For HIGH complexity: generate 2-3 candidate solutions -9. **Solution ID format**: `SOL-{issue-id}-{uid}` where uid is 4 random alphanumeric chars (e.g., `SOL-GH-123-a7x9`) -10. **GitHub Reply Task**: If issue has `github_url` or `github_number`, add final task to comment on GitHub issue with completion summary +3. Use ACE semantic search as PRIMARY exploration tool +4. Fetch issue details via `ccw issue status --json` +5. **Analyze failure history**: Check `issue.feedback` for type='failure', stage='execute' +6. **For replanning**: Reference previous failures in `solution.approach`, add prevention steps +7. Quantify acceptance.criteria with testable conditions +8. Validate DAG before output +9. Evaluate each solution with `analysis` and `score` +10. Write solutions to `.workflow/issues/solutions/{issue-id}.jsonl` (append mode) +11. For HIGH complexity: generate 2-3 candidate solutions +12. **Solution ID format**: `SOL-{issue-id}-{uid}` where uid is 4 random alphanumeric chars (e.g., `SOL-GH-123-a7x9`) +13. **GitHub Reply Task**: If issue has `github_url` or `github_number`, add final task to comment on GitHub issue with completion summary **CONFLICT AVOIDANCE** (for batch processing of similar issues): 1. **File isolation**: Each issue's solution should target distinct files when possible diff --git a/.claude/commands/issue/plan.md b/.claude/commands/issue/plan.md index 88f017a7..df8843e4 100644 --- a/.claude/commands/issue/plan.md +++ b/.claude/commands/issue/plan.md @@ -195,12 +195,26 @@ ${issueList} ### Workflow 1. Fetch issue details: ccw issue status --json -2. Load project context files -3. Explore codebase (ACE semantic search) -4. Plan solution with tasks (schema: solution-schema.json) -5. **If github_url exists**: Add final task to comment on GitHub issue -6. Write solution to: .workflow/issues/solutions/{issue-id}.jsonl -7. Single solution → auto-bind; Multiple → return for selection +2. **Analyze failure history** (if issue.feedback exists): + - Extract failure details from issue.feedback (type='failure', stage='execute') + - Parse error_type, message, task_id, solution_id from content JSON + - Identify failure patterns: repeated errors, root causes, blockers + - **Constraint**: Avoid repeating failed approaches +3. Load project context files +4. Explore codebase (ACE semantic search) +5. Plan solution with tasks (schema: solution-schema.json) + - **If previous solution failed**: Reference failure analysis in solution.approach + - Add explicit verification steps to prevent same failure mode +6. **If github_url exists**: Add final task to comment on GitHub issue +7. Write solution to: .workflow/issues/solutions/{issue-id}.jsonl +8. Single solution → auto-bind; Multiple → return for selection + +### Failure-Aware Planning Rules +- **Extract failure patterns**: Parse issue.feedback where type='failure' and stage='execute' +- **Identify root causes**: Analyze error_type (test_failure, compilation, timeout, etc.) +- **Design alternative approach**: Create solution that addresses root cause +- **Add prevention steps**: Include explicit verification to catch same error earlier +- **Document lessons**: Reference previous failures in solution.approach ### Rules - Solution ID format: SOL-{issue-id}-{uid} (uid: 4 random alphanumeric chars, e.g., a7x9) diff --git a/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json b/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json index 0214e9a9..f6c7a1b9 100644 --- a/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +++ b/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json @@ -65,6 +65,35 @@ "items": { "type": "string" }, "description": "Files/modules affected" }, + "feedback": { + "type": "array", + "description": "Execution feedback history (failures, clarifications, rejections) for planning phase reference", + "items": { + "type": "object", + "required": ["type", "stage", "content", "created_at"], + "properties": { + "type": { + "type": "string", + "enum": ["failure", "clarification", "rejection"], + "description": "Type of feedback" + }, + "stage": { + "type": "string", + "enum": ["new", "plan", "execute"], + "description": "Which stage the feedback occurred (new=creation, plan=planning, execute=execution)" + }, + "content": { + "type": "string", + "description": "JSON string for failures (with solution_id, task_id, error_type, message, stack_trace) or plain text for clarifications/rejections" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp when feedback was created" + } + } + } + }, "lifecycle_requirements": { "type": "object", "properties": { diff --git a/ccw/src/commands/issue.ts b/ccw/src/commands/issue.ts index bc932033..4ad3a5d3 100644 --- a/ccw/src/commands/issue.ts +++ b/ccw/src/commands/issue.ts @@ -2587,6 +2587,7 @@ async function doneAction(queueItemId: string | undefined, options: IssueOptions /** * retry - Reset failed items to pending for re-execution + * Syncs failure details to Issue.feedback for planning phase */ async function retryAction(issueId: string | undefined, options: IssueOptions): Promise { let queues: Queue[]; @@ -2609,6 +2610,7 @@ async function retryAction(issueId: string | undefined, options: IssueOptions): } let totalUpdated = 0; + const updatedIssues = new Set(); for (const queue of queues) { const items = queue.solutions || queue.tasks || []; @@ -2618,6 +2620,41 @@ async function retryAction(issueId: string | undefined, options: IssueOptions): // Retry failed items only if (item.status === 'failed') { if (!issueId || item.issue_id === issueId) { + // Sync failure details to Issue.feedback (persistent for planning phase) + if (item.failure_details && item.issue_id) { + const issue = findIssue(item.issue_id); + if (issue) { + if (!issue.feedback) { + issue.feedback = []; + } + + // Add failure to feedback history + issue.feedback.push({ + type: 'failure', + stage: 'execute', + content: JSON.stringify({ + solution_id: item.solution_id, + task_id: item.failure_details.task_id, + error_type: item.failure_details.error_type, + message: item.failure_details.message, + stack_trace: item.failure_details.stack_trace, + queue_id: queue.id, + item_id: item.item_id + }), + created_at: item.failure_details.timestamp + }); + + // Keep issue status as 'failed' (or optionally 'pending_replan') + // This signals to planning phase that this issue had failures + updateIssue(item.issue_id, { + status: 'failed', + updated_at: new Date().toISOString() + }); + + updatedIssues.add(item.issue_id); + } + } + // Preserve failure history before resetting if (item.failure_details) { if (!item.failure_history) { @@ -2626,7 +2663,7 @@ async function retryAction(issueId: string | undefined, options: IssueOptions): item.failure_history.push(item.failure_details); } - // Reset for retry + // Reset QueueItem for retry (but Issue status remains 'failed') item.status = 'pending'; item.failure_reason = undefined; item.failure_details = undefined; @@ -2659,11 +2696,10 @@ async function retryAction(issueId: string | undefined, options: IssueOptions): return; } - if (issueId) { - updateIssue(issueId, { status: 'queued' }); - } - console.log(chalk.green(`✓ Reset ${totalUpdated} item(s) to pending (failure history preserved)`)); + if (updatedIssues.size > 0) { + console.log(chalk.cyan(`✓ Synced failure details to ${updatedIssues.size} issue(s) for planning phase`)); + } } // ============ Main Entry ============ diff --git a/ccw/src/templates/dashboard-css/32-issue-manager.css b/ccw/src/templates/dashboard-css/32-issue-manager.css index c7f1b9c9..b28690f6 100644 --- a/ccw/src/templates/dashboard-css/32-issue-manager.css +++ b/ccw/src/templates/dashboard-css/32-issue-manager.css @@ -279,6 +279,58 @@ color: hsl(var(--destructive)); } +/* Issue Failure Info */ +.issue-failure-info { + margin-top: 0.75rem; + padding: 0.5rem 0.75rem; + background: hsl(var(--destructive) / 0.08); + border: 1px solid hsl(var(--destructive) / 0.2); + border-radius: 0.375rem; + border-left: 3px solid hsl(var(--destructive)); +} + +.issue-failure-info .failure-header { + display: flex; + align-items: center; + gap: 0.375rem; + color: hsl(var(--destructive)); + font-size: 0.75rem; + font-weight: 500; + margin-bottom: 0.25rem; +} + +.issue-failure-info .failure-label { + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.issue-failure-info .failure-task { + font-family: var(--font-mono); + background: hsl(var(--destructive) / 0.15); + padding: 0 0.25rem; + border-radius: 0.25rem; + font-size: 0.6875rem; +} + +.issue-failure-info .failure-message { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); + line-height: 1.4; +} + +.issue-failure-info .failure-type { + font-family: var(--font-mono); + color: hsl(var(--destructive) / 0.8); + font-weight: 500; +} + +.issue-failure-info .failure-text { + word-break: break-word; +} + /* Priority Badges */ .issue-priority { display: inline-flex; @@ -2014,6 +2066,41 @@ border-left: 3px solid hsl(0 84% 60%); } +/* Queue Item Failure Info */ +.queue-item-failure { + display: flex; + align-items: center; + gap: 0.25rem; + color: hsl(var(--destructive)); + background: hsl(var(--destructive) / 0.1); + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + max-width: 250px; + overflow: hidden; +} + +.queue-item-failure i { + flex-shrink: 0; +} + +.queue-item-failure .failure-type { + font-family: var(--font-mono); + font-weight: 500; + flex-shrink: 0; +} + +.queue-item-failure .failure-msg { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: hsl(var(--muted-foreground)); +} + +/* Hide failure in parallel view to save space */ +.queue-items.parallel .queue-item .queue-item-failure { + display: none; +} + /* Blocked - Purple/violet blocked state */ .queue-item.blocked { border-color: hsl(262 83% 58%); diff --git a/ccw/src/templates/dashboard-js/views/issue-manager.js b/ccw/src/templates/dashboard-js/views/issue-manager.js index 7abac460..4079ef7b 100644 --- a/ccw/src/templates/dashboard-js/views/issue-manager.js +++ b/ccw/src/templates/dashboard-js/views/issue-manager.js @@ -431,10 +431,60 @@ function renderIssueCard(issue) { Archived on ${archivedDate} ` : ''} + + ${renderFailureInfo(issue)} `; } +// Render failure information for failed issues +function renderFailureInfo(issue) { + // Check if issue has failure feedback + if (!issue.feedback || issue.feedback.length === 0) { + return ''; + } + + // Extract failure feedbacks + const failures = issue.feedback.filter(f => f.type === 'failure' && f.stage === 'execute'); + if (failures.length === 0) { + return ''; + } + + // Get latest failure + const latestFailure = failures[failures.length - 1]; + let failureDetail; + try { + failureDetail = JSON.parse(latestFailure.content); + } catch { + return ''; + } + + const errorMessage = failureDetail.message || 'Unknown error'; + const errorType = failureDetail.error_type || 'error'; + const taskId = failureDetail.task_id; + const failureCount = failures.length; + + return ` +
+
+ + ${failureCount > 1 ? `Failed ${failureCount} times` : 'Execution Failed'} + ${taskId ? `${taskId}` : ''} +
+
+ ${errorType}: + ${escapeHtml(truncateText(errorMessage, 80))} +
+
+ `; +} + +// Helper: Truncate text to max length +function truncateText(text, maxLength) { + if (!text || text.length <= maxLength) return text; + return text.substring(0, maxLength - 3) + '...'; +} + function renderPriorityStars(priority) { const maxStars = 5; let stars = ''; @@ -879,6 +929,7 @@ function renderQueueItemWithDelete(item, index, total, queueId) { ` : ''} + ${renderQueueItemFailureInfo(item)} @@ -886,6 +937,47 @@ function renderQueueItemWithDelete(item, index, total, queueId) { `; } +// Render failure info for queue items +function renderQueueItemFailureInfo(item) { + // Only show for failed items + if (item.status !== 'failed') { + return ''; + } + + // Check failure_details or failure_reason + const failureDetails = item.failure_details; + const failureReason = item.failure_reason; + + if (!failureDetails && !failureReason) { + return ''; + } + + let errorType = 'error'; + let errorMessage = 'Unknown error'; + + if (failureDetails) { + errorType = failureDetails.error_type || 'error'; + errorMessage = failureDetails.message || 'Unknown error'; + } else if (failureReason) { + // Try to parse as JSON + try { + const parsed = JSON.parse(failureReason); + errorType = parsed.error_type || 'error'; + errorMessage = parsed.message || failureReason; + } catch { + errorMessage = failureReason; + } + } + + return ` + + + ${escapeHtml(errorType)}: + ${escapeHtml(truncateText(errorMessage, 40))} + + `; +} + async function deleteQueueItem(queueId, itemId) { if (!confirm('Delete this item from queue?')) return;