mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: 增加失败分析功能,改进问题规划和解决方案生成
This commit is contained in:
@@ -56,14 +56,61 @@ Phase 4: Validation & Output (15%)
|
||||
ccw issue status <issue-id> --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 <id> --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 <id> --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
|
||||
|
||||
@@ -195,12 +195,26 @@ ${issueList}
|
||||
|
||||
### Workflow
|
||||
1. Fetch issue details: ccw issue status <id> --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)
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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<void> {
|
||||
let queues: Queue[];
|
||||
@@ -2609,6 +2610,7 @@ async function retryAction(issueId: string | undefined, options: IssueOptions):
|
||||
}
|
||||
|
||||
let totalUpdated = 0;
|
||||
const updatedIssues = new Set<string>();
|
||||
|
||||
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 ============
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -431,10 +431,60 @@ function renderIssueCard(issue) {
|
||||
<span>Archived on ${archivedDate}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${renderFailureInfo(issue)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 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 `
|
||||
<div class="issue-failure-info">
|
||||
<div class="failure-header">
|
||||
<i data-lucide="alert-circle" class="w-3.5 h-3.5"></i>
|
||||
<span class="failure-label">${failureCount > 1 ? `Failed ${failureCount} times` : 'Execution Failed'}</span>
|
||||
${taskId ? `<span class="failure-task">${taskId}</span>` : ''}
|
||||
</div>
|
||||
<div class="failure-message">
|
||||
<span class="failure-type">${errorType}:</span>
|
||||
<span class="failure-text" title="${escapeHtml(errorMessage)}">${escapeHtml(truncateText(errorMessage, 80))}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
<i data-lucide="link" class="w-3 h-3"></i>
|
||||
</span>
|
||||
` : ''}
|
||||
${renderQueueItemFailureInfo(item)}
|
||||
<button class="queue-item-delete btn-icon" onclick="event.stopPropagation(); deleteQueueItem('${safeQueueId}', '${safeItemId}')" title="Delete item">
|
||||
<i data-lucide="trash-2" class="w-3 h-3"></i>
|
||||
</button>
|
||||
@@ -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 `
|
||||
<span class="queue-item-failure text-xs" title="${escapeHtml(errorMessage)}">
|
||||
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||
<span class="failure-type">${escapeHtml(errorType)}:</span>
|
||||
<span class="failure-msg">${escapeHtml(truncateText(errorMessage, 40))}</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
async function deleteQueueItem(queueId, itemId) {
|
||||
if (!confirm('Delete this item from queue?')) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user