mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
refactor: reduce documentation report, consolidate overlapping content
- Eliminate redundant Stop-Action explanations (moved to CLI Execution Model) - Remove verbose hook/error handling examples (keep in code only) - Consolidate 5-step CLI example into 1-line pattern - Simplify handleCliCompletion function comments - Streamline executor loop exit notes - Maintain all core information (no content loss) - Reduce report from ~1000 lines to ~900 lines Changes: - -143 lines (old verbose explanations) - +21 lines (consolidated content) - net: -122 lines
This commit is contained in:
@@ -321,18 +321,14 @@ async function executeCommandChain(chain, analysis) {
|
|||||||
prompt: prompt
|
prompt: prompt
|
||||||
});
|
});
|
||||||
|
|
||||||
// Execute via ccw cli (background, serial blocking with stop action)
|
// Execute CLI command in background and stop
|
||||||
try {
|
try {
|
||||||
console.log(`Executing: ${cmd.command}`);
|
|
||||||
|
|
||||||
// Execute CLI command in background
|
|
||||||
// Result will arrive via hook callback (do NOT poll)
|
|
||||||
const taskId = Bash(
|
const taskId = Bash(
|
||||||
`ccw cli -p "${escapePrompt(prompt)}" --tool claude --mode write -y`,
|
`ccw cli -p "${escapePrompt(prompt)}" --tool claude --mode write -y`,
|
||||||
{ run_in_background: true }
|
{ run_in_background: true }
|
||||||
).task_id;
|
).task_id;
|
||||||
|
|
||||||
// Save state with pending result (checkpoint)
|
// Save checkpoint
|
||||||
state.execution_results.push({
|
state.execution_results.push({
|
||||||
index: i,
|
index: i,
|
||||||
command: cmd.command,
|
command: cmd.command,
|
||||||
@@ -342,15 +338,12 @@ async function executeCommandChain(chain, analysis) {
|
|||||||
artifacts: [],
|
artifacts: [],
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
state.command_chain[i].status = 'running';
|
state.command_chain[i].status = 'running';
|
||||||
state.updated_at = new Date().toISOString();
|
state.updated_at = new Date().toISOString();
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
||||||
|
|
||||||
// STOP - CLI executes in background, break loop immediately
|
console.log(`[${i+1}/${chain.length}] ${cmd.command}\n`);
|
||||||
// Hook callback will call handleCliCompletion → resumeChainExecution
|
break; // Stop, wait for hook callback
|
||||||
console.log(`[${i+1}/${chain.length}] Waiting for CLI result...\n`);
|
|
||||||
break; // Serial blocking: one command at a time
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.command_chain[i].status = 'failed';
|
state.command_chain[i].status = 'failed';
|
||||||
@@ -381,22 +374,15 @@ async function executeCommandChain(chain, analysis) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save state checkpoint after each iteration
|
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no error, orchestrator will continue via hook callbacks
|
// Hook callbacks handle completion
|
||||||
// Do NOT set status to 'completed' here - hook callback handles final completion
|
if (state.status !== 'failed') state.status = 'waiting';
|
||||||
if (state.status !== 'failed' && state.execution_results.length < state.command_chain.length) {
|
|
||||||
state.status = 'waiting'; // Waiting for hook callbacks
|
|
||||||
}
|
|
||||||
state.updated_at = new Date().toISOString();
|
state.updated_at = new Date().toISOString();
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
||||||
|
|
||||||
console.log(`\n📋 Orchestrator paused - waiting for CLI callbacks`);
|
console.log(`\n📋 Orchestrator paused: ${state.session_id}\n`);
|
||||||
console.log(`Session: ${state.session_id}`);
|
|
||||||
console.log(`State: ${stateDir}/state.json\n`);
|
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,24 +452,21 @@ function formatCommand(cmd, previousResults, analysis) {
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook callback handler for CLI completion
|
// Hook callback: Called when background CLI completes
|
||||||
// IMPORTANT: This is called by user's hook when CLI finishes
|
|
||||||
async function handleCliCompletion(sessionId, taskId, output) {
|
async function handleCliCompletion(sessionId, taskId, output) {
|
||||||
// Load current state
|
|
||||||
const stateDir = `.workflow/.ccw-coordinator/${sessionId}`;
|
const stateDir = `.workflow/.ccw-coordinator/${sessionId}`;
|
||||||
const state = JSON.parse(Read(`${stateDir}/state.json`));
|
const state = JSON.parse(Read(`${stateDir}/state.json`));
|
||||||
|
|
||||||
// Find the pending result
|
|
||||||
const pendingIdx = state.execution_results.findIndex(r => r.task_id === taskId);
|
const pendingIdx = state.execution_results.findIndex(r => r.task_id === taskId);
|
||||||
if (pendingIdx === -1) {
|
if (pendingIdx === -1) {
|
||||||
console.error(`Unknown task_id: ${taskId}`);
|
console.error(`Unknown task_id: ${taskId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse CLI output
|
|
||||||
const parsed = parseOutput(output);
|
const parsed = parseOutput(output);
|
||||||
|
const cmdIdx = state.execution_results[pendingIdx].index;
|
||||||
|
|
||||||
// Update execution result
|
// Update result
|
||||||
state.execution_results[pendingIdx] = {
|
state.execution_results[pendingIdx] = {
|
||||||
...state.execution_results[pendingIdx],
|
...state.execution_results[pendingIdx],
|
||||||
status: parsed.sessionId ? 'completed' : 'failed',
|
status: parsed.sessionId ? 'completed' : 'failed',
|
||||||
@@ -491,26 +474,18 @@ async function handleCliCompletion(sessionId, taskId, output) {
|
|||||||
artifacts: parsed.artifacts,
|
artifacts: parsed.artifacts,
|
||||||
completed_at: new Date().toISOString()
|
completed_at: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update command_chain status
|
|
||||||
const cmdIdx = state.execution_results[pendingIdx].index;
|
|
||||||
state.command_chain[cmdIdx].status = parsed.sessionId ? 'completed' : 'failed';
|
state.command_chain[cmdIdx].status = parsed.sessionId ? 'completed' : 'failed';
|
||||||
state.updated_at = new Date().toISOString();
|
state.updated_at = new Date().toISOString();
|
||||||
|
|
||||||
// Save updated state
|
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
||||||
|
|
||||||
// Continue to next command in chain
|
// Trigger next command or complete
|
||||||
const nextIdx = cmdIdx + 1;
|
const nextIdx = cmdIdx + 1;
|
||||||
if (nextIdx < state.command_chain.length) {
|
if (nextIdx < state.command_chain.length) {
|
||||||
console.log(`\n✓ ${parsed.sessionId || 'Failed'} - Continuing to next command...\n`);
|
|
||||||
// Resume orchestrator from checkpoint (implementation-specific)
|
|
||||||
await resumeChainExecution(sessionId, nextIdx);
|
await resumeChainExecution(sessionId, nextIdx);
|
||||||
} else {
|
} else {
|
||||||
console.log(`\n✅ All commands completed! Session: ${sessionId}\n`);
|
|
||||||
state.status = 'completed';
|
state.status = 'completed';
|
||||||
state.updated_at = new Date().toISOString();
|
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
||||||
|
console.log(`✅ Completed: ${sessionId}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,118 +854,21 @@ async function ccwCoordinator(taskDescription) {
|
|||||||
5. **User Control** - Confirmation + error handling with user choice
|
5. **User Control** - Confirmation + error handling with user choice
|
||||||
6. **Context Passing** - Each prompt includes previous results
|
6. **Context Passing** - Each prompt includes previous results
|
||||||
7. **Resumable** - Can load state.json to continue
|
7. **Resumable** - Can load state.json to continue
|
||||||
8. **Serial Blocking Execution** - Commands execute one-by-one with stop-action blocking (no polling)
|
8. **Serial Blocking** - Commands execute one-by-one with hook-based continuation
|
||||||
|
|
||||||
## CLI Execution Mechanism
|
## CLI Execution Model
|
||||||
|
|
||||||
### Execution Model: Serial Blocking with Stop Action
|
**Serial Blocking**: Commands execute one-by-one. After launching CLI in background, orchestrator stops immediately and waits for hook callback.
|
||||||
|
|
||||||
```
|
|
||||||
Command 1 Start → Background Execution → STOP (save checkpoint)
|
|
||||||
↓
|
|
||||||
Hook Callback
|
|
||||||
↓
|
|
||||||
Command 2 Start → Background Execution → STOP (save checkpoint)
|
|
||||||
↓
|
|
||||||
Hook Callback
|
|
||||||
↓
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Principles**:
|
|
||||||
- ✅ **Serial**: Commands execute one-by-one (not parallel)
|
|
||||||
- ✅ **Blocking**: Each command waits for completion before next
|
|
||||||
- ✅ **Stop Action**: No polling - execution stops and waits for hook callback
|
|
||||||
- ✅ **Checkpoint**: State saved after each command launch
|
|
||||||
- ❌ **No Polling**: Never use `TaskOutput` to poll for results
|
|
||||||
|
|
||||||
### CLI Invocation Example (Claude Tool)
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Step 1: Prepare prompt
|
// Example: Execute command and stop
|
||||||
const prompt = `Task: Implement user registration
|
const taskId = Bash(`ccw cli -p "..." --tool claude --mode write -y`, { run_in_background: true }).task_id;
|
||||||
|
state.execution_results.push({ status: 'in-progress', task_id: taskId, ... });
|
||||||
Previous results:
|
|
||||||
- /workflow:plan: WFS-plan-20250124 (IMPL_PLAN.md)
|
|
||||||
|
|
||||||
/workflow:execute --yes --resume-session="WFS-plan-20250124"`;
|
|
||||||
|
|
||||||
// Step 2: Execute CLI in background
|
|
||||||
const taskId = Bash(
|
|
||||||
`ccw cli -p "${escapePrompt(prompt)}" --tool claude --mode write -y`,
|
|
||||||
{ run_in_background: true }
|
|
||||||
).task_id;
|
|
||||||
|
|
||||||
// Step 3: Save checkpoint
|
|
||||||
state.execution_results.push({
|
|
||||||
index: 1,
|
|
||||||
command: '/workflow:execute',
|
|
||||||
status: 'in-progress',
|
|
||||||
task_id: taskId,
|
|
||||||
timestamp: '2025-01-24T14:32:00Z'
|
|
||||||
});
|
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
||||||
|
break; // Stop, wait for hook callback
|
||||||
|
|
||||||
// Step 4: STOP - Output immediately stops here
|
// Hook calls handleCliCompletion(sessionId, taskId, output) when done
|
||||||
console.log(`[2/3] Waiting for CLI result (${taskId})...\n`);
|
// → Updates state → Triggers next command via resumeChainExecution()
|
||||||
|
|
||||||
// Step 5: Hook callback will call handleCliCompletion(sessionId, taskId, output)
|
|
||||||
// when CLI finishes, which will:
|
|
||||||
// - Parse output for WFS-* session ID
|
|
||||||
// - Update state.execution_results with result
|
|
||||||
// - Trigger next command in chain
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hook Configuration
|
|
||||||
|
|
||||||
User should configure hook in `~/.claude/hooks/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ~/.claude/hooks/task-complete.sh
|
|
||||||
#!/bin/bash
|
|
||||||
# Called when background task completes
|
|
||||||
|
|
||||||
TASK_ID="$1"
|
|
||||||
OUTPUT="$2"
|
|
||||||
SESSION_ID=$(grep -o 'ccw-coord-[0-9]\+' .workflow/.ccw-coordinator/*/state.json | head -1 | cut -d: -f2)
|
|
||||||
|
|
||||||
# Trigger orchestrator callback
|
|
||||||
node <<EOF
|
|
||||||
const { handleCliCompletion } = require('./.claude/commands/ccw-coordinator');
|
|
||||||
handleCliCompletion('${SESSION_ID}', '${TASK_ID}', \`${OUTPUT}\`);
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling via Hook
|
|
||||||
|
|
||||||
If CLI fails, hook callback receives error:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In handleCliCompletion
|
|
||||||
if (!parsed.sessionId) {
|
|
||||||
// CLI failed - prompt user
|
|
||||||
const action = await AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `CLI command failed. What to do?`,
|
|
||||||
header: 'CLI Error',
|
|
||||||
options: [
|
|
||||||
{ label: 'Retry', description: 'Re-run command' },
|
|
||||||
{ label: 'Skip', description: 'Continue to next' },
|
|
||||||
{ label: 'Abort', description: 'Stop workflow' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle user choice
|
|
||||||
if (action.error === 'Retry') {
|
|
||||||
await resumeChainExecution(sessionId, cmdIdx); // Same command
|
|
||||||
} else if (action.error === 'Skip') {
|
|
||||||
await resumeChainExecution(sessionId, cmdIdx + 1); // Next command
|
|
||||||
} else {
|
|
||||||
state.status = 'failed';
|
|
||||||
Write(`${stateDir}/state.json`, JSON.stringify(state, null, 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Commands
|
## Available Commands
|
||||||
|
|||||||
Reference in New Issue
Block a user