feat(workflow): add multi-CLI collaborative planning command

- Introduced a new command `/workflow:multi-cli-plan` for collaborative planning using ACE semantic search and iterative analysis with Claude and Codex.
- Implemented a structured execution flow with phases for context gathering, multi-tool analysis, user decision points, and final plan generation.
- Added detailed documentation outlining the command's usage, execution phases, and key features.
- Included error handling and configuration options for enhanced user experience.
This commit is contained in:
catlog22
2026-01-13 23:23:09 +08:00
parent 2f1c56285a
commit c3da637849
7 changed files with 2669 additions and 196 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -17,21 +17,21 @@ Minimal orchestrator that dispatches **solution IDs** to executors. Each executo
- `done <id>` → update solution completion status
- No race conditions: status changes only via `done`
- **Executor handles all tasks within a solution sequentially**
- **Worktree isolation**: Each executor can work in its own git worktree
- **Single worktree for entire queue**: One worktree isolates ALL queue execution from main workspace
## Usage
```bash
/issue:execute # Execute active queue(s)
/issue:execute --queue QUE-xxx # Execute specific queue
/issue:execute --worktree # Use git worktrees for parallel isolation
/issue:execute --worktree # Execute entire queue in isolated worktree
/issue:execute --worktree --queue QUE-xxx
/issue:execute --worktree /path/to/existing/worktree # Resume in existing worktree
```
**Parallelism**: Determined automatically by task dependency DAG (no manual control)
**Executor & Dry-run**: Selected via interactive prompt (AskUserQuestion)
**Worktree**: Creates isolated git worktrees for each parallel executor
**Worktree**: Creates ONE worktree for the entire queue execution (not per-solution)
**⭐ Recommended Executor**: **Codex** - Best for long-running autonomous work (2hr timeout), supports background execution and full write access
@@ -44,8 +44,10 @@ Minimal orchestrator that dispatches **solution IDs** to executors. Each executo
## Execution Flow
```
Phase 0 (if --worktree): Setup Worktree Base
Ensure .worktrees directory exists
Phase 0 (if --worktree): Setup Queue Worktree
Create ONE worktree for entire queue: .ccw/worktrees/queue-<timestamp>
├─ All subsequent execution happens in this worktree
└─ Main workspace remains clean and untouched
Phase 1: Get DAG & User Selection
├─ ccw issue queue dag [--queue QUE-xxx] → { parallel_batches: [["S-1","S-2"], ["S-3"]] }
@@ -53,19 +55,22 @@ Phase 1: Get DAG & User Selection
Phase 2: Dispatch Parallel Batch (DAG-driven)
├─ Parallelism determined by DAG (no manual limit)
├─ All executors work in the SAME worktree (or main if no worktree)
├─ For each solution ID in batch (parallel - all at once):
│ ├─ (if worktree) Create isolated worktree: git worktree add
│ ├─ Executor calls: ccw issue detail <id> (READ-ONLY)
│ ├─ Executor gets FULL SOLUTION with all tasks
│ ├─ Executor implements all tasks sequentially (T1 → T2 → T3)
│ ├─ Executor tests + verifies each task
│ ├─ Executor commits ONCE per solution (with formatted summary)
─ Executor calls: ccw issue done <id>
│ └─ (if worktree) Cleanup: merge branch, remove worktree
─ Executor calls: ccw issue done <id>
└─ Wait for batch completion
Phase 3: Next Batch
Phase 3: Next Batch (repeat Phase 2)
└─ ccw issue queue dag → check for newly-ready solutions
Phase 4 (if --worktree): Worktree Completion
├─ All batches complete → prompt for merge strategy
└─ Options: Create PR / Merge to main / Keep branch
```
## Implementation
@@ -115,12 +120,12 @@ const answer = AskUserQuestion({
]
},
{
question: 'Use git worktrees for parallel isolation?',
question: 'Use git worktree for queue isolation?',
header: 'Worktree',
multiSelect: false,
options: [
{ label: 'Yes (Recommended for parallel)', description: 'Each executor works in isolated worktree branch' },
{ label: 'No', description: 'Work directly in current directory (serial only)' }
{ label: 'Yes (Recommended)', description: 'Create ONE worktree for entire queue - main stays clean' },
{ label: 'No', description: 'Work directly in current directory' }
]
}
]
@@ -140,7 +145,7 @@ if (isDryRun) {
}
```
### Phase 2: Dispatch Parallel Batch (DAG-driven)
### Phase 0 & 2: Setup Queue Worktree & Dispatch
```javascript
// Parallelism determined by DAG - no manual limit
@@ -158,24 +163,40 @@ TodoWrite({
console.log(`\n### Executing Solutions (DAG batch 1): ${batch.join(', ')}`);
// Setup worktree base directory if needed (using absolute paths)
if (useWorktree) {
// Use absolute paths to avoid issues when running from subdirectories
const repoRoot = Bash('git rev-parse --show-toplevel').trim();
const worktreeBase = `${repoRoot}/.ccw/worktrees`;
Bash(`mkdir -p "${worktreeBase}"`);
// Prune stale worktrees from previous interrupted executions
Bash('git worktree prune');
}
// Parse existing worktree path from args if provided
// Example: --worktree /path/to/existing/worktree
const existingWorktree = args.worktree && typeof args.worktree === 'string' ? args.worktree : null;
// Setup ONE worktree for entire queue (not per-solution)
let worktreePath = null;
let worktreeBranch = null;
if (useWorktree) {
const repoRoot = Bash('git rev-parse --show-toplevel').trim();
const worktreeBase = `${repoRoot}/.ccw/worktrees`;
Bash(`mkdir -p "${worktreeBase}"`);
Bash('git worktree prune'); // Cleanup stale worktrees
if (existingWorktree) {
// Resume mode: Use existing worktree
worktreePath = existingWorktree;
worktreeBranch = Bash(`git -C "${worktreePath}" branch --show-current`).trim();
console.log(`Resuming in existing worktree: ${worktreePath} (branch: ${worktreeBranch})`);
} else {
// Create mode: ONE worktree for the entire queue
const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
worktreeBranch = `queue-exec-${dag.queue_id || timestamp}`;
worktreePath = `${worktreeBase}/${worktreeBranch}`;
Bash(`git worktree add "${worktreePath}" -b "${worktreeBranch}"`);
console.log(`Created queue worktree: ${worktreePath}`);
}
}
// Launch ALL solutions in batch in parallel (DAG guarantees no conflicts)
// All executors work in the SAME worktree (or main if no worktree)
const executions = batch.map(solutionId => {
updateTodo(solutionId, 'in_progress');
return dispatchExecutor(solutionId, executor, useWorktree, existingWorktree);
return dispatchExecutor(solutionId, executor, worktreePath);
});
await Promise.all(executions);
@@ -185,126 +206,20 @@ batch.forEach(id => updateTodo(id, 'completed'));
### Executor Dispatch
```javascript
function dispatchExecutor(solutionId, executorType, useWorktree = false, existingWorktree = null) {
// Worktree setup commands (if enabled) - using absolute paths
// Supports both creating new worktrees and resuming in existing ones
const worktreeSetup = useWorktree ? `
### Step 0: Setup Isolated Worktree
\`\`\`bash
# Use absolute paths to avoid issues when running from subdirectories
REPO_ROOT=$(git rev-parse --show-toplevel)
WORKTREE_BASE="\${REPO_ROOT}/.ccw/worktrees"
# Check if existing worktree path was provided
EXISTING_WORKTREE="${existingWorktree || ''}"
if [[ -n "\${EXISTING_WORKTREE}" && -d "\${EXISTING_WORKTREE}" ]]; then
# Resume mode: Use existing worktree
WORKTREE_PATH="\${EXISTING_WORKTREE}"
WORKTREE_NAME=$(basename "\${WORKTREE_PATH}")
# Verify it's a valid git worktree
if ! git -C "\${WORKTREE_PATH}" rev-parse --is-inside-work-tree &>/dev/null; then
echo "Error: \${EXISTING_WORKTREE} is not a valid git worktree"
exit 1
fi
echo "Resuming in existing worktree: \${WORKTREE_PATH}"
else
# Create mode: New worktree with timestamp
WORKTREE_NAME="exec-${solutionId}-$(date +%H%M%S)"
WORKTREE_PATH="\${WORKTREE_BASE}/\${WORKTREE_NAME}"
# Ensure worktree base exists
mkdir -p "\${WORKTREE_BASE}"
# Prune stale worktrees
git worktree prune
# Create worktree
git worktree add "\${WORKTREE_PATH}" -b "\${WORKTREE_NAME}"
echo "Created new worktree: \${WORKTREE_PATH}"
fi
# Setup cleanup trap for graceful failure handling
cleanup_worktree() {
echo "Cleaning up worktree due to interruption..."
cd "\${REPO_ROOT}" 2>/dev/null || true
git worktree remove "\${WORKTREE_PATH}" --force 2>/dev/null || true
echo "Worktree removed. Branch '\${WORKTREE_NAME}' kept for inspection."
}
trap cleanup_worktree EXIT INT TERM
cd "\${WORKTREE_PATH}"
\`\`\`
` : '';
const worktreeCleanup = useWorktree ? `
### Step 5: Worktree Completion (User Choice)
After all tasks complete, prompt for merge strategy:
\`\`\`javascript
AskUserQuestion({
questions: [{
question: "Solution ${solutionId} completed. What to do with worktree branch?",
header: "Merge",
multiSelect: false,
options: [
{ label: "Create PR (Recommended)", description: "Push branch and create pull request - safest for parallel execution" },
{ label: "Merge to main", description: "Merge branch and cleanup worktree (requires clean main)" },
{ label: "Keep branch", description: "Cleanup worktree, keep branch for manual handling" }
]
}]
})
\`\`\`
**Based on selection:**
\`\`\`bash
# Disable cleanup trap before intentional cleanup
trap - EXIT INT TERM
# Return to repo root (use REPO_ROOT from setup)
cd "\${REPO_ROOT}"
# Validate main repo state before merge
validate_main_clean() {
if [[ -n \$(git status --porcelain) ]]; then
echo "⚠️ Warning: Main repo has uncommitted changes."
echo "Cannot auto-merge. Falling back to 'Create PR' option."
return 1
fi
return 0
}
# Create PR (Recommended for parallel execution):
git push -u origin "\${WORKTREE_NAME}"
gh pr create --title "Solution ${solutionId}" --body "Issue queue execution"
git worktree remove "\${WORKTREE_PATH}"
# Merge to main (only if main is clean):
if validate_main_clean; then
git merge --no-ff "\${WORKTREE_NAME}" -m "Merge solution ${solutionId}"
git worktree remove "\${WORKTREE_PATH}" && git branch -d "\${WORKTREE_NAME}"
else
# Fallback to PR if main is dirty
git push -u origin "\${WORKTREE_NAME}"
gh pr create --title "Solution ${solutionId}" --body "Issue queue execution (main had uncommitted changes)"
git worktree remove "\${WORKTREE_PATH}"
fi
# Keep branch:
git worktree remove "\${WORKTREE_PATH}"
echo "Branch \${WORKTREE_NAME} kept for manual handling"
\`\`\`
**Parallel Execution Safety**: "Create PR" is the default and safest option for parallel executors, avoiding merge race conditions.
` : '';
// worktreePath: path to shared worktree (null if not using worktree)
function dispatchExecutor(solutionId, executorType, worktreePath = null) {
// If worktree is provided, executor works in that directory
// No per-solution worktree creation - ONE worktree for entire queue
const cdCommand = worktreePath ? `cd "${worktreePath}"` : '';
const prompt = `
## Execute Solution ${solutionId}
${worktreeSetup}
${worktreePath ? `
### Step 0: Enter Queue Worktree
\`\`\`bash
cd "${worktreePath}"
\`\`\`
` : ''}
### Step 1: Get Solution (read-only)
\`\`\`bash
ccw issue detail ${solutionId}
@@ -352,16 +267,21 @@ If any task failed:
\`\`\`bash
ccw issue done ${solutionId} --fail --reason '{"task_id": "TX", "error_type": "test_failure", "message": "..."}'
\`\`\`
${worktreeCleanup}`;
**Note**: Do NOT cleanup worktree after this solution. Worktree is shared by all solutions in the queue.
`;
// For CLI tools, pass --cd to set working directory
const cdOption = worktreePath ? ` --cd "${worktreePath}"` : '';
if (executorType === 'codex') {
return Bash(
`ccw cli -p "${escapePrompt(prompt)}" --tool codex --mode write --id exec-${solutionId}`,
`ccw cli -p "${escapePrompt(prompt)}" --tool codex --mode write --id exec-${solutionId}${cdOption}`,
{ timeout: 7200000, run_in_background: true } // 2hr for full solution
);
} else if (executorType === 'gemini') {
return Bash(
`ccw cli -p "${escapePrompt(prompt)}" --tool gemini --mode write --id exec-${solutionId}`,
`ccw cli -p "${escapePrompt(prompt)}" --tool gemini --mode write --id exec-${solutionId}${cdOption}`,
{ timeout: 3600000, run_in_background: true }
);
} else {
@@ -369,7 +289,7 @@ ${worktreeCleanup}`;
subagent_type: 'code-developer',
run_in_background: false,
description: `Execute solution ${solutionId}`,
prompt: prompt
prompt: worktreePath ? `Working directory: ${worktreePath}\n\n${prompt}` : prompt
});
}
}
@@ -390,40 +310,98 @@ console.log(`
if (refreshedDag.ready_count > 0) {
console.log('Run `/issue:execute` again for next batch.');
// Note: If resuming, pass existing worktree path:
// /issue:execute --worktree <worktreePath>
}
```
### Phase 4: Worktree Completion (after ALL batches)
```javascript
// Only run when ALL solutions completed AND using worktree
if (useWorktree && refreshedDag.ready_count === 0 && refreshedDag.completed_count === refreshedDag.total) {
console.log('\n## All Solutions Completed - Worktree Cleanup');
const answer = AskUserQuestion({
questions: [{
question: `Queue complete. What to do with worktree branch "${worktreeBranch}"?`,
header: 'Merge',
multiSelect: false,
options: [
{ label: 'Create PR (Recommended)', description: 'Push branch and create pull request' },
{ label: 'Merge to main', description: 'Merge all commits and cleanup worktree' },
{ label: 'Keep branch', description: 'Cleanup worktree, keep branch for manual handling' }
]
}]
});
const repoRoot = Bash('git rev-parse --show-toplevel').trim();
if (answer['Merge'].includes('Create PR')) {
Bash(`git -C "${worktreePath}" push -u origin "${worktreeBranch}"`);
Bash(`gh pr create --title "Queue ${dag.queue_id}" --body "Issue queue execution - all solutions completed" --head "${worktreeBranch}"`);
Bash(`git worktree remove "${worktreePath}"`);
console.log(`PR created for branch: ${worktreeBranch}`);
} else if (answer['Merge'].includes('Merge to main')) {
// Check main is clean
const mainDirty = Bash('git status --porcelain').trim();
if (mainDirty) {
console.log('Warning: Main has uncommitted changes. Falling back to PR.');
Bash(`git -C "${worktreePath}" push -u origin "${worktreeBranch}"`);
Bash(`gh pr create --title "Queue ${dag.queue_id}" --body "Issue queue execution (main had uncommitted changes)" --head "${worktreeBranch}"`);
} else {
Bash(`git merge --no-ff "${worktreeBranch}" -m "Merge queue ${dag.queue_id}"`);
Bash(`git branch -d "${worktreeBranch}"`);
}
Bash(`git worktree remove "${worktreePath}"`);
} else {
Bash(`git worktree remove "${worktreePath}"`);
console.log(`Branch ${worktreeBranch} kept for manual handling`);
}
}
```
## Parallel Execution Model
```
┌─────────────────────────────────────────────────────────────┐
│ Orchestrator │
├─────────────────────────────────────────────────────────────┤
1. ccw issue queue dag
│ → { parallel_batches: [["S-1","S-2"], ["S-3"]] }
│ │
2. Dispatch batch 1 (parallel):
┌──────────────────────┐ ┌──────────────────────┐
│ Executor 1 │ │ Executor 2
│ detail S-1 │ │ detail S-2
│ → gets full solution │ │ → gets full solution │
│ │ [T1→T2→T3 sequential]│ │ [T1→T2 sequential]
│ │ commit (1x solution) │ │ commit (1x solution) │
│ │ done S-1 │ │ done S-2
└──────────────────────┘ └──────────────────────┘
3. ccw issue queue dag (refresh)
→ S-3 now ready (S-1 completed, file conflict resolved)
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────
│ Orchestrator
├─────────────────────────────────────────────────────────────────
0. (if --worktree) Create ONE worktree for entire queue
│ → .ccw/worktrees/queue-exec-<queue-id>
1. ccw issue queue dag
→ { parallel_batches: [["S-1","S-2"], ["S-3"]] }
2. Dispatch batch 1 (parallel, SAME worktree):
┌──────────────────────────────────────────────────────┐
│ │ Shared Queue Worktree (or main) │ │
│ │ ┌──────────────────┐ ┌──────────────────┐
│ │ │ Executor 1 │ │ Executor 2 │ │ │
│ │ detail S-1 │ │ detail S-2 │ │
│ [T1→T2→T3] │ │ [T1→T2]
│ │ commit S-1 │ │ commit S-2
│ │ done S-1 │ │ done S-2 │ │
│ │ └──────────────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 3. ccw issue queue dag (refresh) │
│ → S-3 now ready → dispatch batch 2 (same worktree) │
│ │
│ 4. (if --worktree) ALL batches complete → cleanup worktree │
│ → Prompt: Create PR / Merge to main / Keep branch │
└─────────────────────────────────────────────────────────────────┘
```
**Why this works for parallel:**
- **ONE worktree for entire queue** → all solutions share same isolated workspace
- `detail <id>` is READ-ONLY → no race conditions
- Each executor handles **all tasks within a solution** sequentially
- **One commit per solution** with formatted summary (not per-task)
- `done <id>` updates only its own solution status
- `queue dag` recalculates ready solutions after each batch
- Solutions in same batch have NO file conflicts
- Solutions in same batch have NO file conflicts (DAG guarantees)
- **Main workspace stays clean** until merge/PR decision
## CLI Endpoint Contract

View File

@@ -0,0 +1,891 @@
---
name: workflow:multi-cli-plan
description: Multi-CLI collaborative planning workflow using ACE semantic search and iterative Claude+Codex analysis to determine execution plan. Features user-driven decision points and convergent refinement.
argument-hint: "<task description> [--max-rounds=3] [--tools=gemini,codex]"
allowed-tools: TodoWrite(*), Task(*), AskUserQuestion(*), Read(*), Bash(*), Glob(*), Grep(*), mcp__ace-tool__search_context(*)
---
# Multi-CLI Collaborative Planning Command (/workflow:multi-cli-plan)
## Overview
Multi-CLI collaborative planning workflow that uses ACE semantic search for context gathering, followed by iterative multi-tool analysis (Claude + Codex/Gemini) to converge on an optimal execution plan.
**Core Philosophy**:
- **Multi-round Verification**: Claude and Codex alternate analysis to ensure solutions match codebase reality
- **User-driven**: Every analysis round ends with user decision point
- **Iterative Convergence**: Multiple cycles progressively refine requirements and solutions
- **Final Confirmation**: Executable plan only output after explicit user approval
**Core Capabilities**:
- ACE semantic search for comprehensive codebase context
- Multi-tool collaborative analysis (Claude + Gemini/Codex)
- Interactive refinement with user feedback loops
- Solution comparison with trade-off analysis
- Final executable plan with file locations and acceptance criteria
## Usage
```bash
/workflow:multi-cli-plan <task-description>
# With options
/workflow:multi-cli-plan "Implement user authentication" --max-rounds=3
/workflow:multi-cli-plan "Refactor payment module" --tools=gemini,codex
# Examples
/workflow:multi-cli-plan "Add dark mode support to the application"
/workflow:multi-cli-plan "Fix the memory leak in WebSocket connections"
/workflow:multi-cli-plan "Implement rate limiting for API endpoints"
```
## Execution Flow
```
Phase 1: Input & Context Gathering
|-- Parse user task description
|-- ACE semantic search for codebase context
|-- Build initial context package
+-- Initialize discussion session
Phase 2: Multi-CLI Collaborative Analysis (Iterative)
|-- Round N:
| |-- Claude Analysis: Architecture perspective
| |-- Codex/Gemini Analysis: Implementation perspective
| |-- Cross-verify technical feasibility
| +-- Synthesize multiple implementation approaches
|
+-- Loop until convergence or max rounds
Phase 3: Stage Summary & Options
|-- Present 2-3 viable solution options with trade-offs
|-- Proactively ask clarifying questions for ambiguities
+-- Wait for user feedback
Phase 4: User Decision Point
|-- Option A: User approves current approach -> Phase 5
|-- Option B: User provides clarification/adjustments -> Return to Phase 2
+-- Option C: User requests different direction -> Reset analysis
Phase 5: Agent Planning & Output Generation
|-- Invoke cli-lite-planning-agent with discussion context
|-- Generate IMPL_PLAN.md (documentation)
|-- Generate plan.json (structured plan for execution)
|-- User confirms execution
+-- Hand off to /workflow:lite-execute
```
## Implementation
### Phase 1: Input & Context Gathering
**Session Initialization**:
```javascript
// Helper: Get UTC+8 (China Standard Time) ISO string
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
// Parse arguments
const { taskDescription, maxRounds, tools } = parseArgs(args)
const effectiveMaxRounds = maxRounds || 3
const effectiveTools = tools || ['gemini', 'codex']
// Generate session ID
const taskSlug = taskDescription.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
const dateStr = getUtc8ISOString().substring(0, 10)
const sessionId = `MCP-${taskSlug}-${dateStr}`
const sessionFolder = `.workflow/.multi-cli-plan/${sessionId}`
// Create session folder
Bash(`mkdir -p ${sessionFolder}/rounds && test -d ${sessionFolder} && echo "SUCCESS: ${sessionFolder}"`)
// Initialize session state
const sessionState = {
session_id: sessionId,
task_description: taskDescription,
created_at: getUtc8ISOString(),
max_rounds: effectiveMaxRounds,
tools: effectiveTools,
current_round: 0,
phase: 'context-gathering',
rounds: [],
solutions: [],
user_decisions: [],
final_plan: null
}
Write(`${sessionFolder}/session-state.json`, JSON.stringify(sessionState, null, 2))
```
**ACE Context Gathering**:
```javascript
// Step 1: Extract keywords from task description
const keywords = extractKeywords(taskDescription)
// e.g., "Add dark mode support" -> ["dark", "mode", "theme", "style", "color"]
// Step 2: Use ACE to understand codebase structure and relevant code
const aceQueries = [
// Architecture query
`Project architecture and module structure related to ${keywords.slice(0, 3).join(', ')}`,
// Implementation query
`Existing implementations of ${keywords[0]} in this codebase`,
// Pattern query
`Code patterns and conventions for ${keywords.slice(0, 2).join(' ')} features`,
// Integration query
`Integration points and dependencies for ${keywords[0]} functionality`
]
const aceResults = []
for (const query of aceQueries) {
const result = await mcp__ace-tool__search_context({
project_root_path: process.cwd(),
query: query
})
aceResults.push({ query, result, timestamp: getUtc8ISOString() })
}
// Step 3: Build context package (kept in memory for CLI consumption)
const contextPackage = {
task_description: taskDescription,
keywords: keywords,
ace_results: aceResults,
relevant_files: extractRelevantFiles(aceResults),
detected_patterns: extractPatterns(aceResults),
architecture_insights: aceResults[0].result,
existing_implementations: aceResults[1].result
}
// Update session state
sessionState.phase = 'context-gathered'
sessionState.context_summary = {
files_identified: contextPackage.relevant_files.length,
patterns_detected: contextPackage.detected_patterns.length,
ace_queries: aceQueries.length
}
```
---
### Phase 2: Agent-Driven Collaborative Analysis
**Core Principle**: Orchestrator delegates all analysis to `cli-discuss-agent`, only reads output files for decision making.
**Analysis Round Loop**:
```javascript
let currentRound = 0
let shouldContinue = true
let analysisResults = []
while (shouldContinue && currentRound < effectiveMaxRounds) {
currentRound++
console.log(`
## Analysis Round ${currentRound}/${effectiveMaxRounds}
Delegating to cli-discuss-agent...
`)
// ========================================
// DELEGATE TO AGENT - No direct analysis
// ========================================
Task({
subagent_type: "cli-discuss-agent",
run_in_background: false,
description: `Discussion round ${currentRound}`,
prompt: `
## Task Objective
Execute collaborative discussion round ${currentRound} for task analysis.
## Input Context
- **Task Description**: ${taskDescription}
- **Round Number**: ${currentRound}
- **Session ID**: ${sessionId}
- **Session Folder**: ${sessionFolder}
## ACE Context
${JSON.stringify(contextPackage, null, 2)}
## Previous Rounds
${analysisResults.length > 0
? analysisResults.map(r => `Round ${r.round}: ${r.summary}`).join('\n')
: 'None (first round)'}
## User Feedback
${userFeedback || 'None'}
## CLI Configuration
- Tools: ${effectiveTools.join(', ')}
- Timeout: 600000ms
- Fallback Chain: gemini → codex → qwen
## Output Requirements
Write: ${sessionFolder}/rounds/${currentRound}/synthesis.json
Follow cli-discuss-agent output schema exactly.
## Success Criteria
- [ ] All configured CLI tools executed
- [ ] Cross-verification completed
- [ ] 2-3 solution options generated
- [ ] Convergence score calculated
- [ ] synthesis.json written to round folder
`
})
// ========================================
// READ AGENT OUTPUT - Decision making only
// ========================================
const synthesisPath = `${sessionFolder}/rounds/${currentRound}/synthesis.json`
const roundSynthesis = JSON.parse(Read(synthesisPath))
analysisResults.push(roundSynthesis)
// Update session state from agent output
sessionState.rounds.push({
number: currentRound,
cli_tools_used: roundSynthesis._metadata.cli_tools_used,
solutions_identified: roundSynthesis.solutions.length,
convergence_score: roundSynthesis.convergence.score,
new_insights: roundSynthesis.convergence.new_insights,
recommendation: roundSynthesis.convergence.recommendation
})
// Display round summary
console.log(`
### Round ${currentRound} Complete
**Convergence**: ${roundSynthesis.convergence.score.toFixed(2)}
**Solutions Found**: ${roundSynthesis.solutions.length}
**Recommendation**: ${roundSynthesis.convergence.recommendation}
**Solutions**:
${roundSynthesis.solutions.map((s, i) => `${i+1}. ${s.name} (${s.effort} effort, ${s.risk} risk)`).join('\n')}
`)
// Decide whether to continue based on agent's recommendation
if (roundSynthesis.convergence.recommendation === 'converged') {
shouldContinue = false
console.log('Analysis converged. Proceeding to decision phase.')
} else if (roundSynthesis.convergence.recommendation === 'user_input_needed') {
// Collect user feedback before next round
const feedbackResult = await AskUserQuestion({
questions: [{
question: 'Clarification needed. How would you like to proceed?',
header: 'Feedback',
multiSelect: false,
options: [
{ label: 'Provide Clarification', description: 'Answer questions and continue analysis' },
{ label: 'Proceed Anyway', description: 'Accept current solutions' },
{ label: 'Change Direction', description: 'Modify task requirements' }
]
}]
})
if (feedbackResult === 'Provide Clarification') {
// Display clarification questions
console.log(`
### Clarification Questions
${roundSynthesis.clarification_questions.map((q, i) => `${i+1}. ${q}`).join('\n')}
`)
// User provides feedback via "Other" option or follow-up
userFeedback = feedbackResult.other || ''
} else if (feedbackResult === 'Proceed Anyway') {
shouldContinue = false
} else {
// Reset with new direction
userFeedback = feedbackResult.other || ''
}
} else {
// Continue to next round
shouldContinue = roundSynthesis.convergence.new_insights && currentRound < effectiveMaxRounds
}
}
// Get final synthesis from last round
const finalSynthesis = analysisResults[analysisResults.length - 1]
```
---
### Phase 3: Review Agent Output & Present Options
**Core Principle**: Orchestrator only reads agent output files and formats them for user decision.
**Read and Present Solutions**:
```javascript
// ========================================
// READ FINAL AGENT OUTPUT - No processing
// ========================================
// finalSynthesis already loaded from agent's synthesis.json in Phase 2
console.log(`
## Stage Summary
### Analysis Complete (from cli-discuss-agent output)
- Rounds completed: ${currentRound}
- CLI tools used: ${finalSynthesis._metadata.cli_tools_used.join(', ')}
- Cross-verification: ${finalSynthesis.cross_verification.agreements.length} agreements, ${finalSynthesis.cross_verification.disagreements.length} disagreements
- Convergence score: ${finalSynthesis.convergence.score.toFixed(2)}
### Solution Options (from agent synthesis)
${finalSynthesis.solutions.map((solution, index) => `
**Option ${index + 1}: ${solution.name}**
*Source: ${solution.source_cli.join(' + ')}*
Description: ${solution.description}
Trade-offs:
| Aspect | Assessment |
|--------|------------|
| Effort | ${solution.effort} |
| Risk | ${solution.risk} |
| Maintainability | ${solution.maintainability} |
| Performance | ${solution.performance_impact} |
Pros:
${solution.pros.map(p => `- ${p}`).join('\n')}
Cons:
${solution.cons.map(c => `- ${c}`).join('\n')}
Key files affected:
${solution.affected_files.slice(0, 5).map(f => `- ${f.file}:${f.line} - ${f.reason}`).join('\n')}
`).join('\n---\n')}
### Cross-Verification Summary
**Agreements**:
${finalSynthesis.cross_verification.agreements.slice(0, 5).map(a => `- ${a}`).join('\n')}
**Disagreements** (resolved):
${finalSynthesis.cross_verification.disagreements.slice(0, 3).map(d => `- ${d}`).join('\n') || '- None'}
### Clarification Questions (from agent)
${finalSynthesis.clarification_questions.length > 0
? finalSynthesis.clarification_questions.map((q, i) => `${i + 1}. ${q}`).join('\n')
: 'No clarifications needed.'}
`)
// Update session state with agent's findings
sessionState.solutions = finalSynthesis.solutions
sessionState.cross_verification = finalSynthesis.cross_verification
sessionState.phase = 'awaiting-decision'
Write(`${sessionFolder}/session-state.json`, JSON.stringify(sessionState, null, 2))
```
---
### Phase 4: User Decision Point
**Collect User Decision**:
```javascript
const decisionResult = await AskUserQuestion({
questions: [
{
question: `Which solution approach do you prefer?`,
header: "Solution",
multiSelect: false,
options: finalSynthesis.solutions.map((sol, i) => ({
label: `Option ${i + 1}: ${sol.name}`,
description: `${sol.effort} effort, ${sol.risk} risk`
})).concat([
{ label: "Need More Analysis", description: "Return to analysis with additional context" }
])
},
{
question: "Any clarifications or adjustments?",
header: "Feedback",
multiSelect: true,
options: [
{ label: "Proceed as-is", description: "Generate final plan with selected option" },
{ label: "Add constraints", description: "Specify additional requirements" },
{ label: "Change scope", description: "Adjust what's included/excluded" },
{ label: "Different direction", description: "Explore completely different approach" }
]
}
]
})
// Process decision
const userDecision = {
timestamp: getUtc8ISOString(),
selected_solution: decisionResult.solution,
feedback_type: decisionResult.feedback,
additional_input: decisionResult.other || null
}
sessionState.user_decisions.push(userDecision)
// Decision routing
if (userDecision.selected_solution === 'Need More Analysis' ||
userDecision.feedback_type.includes('Different direction')) {
// Return to Phase 2 with updated context
sessionState.phase = 'additional-analysis'
// Continue analysis loop with user feedback incorporated
} else if (userDecision.feedback_type.includes('Add constraints') ||
userDecision.feedback_type.includes('Change scope')) {
// Prompt for additional details
const additionalInput = await AskUserQuestion({
questions: [{
question: "Please provide the additional constraints or scope changes:",
header: "Details",
multiSelect: false,
options: [
{ label: "Performance priority", description: "Optimize for speed over simplicity" },
{ label: "Maintainability priority", description: "Prefer clear, maintainable code" },
{ label: "Minimal changes", description: "Change as few files as possible" },
{ label: "Full refactor OK", description: "Willing to do comprehensive changes" }
]
}]
})
// Incorporate and proceed to Phase 5
userDecision.constraints = additionalInput
sessionState.phase = 'generating-plan'
} else {
// Proceed to Phase 5
sessionState.phase = 'generating-plan'
}
```
---
### Phase 5: Agent Planning & Output Generation
**Step 5.1: Prepare Planning Context**
```javascript
// Select the approved solution
const selectedSolution = finalSynthesis.solutions[userDecision.selected_solution_index]
// Build comprehensive planning context from discussion
const planningContext = {
task_description: taskDescription,
selected_solution: selectedSolution,
analysis_rounds: analysisResults,
consensus_points: finalSynthesis.consensus_points,
user_constraints: userDecision.constraints || null,
ace_context: contextPackage,
clarifications: sessionState.user_decisions
}
console.log(`
## Generating Implementation Plan
Selected approach: **${selectedSolution.name}**
Invoking planning agent...
`)
```
**Step 5.2: Invoke cli-lite-planning-agent**
```javascript
// Call planning agent to generate detailed plan
Task({
subagent_type: "cli-lite-planning-agent",
run_in_background: false,
description: "Generate detailed implementation plan",
prompt: `
## Task Objective
Generate detailed implementation plan based on collaborative discussion results.
## Output Schema Reference
Execute: cat ~/.claude/workflows/cli-templates/schemas/plan-json-schema.json
## Project Context (MANDATORY - Read Both Files)
1. Read: .workflow/project-tech.json (technology stack, architecture)
2. Read: .workflow/project-guidelines.json (user-defined constraints)
## Discussion Results
### Task Description
${taskDescription}
### Selected Solution
**Name**: ${selectedSolution.name}
**Description**: ${selectedSolution.description}
**Effort**: ${selectedSolution.effort}
**Risk**: ${selectedSolution.risk}
**Pros**:
${selectedSolution.pros.map(p => `- ${p}`).join('\n')}
**Cons**:
${selectedSolution.cons.map(c => `- ${c}`).join('\n')}
**Affected Files**:
${selectedSolution.affected_files.map(f => `- ${f.file}:${f.line} - ${f.reason}`).join('\n')}
### Analysis Consensus
${finalSynthesis.consensus_points.map(p => `- ${p}`).join('\n')}
### User Constraints
${userDecision.constraints ? JSON.stringify(userDecision.constraints) : 'None specified'}
### ACE Context Summary
Relevant files: ${contextPackage.relevant_files.slice(0, 15).join(', ')}
Detected patterns: ${contextPackage.detected_patterns.join(', ')}
## Output Requirements
### 1. IMPL_PLAN.md (Documentation)
Write: ${sessionFolder}/IMPL_PLAN.md
Structure:
\`\`\`markdown
# Implementation Plan: {Task Title}
## Overview
- **Task**: {description}
- **Approach**: {selected solution name}
- **Complexity**: {Low/Medium/High}
- **Generated**: {timestamp}
## Background & Decision Rationale
{Why this approach was chosen, key trade-offs considered}
## Implementation Steps
### Step 1: {Title}
**Objective**: {what this step achieves}
**Files**:
- \`path/to/file.ts:line\` - {change description}
**Actions**:
1. {specific action}
2. {specific action}
**Verification**: {how to verify this step is complete}
### Step 2: ...
## File Manifest
| File | Lines | Change Type | Description |
|------|-------|-------------|-------------|
| ... | ... | ... | ... |
## Acceptance Criteria
1. {criterion with verification method}
2. ...
## Risk Mitigation
| Risk | Mitigation Strategy |
|------|---------------------|
| ... | ... |
## Dependencies & Prerequisites
- {prerequisite 1}
- {prerequisite 2}
\`\`\`
### 2. plan.json (Structured Plan)
Write: ${sessionFolder}/plan.json
Follow schema from plan-json-schema.json. Key requirements:
- tasks: 2-7 structured tasks (group by feature/module, NOT by file)
- Each task includes: id, title, description, scope, files, depends_on, execution_group
- _metadata.source: "collaborative-discussion"
- _metadata.session_id: "${sessionId}"
## Task Grouping Rules
1. **Group by feature**: All changes for one feature = one task
2. **Substantial tasks**: Each task = 15-60 minutes of work
3. **True dependencies only**: Use depends_on only when Task B needs Task A's output
4. **Prefer parallel**: Most tasks should be independent
## Success Criteria
- [ ] IMPL_PLAN.md written with complete documentation
- [ ] plan.json follows schema exactly
- [ ] All affected files have line numbers
- [ ] Acceptance criteria are testable
- [ ] Tasks are properly grouped (not one per file)
`
})
```
**Step 5.3: Display Generated Plan**
```javascript
// Read generated outputs
const implPlan = Read(`${sessionFolder}/IMPL_PLAN.md`)
const planJson = JSON.parse(Read(`${sessionFolder}/plan.json`))
console.log(`
## Plan Generated Successfully
### Documentation
${implPlan}
---
### Structured Plan Summary
**Tasks**: ${planJson.tasks.length}
**Complexity**: ${planJson.complexity}
**Estimated Time**: ${planJson.estimated_time}
| # | Task | Scope | Dependencies |
|---|------|-------|--------------|
${planJson.tasks.map((t, i) =>
`| ${i+1} | ${t.title} | ${t.scope} | ${t.depends_on?.join(', ') || 'None'} |`
).join('\n')}
`)
// Update session state
sessionState.phase = 'plan-generated'
sessionState.artifacts = {
impl_plan: `${sessionFolder}/IMPL_PLAN.md`,
plan_json: `${sessionFolder}/plan.json`
}
Write(`${sessionFolder}/session-state.json`, JSON.stringify(sessionState, null, 2))
```
**Step 5.4: Confirm & Hand off to Execution**
```javascript
const executeDecision = await AskUserQuestion({
questions: [{
question: `Plan generated (${planJson.tasks.length} tasks). Proceed to execution?`,
header: "Execute",
multiSelect: false,
options: [
{ label: "Execute Now (Recommended)", description: "Hand off to /workflow:lite-execute" },
{ label: "Review First", description: "Review plan files before execution" },
{ label: "Modify Plan", description: "Adjust plan before execution" },
{ label: "Save Only", description: "Save plan without execution" }
]
}]
})
if (executeDecision === 'Execute Now') {
// Build execution context
const executionContext = {
planObject: planJson,
explorationsContext: contextPackage,
clarificationContext: sessionState.user_decisions,
originalUserInput: taskDescription,
executionMethod: 'Agent', // Default to Agent execution
session: {
id: sessionId,
folder: sessionFolder,
artifacts: {
impl_plan: `${sessionFolder}/IMPL_PLAN.md`,
plan_json: `${sessionFolder}/plan.json`,
session_state: `${sessionFolder}/session-state.json`
}
}
}
// Update state and hand off
sessionState.phase = 'executing'
Write(`${sessionFolder}/session-state.json`, JSON.stringify(sessionState, null, 2))
console.log(`
## Handing off to lite-execute
Session: ${sessionId}
Tasks: ${planJson.tasks.length}
`)
// Hand off to lite-execute
SlashCommand(command="/workflow:lite-execute --in-memory")
} else if (executeDecision === 'Review First') {
console.log(`
## Plan Files Ready for Review
- Documentation: ${sessionFolder}/IMPL_PLAN.md
- Structured Plan: ${sessionFolder}/plan.json
Run \`/workflow:lite-execute --session=${sessionId}\` when ready.
`)
} else if (executeDecision === 'Modify Plan') {
// Return to Phase 4 with modification request
sessionState.phase = 'awaiting-decision'
console.log('Returning to decision phase for plan modification...')
} else {
console.log(`
## Plan Saved
Session: ${sessionId}
Location: ${sessionFolder}/
Files:
- IMPL_PLAN.md (documentation)
- plan.json (structured plan)
- session-state.json (full context)
To execute later: /workflow:lite-execute --session=${sessionId}
`)
sessionState.phase = 'complete'
}
Write(`${sessionFolder}/session-state.json`, JSON.stringify(sessionState, null, 2))
```
---
## Session Folder Structure
```
.workflow/.multi-cli-plan/{MCP-task-slug-YYYY-MM-DD}/
|-- session-state.json # Session state with all rounds and decisions
|-- rounds/
| |-- 1/
| | +-- synthesis.json # Round 1 analysis synthesis
| |-- 2/
| | +-- synthesis.json # Round 2 analysis synthesis
| +-- .../
|-- IMPL_PLAN.md # Implementation plan documentation
+-- plan.json # Structured plan for lite-execute
```
## Key Features
### 1. Agent-Orchestrator Separation
**Orchestrator (this command)** only handles:
- Task delegation to agents
- Reading agent output files
- User interaction and decisions
- Session state management
**Agent (cli-discuss-agent)** handles:
- Multi-CLI execution (Gemini, Codex, Qwen)
- Cross-verification between CLI outputs
- Solution synthesis and ranking
- Writing structured output files
```
┌─────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR │
│ (multi-cli-plan.md - decision layer) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Delegate → Task(cli-discuss-agent) │
│ 2. Wait for completion │
│ 3. Read → synthesis.json │
│ 4. Display → User │
│ 5. Collect → Decision │
│ 6. Loop or proceed │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ CLI-DISCUSS-AGENT │
│ (analysis layer) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Gemini CLI ──┐ │
│ ├──→ Cross-Verify ──→ Synthesize │
│ Codex CLI ───┘ │ │
│ ▼ │
│ synthesis.json │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 2. Multi-CLI Cross-Verification
Agent invokes multiple CLI tools and cross-verifies:
- **Gemini**: Deep code analysis, pattern recognition
- **Codex**: Implementation verification, code generation feasibility
- **Qwen** (fallback): Alternative perspective
Cross-verification identifies:
- Agreements (high confidence points)
- Disagreements (requiring resolution)
- Unique insights from each tool
### 3. User-Driven Decision Points
Every analysis cycle ends with user decision:
- Approve and proceed to planning
- Request more analysis with feedback
- Adjust requirements or direction
- View detailed agent output files
### 4. Iterative Convergence
Each round builds on previous findings:
- Round 1: Initial exploration, identify major approaches
- Round 2: Deep dive into promising approaches, resolve conflicts
- Round 3: Final refinement, edge case analysis
Agent calculates convergence score (0.0-1.0) and recommends:
- `converged`: Ready for planning
- `continue`: More analysis needed
- `user_input_needed`: Clarification required
### 5. Trade-off Transparency
Each solution option includes explicit trade-offs:
- Effort (low/medium/high)
- Risk assessment
- Maintainability impact
- Performance considerations
- Affected files with line numbers
- Source CLI(s) that proposed the solution
## Error Handling
| Error | Resolution |
|-------|------------|
| ACE search fails | Fall back to Glob/Grep for file discovery |
| Agent fails to produce synthesis.json | Retry agent with simpler context |
| CLI tool timeout (in agent) | Agent uses fallback chain: gemini → codex → qwen |
| No convergence after max rounds | Present best available options, flag uncertainty |
| synthesis.json parse error | Agent retries with degraded mode |
| User cancels | Save session state for later resumption |
## Configuration
| Flag | Default | Description |
|------|---------|-------------|
| `--max-rounds` | 3 | Maximum analysis rounds before forcing decision |
| `--tools` | gemini,codex | CLI tools to use for analysis |
| `--auto-execute` | false | Auto-execute after plan approval |
| `--save-context` | true | Persist ACE context for resumption |
## Best Practices
1. **Be Specific**: More detailed task descriptions lead to better initial context gathering
2. **Provide Feedback**: Use clarification rounds to narrow down requirements
3. **Trust the Process**: Allow multiple rounds for complex tasks
4. **Review Trade-offs**: Carefully consider pros/cons of each solution option
5. **Iterate**: Don't hesitate to request additional analysis if uncertain
6. **Review Plan**: Check IMPL_PLAN.md before execution for complete understanding
## Output Artifacts
| File | Purpose | Producer |
|------|---------|----------|
| `rounds/{n}/synthesis.json` | Round analysis results | cli-discuss-agent |
| `IMPL_PLAN.md` | Human-readable documentation | cli-lite-planning-agent |
| `plan.json` | Structured tasks for execution | cli-lite-planning-agent |
| `session-state.json` | Session tracking | Orchestrator |
**synthesis.json schema** (produced by cli-discuss-agent):
```json
{
"round": 1,
"cli_analyses": [...],
"cross_verification": { "agreements": [], "disagreements": [] },
"solutions": [{ "name": "...", "pros": [], "cons": [], "effort": "..." }],
"convergence": { "score": 0.85, "recommendation": "converged" },
"clarification_questions": []
}
```
## Related Commands
```bash
# Resume a saved multi-cli-plan session
/workflow:lite-execute --session=MCP-xxx
# For simpler tasks without multi-round discussion
/workflow:lite-plan "task description"
# For issue-driven discovery
/issue:discover-by-prompt "find issues"
# View generated plan
cat .workflow/.multi-cli-plan/{session-id}/IMPL_PLAN.md
```