mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-02 15:23:19 +08:00
- Rename skill directory from workflow-lite-plan to workflow-lite-planex (planex = plan + execute) - Remove standalone lite-execute command entry from command.json and analyze_commands.py - Update all 60+ files referencing workflow-lite-plan to use workflow-lite-planex - Update descriptions to clarify Phase 1: plan → Phase 2: execute architecture - Remove lite-execute as standalone command from orchestrator routing tables - Update docs (EN/ZH) to reflect unified planex naming and phase descriptions
18 KiB
18 KiB
name, description, argument-hint, allowed-tools
| name | description | argument-hint | allowed-tools |
|---|---|---|---|
| run | IDAW orchestrator - execute task skill chains serially with git checkpoints | [-y|--yes] [--task <id>[,<id>,...]] [--dry-run] | Skill(*), TodoWrite(*), AskUserQuestion(*), Read(*), Write(*), Bash(*), Glob(*) |
IDAW Run Command (/idaw:run)
Auto Mode
When --yes or -y: Skip all confirmations, auto-skip on failure, proceed with dirty git.
Skill Chain Mapping
const SKILL_CHAIN_MAP = {
'bugfix': ['workflow-lite-planex', 'workflow-test-fix'],
'bugfix-hotfix': ['workflow-lite-planex'],
'feature': ['workflow-lite-planex', 'workflow-test-fix'],
'feature-complex': ['workflow-plan', 'workflow-execute', 'workflow-test-fix'],
'refactor': ['workflow:refactor-cycle'],
'tdd': ['workflow-tdd-plan', 'workflow-execute'],
'test': ['workflow-test-fix'],
'test-fix': ['workflow-test-fix'],
'review': ['review-cycle'],
'docs': ['workflow-lite-planex']
};
Task Type Inference
function inferTaskType(title, description) {
const text = `${title} ${description}`.toLowerCase();
if (/urgent|production|critical/.test(text) && /fix|bug/.test(text)) return 'bugfix-hotfix';
if (/refactor|重构|tech.*debt/.test(text)) return 'refactor';
if (/tdd|test-driven|test first/.test(text)) return 'tdd';
if (/test fail|fix test|failing test/.test(text)) return 'test-fix';
if (/generate test|写测试|add test/.test(text)) return 'test';
if (/review|code review/.test(text)) return 'review';
if (/docs|documentation|readme/.test(text)) return 'docs';
if (/fix|bug|error|crash|fail/.test(text)) return 'bugfix';
if (/complex|multi-module|architecture/.test(text)) return 'feature-complex';
return 'feature';
}
6-Phase Execution
Phase 1: Load Tasks
const args = $ARGUMENTS;
const autoYes = /(-y|--yes)/.test(args);
const dryRun = /--dry-run/.test(args);
const taskFilter = args.match(/--task\s+([\w,-]+)/)?.[1]?.split(',') || null;
// Load task files
const taskFiles = Glob('.workflow/.idaw/tasks/IDAW-*.json') || [];
if (taskFiles.length === 0) {
console.log('No IDAW tasks found. Use /idaw:add to create tasks.');
return;
}
// Parse and filter
let tasks = taskFiles.map(f => JSON.parse(Read(f)));
if (taskFilter) {
tasks = tasks.filter(t => taskFilter.includes(t.id));
} else {
tasks = tasks.filter(t => t.status === 'pending');
}
if (tasks.length === 0) {
console.log('No pending tasks to execute. Use /idaw:add to add tasks or --task to specify IDs.');
return;
}
// Sort: priority ASC (1=critical first), then ID ASC
tasks.sort((a, b) => {
if (a.priority !== b.priority) return a.priority - b.priority;
return a.id.localeCompare(b.id);
});
Phase 2: Session Setup
// Generate session ID: IDA-{slug}-YYYYMMDD
const slug = tasks[0].title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.substring(0, 20)
.replace(/-$/, '');
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
let sessionId = `IDA-${slug}-${dateStr}`;
// Check collision
const existingSession = Glob(`.workflow/.idaw/sessions/${sessionId}/session.json`);
if (existingSession?.length > 0) {
sessionId = `${sessionId}-2`;
}
const sessionDir = `.workflow/.idaw/sessions/${sessionId}`;
Bash(`mkdir -p "${sessionDir}"`);
const session = {
session_id: sessionId,
status: 'running',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
tasks: tasks.map(t => t.id),
current_task: null,
completed: [],
failed: [],
skipped: []
};
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
// Initialize progress.md
const progressHeader = `# IDAW Progress — ${sessionId}\nStarted: ${session.created_at}\n\n`;
Write(`${sessionDir}/progress.md`, progressHeader);
// TodoWrite
TodoWrite({
todos: tasks.map((t, i) => ({
content: `IDAW:[${i + 1}/${tasks.length}] ${t.title}`,
status: i === 0 ? 'in_progress' : 'pending',
activeForm: `Executing ${t.title}`
}))
});
Phase 3: Startup Protocol
// Check for existing running sessions
const runningSessions = Glob('.workflow/.idaw/sessions/IDA-*/session.json')
?.map(f => JSON.parse(Read(f)))
.filter(s => s.status === 'running' && s.session_id !== sessionId) || [];
if (runningSessions.length > 0) {
if (!autoYes) {
const answer = AskUserQuestion({
questions: [{
question: `Found running session: ${runningSessions[0].session_id}. How to proceed?`,
header: 'Conflict',
multiSelect: false,
options: [
{ label: 'Resume existing', description: 'Use /idaw:resume instead' },
{ label: 'Start fresh', description: 'Continue with new session' },
{ label: 'Abort', description: 'Cancel this run' }
]
}]
});
if (answer.answers?.Conflict === 'Resume existing') {
console.log(`Use: /idaw:resume ${runningSessions[0].session_id}`);
return;
}
if (answer.answers?.Conflict === 'Abort') return;
}
// autoYes or "Start fresh": proceed
}
// Check git status
const gitStatus = Bash('git status --porcelain 2>/dev/null');
if (gitStatus?.trim()) {
if (!autoYes) {
const answer = AskUserQuestion({
questions: [{
question: 'Working tree has uncommitted changes. How to proceed?',
header: 'Git',
multiSelect: false,
options: [
{ label: 'Continue', description: 'Proceed with dirty tree' },
{ label: 'Stash', description: 'git stash before running' },
{ label: 'Abort', description: 'Stop and handle manually' }
]
}]
});
if (answer.answers?.Git === 'Stash') {
Bash('git stash push -m "idaw-pre-run"');
}
if (answer.answers?.Git === 'Abort') return;
}
// autoYes: proceed silently
}
// Dry run: show plan and exit
if (dryRun) {
console.log(`# Dry Run — ${sessionId}\n`);
for (const task of tasks) {
const taskType = task.task_type || inferTaskType(task.title, task.description);
const chain = task.skill_chain || SKILL_CHAIN_MAP[taskType] || SKILL_CHAIN_MAP['feature'];
console.log(`## ${task.id}: ${task.title}`);
console.log(` Type: ${taskType} | Priority: ${task.priority}`);
console.log(` Chain: ${chain.join(' → ')}\n`);
}
console.log(`Total: ${tasks.length} tasks`);
return;
}
Phase 4: Main Loop (serial, one task at a time)
for (let taskIdx = 0; taskIdx < tasks.length; taskIdx++) {
const task = tasks[taskIdx];
// Skip completed/failed/skipped
if (['completed', 'failed', 'skipped'].includes(task.status)) continue;
// Resolve skill chain
const resolvedType = task.task_type || inferTaskType(task.title, task.description);
const chain = task.skill_chain || SKILL_CHAIN_MAP[resolvedType] || SKILL_CHAIN_MAP['feature'];
// Update task status → in_progress
task.status = 'in_progress';
task.task_type = resolvedType; // persist inferred type
task.execution.started_at = new Date().toISOString();
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
// Update session
session.current_task = task.id;
session.updated_at = new Date().toISOString();
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
console.log(`\n--- [${taskIdx + 1}/${tasks.length}] ${task.id}: ${task.title} ---`);
console.log(`Chain: ${chain.join(' → ')}`);
// ━━━ Pre-Task CLI Context Analysis (for complex/bugfix tasks) ━━━
if (['bugfix', 'bugfix-hotfix', 'feature-complex'].includes(resolvedType)) {
console.log(` Pre-analysis: gathering context for ${resolvedType} task...`);
const affectedFiles = (task.context?.affected_files || []).join(', ');
const preAnalysisPrompt = `PURPOSE: Pre-analyze codebase context for IDAW task before execution.
TASK: • Understand current state of: ${affectedFiles || 'files related to: ' + task.title} • Identify dependencies and risk areas • Note existing patterns to follow
MODE: analysis
CONTEXT: @**/*
EXPECTED: Brief context summary (affected modules, dependencies, risk areas) in 3-5 bullet points
CONSTRAINTS: Keep concise | Focus on execution-relevant context`;
const preAnalysis = Bash(`ccw cli -p '${preAnalysisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis 2>&1 || echo "Pre-analysis skipped"`);
task.execution.skill_results.push({
skill: 'cli-pre-analysis',
status: 'completed',
context_summary: preAnalysis?.substring(0, 500),
timestamp: new Date().toISOString()
});
}
// Execute each skill in chain
let previousResult = null;
let taskFailed = false;
for (let skillIdx = 0; skillIdx < chain.length; skillIdx++) {
const skillName = chain[skillIdx];
const skillArgs = assembleSkillArgs(skillName, task, previousResult, autoYes, skillIdx === 0);
console.log(` [${skillIdx + 1}/${chain.length}] ${skillName}`);
try {
const result = Skill({ skill: skillName, args: skillArgs });
previousResult = result;
task.execution.skill_results.push({
skill: skillName,
status: 'completed',
timestamp: new Date().toISOString()
});
} catch (error) {
// ━━━ CLI-Assisted Error Recovery ━━━
// Step 1: Invoke CLI diagnosis (auto-invoke trigger: self-repair fails)
console.log(` Diagnosing failure: ${skillName}...`);
const diagnosisPrompt = `PURPOSE: Diagnose why skill "${skillName}" failed during IDAW task execution.
TASK: • Analyze error: ${String(error).substring(0, 300)} • Check affected files: ${(task.context?.affected_files || []).join(', ') || 'unknown'} • Identify root cause • Suggest fix strategy
MODE: analysis
CONTEXT: @**/* | Memory: IDAW task ${task.id}: ${task.title}
EXPECTED: Root cause + actionable fix recommendation (1-2 sentences)
CONSTRAINTS: Focus on actionable diagnosis`;
const diagnosisResult = Bash(`ccw cli -p '${diagnosisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis --rule analysis-diagnose-bug-root-cause 2>&1 || echo "CLI diagnosis unavailable"`);
task.execution.skill_results.push({
skill: `cli-diagnosis:${skillName}`,
status: 'completed',
diagnosis: diagnosisResult?.substring(0, 500),
timestamp: new Date().toISOString()
});
// Step 2: Retry with diagnosis context
console.log(` Retry with diagnosis: ${skillName}`);
try {
const retryResult = Skill({ skill: skillName, args: skillArgs });
previousResult = retryResult;
task.execution.skill_results.push({
skill: skillName,
status: 'completed-retry-with-diagnosis',
timestamp: new Date().toISOString()
});
} catch (retryError) {
// Step 3: Failed after CLI-assisted retry
task.execution.skill_results.push({
skill: skillName,
status: 'failed',
error: String(retryError).substring(0, 200),
timestamp: new Date().toISOString()
});
if (autoYes) {
taskFailed = true;
break;
} else {
const answer = AskUserQuestion({
questions: [{
question: `${skillName} failed after CLI diagnosis + retry: ${String(retryError).substring(0, 100)}. How to proceed?`,
header: 'Error',
multiSelect: false,
options: [
{ label: 'Skip task', description: 'Mark task as failed, continue to next' },
{ label: 'Abort', description: 'Stop entire run' }
]
}]
});
if (answer.answers?.Error === 'Abort') {
task.status = 'failed';
task.execution.error = String(retryError).substring(0, 200);
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
session.failed.push(task.id);
session.status = 'failed';
session.updated_at = new Date().toISOString();
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
return;
}
taskFailed = true;
break;
}
}
}
}
// Phase 5: Checkpoint (per task) — inline
if (taskFailed) {
task.status = 'failed';
task.execution.error = 'Skill chain failed after retry';
task.execution.completed_at = new Date().toISOString();
session.failed.push(task.id);
} else {
// Git commit checkpoint
const commitMsg = `feat(idaw): ${task.title} [${task.id}]`;
const diffCheck = Bash('git diff --stat HEAD 2>/dev/null || echo ""');
const untrackedCheck = Bash('git ls-files --others --exclude-standard 2>/dev/null || echo ""');
if (diffCheck?.trim() || untrackedCheck?.trim()) {
Bash('git add -A');
const commitResult = Bash(`git commit -m "$(cat <<'EOF'\n${commitMsg}\nEOF\n)"`);
const commitHash = Bash('git rev-parse --short HEAD 2>/dev/null')?.trim();
task.execution.git_commit = commitHash;
} else {
task.execution.git_commit = 'no-commit';
}
task.status = 'completed';
task.execution.completed_at = new Date().toISOString();
session.completed.push(task.id);
}
// Write task + session state
task.updated_at = new Date().toISOString();
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
session.updated_at = new Date().toISOString();
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
// Append to progress.md
const duration = task.execution.started_at && task.execution.completed_at
? formatDuration(new Date(task.execution.completed_at) - new Date(task.execution.started_at))
: 'unknown';
const progressEntry = `## ${task.id} — ${task.title}\n` +
`- Status: ${task.status}\n` +
`- Type: ${task.task_type}\n` +
`- Chain: ${chain.join(' → ')}\n` +
`- Commit: ${task.execution.git_commit || '-'}\n` +
`- Duration: ${duration}\n\n`;
const currentProgress = Read(`${sessionDir}/progress.md`);
Write(`${sessionDir}/progress.md`, currentProgress + progressEntry);
// Update TodoWrite
if (taskIdx + 1 < tasks.length) {
TodoWrite({
todos: tasks.map((t, i) => ({
content: `IDAW:[${i + 1}/${tasks.length}] ${t.title}`,
status: i < taskIdx + 1 ? 'completed' : (i === taskIdx + 1 ? 'in_progress' : 'pending'),
activeForm: `Executing ${t.title}`
}))
});
}
}
Phase 6: Report
session.status = session.failed.length > 0 && session.completed.length === 0 ? 'failed' : 'completed';
session.current_task = null;
session.updated_at = new Date().toISOString();
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
// Final progress summary
const summary = `\n---\n## Summary\n` +
`- Completed: ${session.completed.length}\n` +
`- Failed: ${session.failed.length}\n` +
`- Skipped: ${session.skipped.length}\n` +
`- Total: ${tasks.length}\n`;
const finalProgress = Read(`${sessionDir}/progress.md`);
Write(`${sessionDir}/progress.md`, finalProgress + summary);
// Display report
console.log('\n=== IDAW Run Complete ===');
console.log(`Session: ${sessionId}`);
console.log(`Completed: ${session.completed.length}/${tasks.length}`);
if (session.failed.length > 0) console.log(`Failed: ${session.failed.join(', ')}`);
if (session.skipped.length > 0) console.log(`Skipped: ${session.skipped.join(', ')}`);
// List git commits
for (const taskId of session.completed) {
const t = JSON.parse(Read(`.workflow/.idaw/tasks/${taskId}.json`));
if (t.execution.git_commit && t.execution.git_commit !== 'no-commit') {
console.log(` ${t.execution.git_commit} ${t.title}`);
}
}
Helper Functions
assembleSkillArgs
function assembleSkillArgs(skillName, task, previousResult, autoYes, isFirst) {
let args = '';
if (isFirst) {
// First skill: pass task goal — sanitize for shell safety
const goal = `${task.title}\n${task.description}`
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\$/g, '\\$')
.replace(/`/g, '\\`');
args = `"${goal}"`;
// bugfix-hotfix: add --hotfix
if (task.task_type === 'bugfix-hotfix') {
args += ' --hotfix';
}
} else if (previousResult?.session_id) {
// Subsequent skills: chain session
args = `--session="${previousResult.session_id}"`;
}
// Propagate -y
if (autoYes && !args.includes('-y') && !args.includes('--yes')) {
args = args ? `${args} -y` : '-y';
}
return args;
}
formatDuration
function formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
return `${seconds}s`;
}
CLI-Assisted Analysis
IDAW integrates ccw cli (Gemini) for intelligent analysis at two key points:
Pre-Task Context Analysis
For bugfix, bugfix-hotfix, and feature-complex tasks, IDAW automatically invokes CLI analysis before executing the skill chain to gather codebase context:
Task starts → CLI pre-analysis (gemini) → Context gathered → Skill chain executes
- Identifies dependencies and risk areas
- Notes existing patterns to follow
- Results stored in
task.execution.skill_resultsascli-pre-analysis
Error Recovery with CLI Diagnosis
When a skill fails, instead of blind retry, IDAW uses CLI-assisted diagnosis:
Skill fails → CLI diagnosis (gemini, analysis-diagnose-bug-root-cause)
→ Root cause identified → Retry with diagnosis context
→ Still fails → Skip (autoYes) or Ask user (interactive)
- Uses
--rule analysis-diagnose-bug-root-causetemplate - Diagnosis results stored in
task.execution.skill_resultsascli-diagnosis:{skill} - Follows CLAUDE.md auto-invoke trigger pattern: "self-repair fails → invoke CLI analysis"
Execution Flow (with CLI analysis)
Phase 4 Main Loop (per task):
├─ [bugfix/complex only] CLI pre-analysis → context summary
├─ Skill 1: execute
│ ├─ Success → next skill
│ └─ Failure → CLI diagnosis → retry → success/fail
├─ Skill 2: execute ...
└─ Phase 5: git checkpoint
Examples
# Execute all pending tasks
/idaw:run -y
# Execute specific tasks
/idaw:run --task IDAW-001,IDAW-003
# Dry run (show plan without executing)
/idaw:run --dry-run
# Interactive mode (confirm at each step)
/idaw:run