diff --git a/.claude/agents/action-planning-agent.md b/.claude/agents/action-planning-agent.md index ad38cd16..5564deff 100644 --- a/.claude/agents/action-planning-agent.md +++ b/.claude/agents/action-planning-agent.md @@ -203,7 +203,13 @@ Generate individual `.task/IMPL-*.json` files with the following structure: "id": "IMPL-N", "title": "Descriptive task name", "status": "pending|active|completed|blocked", - "context_package_path": ".workflow/active/WFS-{session}/.process/context-package.json" + "context_package_path": ".workflow/active/WFS-{session}/.process/context-package.json", + "cli_execution_id": "WFS-{session}-IMPL-N", + "cli_execution": { + "strategy": "new|resume|fork|merge_fork", + "resume_from": "parent-cli-id", + "merge_from": ["id1", "id2"] + } } ``` @@ -216,6 +222,50 @@ Generate individual `.task/IMPL-*.json` files with the following structure: - `title`: Descriptive task name summarizing the work - `status`: Task state - `pending` (not started), `active` (in progress), `completed` (done), `blocked` (waiting on dependencies) - `context_package_path`: Path to smart context package containing project structure, dependencies, and brainstorming artifacts catalog +- `cli_execution_id`: Unique CLI conversation ID (format: `{session_id}-{task_id}`) +- `cli_execution`: CLI execution strategy based on task dependencies + - `strategy`: Execution pattern (`new`, `resume`, `fork`, `merge_fork`) + - `resume_from`: Parent task's cli_execution_id (for resume/fork) + - `merge_from`: Array of parent cli_execution_ids (for merge_fork) + +**CLI Execution Strategy Rules** (MANDATORY - apply to all tasks): + +| Dependency Pattern | Strategy | CLI Command Pattern | +|--------------------|----------|---------------------| +| No `depends_on` | `new` | `--id {cli_execution_id}` | +| 1 parent, parent has 1 child | `resume` | `--resume {resume_from}` | +| 1 parent, parent has N children | `fork` | `--resume {resume_from} --id {cli_execution_id}` | +| N parents | `merge_fork` | `--resume {merge_from.join(',')} --id {cli_execution_id}` | + +**Strategy Selection Algorithm**: +```javascript +function computeCliStrategy(task, allTasks) { + const deps = task.context?.depends_on || [] + const childCount = allTasks.filter(t => + t.context?.depends_on?.includes(task.id) + ).length + + if (deps.length === 0) { + return { strategy: "new" } + } else if (deps.length === 1) { + const parentTask = allTasks.find(t => t.id === deps[0]) + const parentChildCount = allTasks.filter(t => + t.context?.depends_on?.includes(deps[0]) + ).length + + if (parentChildCount === 1) { + return { strategy: "resume", resume_from: parentTask.cli_execution_id } + } else { + return { strategy: "fork", resume_from: parentTask.cli_execution_id } + } + } else { + const mergeFrom = deps.map(depId => + allTasks.find(t => t.id === depId).cli_execution_id + ) + return { strategy: "merge_fork", merge_from: mergeFrom } + } +} +``` #### Meta Object @@ -225,7 +275,13 @@ Generate individual `.task/IMPL-*.json` files with the following structure: "type": "feature|bugfix|refactor|test-gen|test-fix|docs", "agent": "@code-developer|@action-planning-agent|@test-fix-agent|@universal-executor", "execution_group": "parallel-abc123|null", - "module": "frontend|backend|shared|null" + "module": "frontend|backend|shared|null", + "execution_config": { + "method": "agent|hybrid|cli", + "cli_tool": "codex|gemini|qwen|auto", + "enable_resume": true, + "previous_cli_id": "string|null" + } } } ``` @@ -235,6 +291,11 @@ Generate individual `.task/IMPL-*.json` files with the following structure: - `agent`: Assigned agent for execution - `execution_group`: Parallelization group ID (tasks with same ID can run concurrently) or `null` for sequential tasks - `module`: Module identifier for multi-module projects (e.g., `frontend`, `backend`, `shared`) or `null` for single-module +- `execution_config`: CLI execution settings (from userConfig in task-generate-agent) + - `method`: Execution method - `agent` (direct), `hybrid` (agent + CLI), `cli` (CLI only) + - `cli_tool`: Preferred CLI tool - `codex`, `gemini`, `qwen`, or `auto` + - `enable_resume`: Whether to use `--resume` for CLI continuity (default: true) + - `previous_cli_id`: Previous task's CLI execution ID for resume (populated at runtime) **Test Task Extensions** (for type="test-gen" or type="test-fix"): @@ -479,11 +540,12 @@ The `implementation_approach` supports **two execution modes** based on the pres - Specified command executes the step directly - Leverages specialized CLI tools (codex/gemini/qwen) for complex reasoning - **Use for**: Large-scale features, complex refactoring, or when user explicitly requests CLI tool usage - - **Required fields**: Same as default mode **PLUS** `command` - - **Command patterns**: + - **Required fields**: Same as default mode **PLUS** `command`, `resume_from` (optional) + - **Command patterns** (with resume support): - `ccw cli exec '[prompt]' --tool codex --mode auto --cd [path]` - - `ccw cli exec '[task]' --tool codex --mode auto` (multi-step with context) + - `ccw cli exec '[prompt]' --resume ${previousCliId} --tool codex --mode auto` (resume from previous) - `ccw cli exec '[prompt]' --tool gemini --mode write --cd [path]` (write mode) + - **Resume mechanism**: When step depends on previous CLI execution, include `--resume` with previous execution ID **Semantic CLI Tool Selection**: @@ -563,7 +625,22 @@ Agent determines CLI tool usage per-step based on user semantics and task nature "modification_points": ["[Same as default mode]"], "logic_flow": ["[Same as default mode]"], "depends_on": [1, 2], - "output": "cli_implementation" + "output": "cli_implementation", + "cli_output_id": "step3_cli_id" // Store execution ID for resume + }, + + // === CLI MODE with Resume: Continue from previous CLI execution === + { + "step": 4, + "title": "Continue implementation with context", + "description": "Resume from previous step with accumulated context", + "command": "ccw cli exec '[continuation prompt]' --resume ${step3_cli_id} --tool codex --mode auto", + "resume_from": "step3_cli_id", // Reference previous step's CLI ID + "modification_points": ["[Continue from step 3]"], + "logic_flow": ["[Build on previous output]"], + "depends_on": [3], + "output": "continued_implementation", + "cli_output_id": "step4_cli_id" } ] ``` @@ -759,6 +836,8 @@ Use `analysis_results.complexity` or task count to determine structure: - Use provided context package: Extract all information from structured context - Respect memory-first rule: Use provided content (already loaded from memory/file) - Follow 6-field schema: All task JSONs must have id, title, status, context_package_path, meta, context, flow_control +- **Assign CLI execution IDs**: Every task MUST have `cli_execution_id` (format: `{session_id}-{task_id}`) +- **Compute CLI execution strategy**: Based on `depends_on`, set `cli_execution.strategy` (new/resume/fork/merge_fork) - Map artifacts: Use artifacts_inventory to populate task.context.artifacts array - Add MCP integration: Include MCP tool steps in flow_control.pre_analysis when capabilities available - Validate task count: Maximum 12 tasks hard limit, request re-scope if exceeded diff --git a/.claude/commands/workflow/lite-execute.md b/.claude/commands/workflow/lite-execute.md index a408b855..77ae6981 100644 --- a/.claude/commands/workflow/lite-execute.md +++ b/.claude/commands/workflow/lite-execute.md @@ -476,7 +476,7 @@ Detailed plan: ${executionContext.session.artifacts.plan}`) ccw cli exec "${buildCLIPrompt(batch)}" --tool codex --mode auto ``` -**Execution with tracking**: +**Execution with fixed IDs** (predictable ID pattern): ```javascript // Launch CLI in foreground (NOT background) // Timeout based on complexity: Low=40min, Medium=60min, High=100min @@ -486,15 +486,48 @@ const timeoutByComplexity = { "High": 6000000 // 100 minutes } +// Generate fixed execution ID: ${sessionId}-${groupId} +// This enables predictable ID lookup without relying on resume context chains +const sessionId = executionContext?.session?.id || 'standalone' +const fixedExecutionId = `${sessionId}-${batch.groupId}` // e.g., "implement-auth-2025-12-13-P1" + +// Check if resuming from previous failed execution +const previousCliId = batch.resumeFromCliId || null + +// Build command with fixed ID (and optional resume for continuation) +const cli_command = previousCliId + ? `ccw cli exec "${buildCLIPrompt(batch)}" --tool codex --mode auto --id ${fixedExecutionId} --resume ${previousCliId}` + : `ccw cli exec "${buildCLIPrompt(batch)}" --tool codex --mode auto --id ${fixedExecutionId}` + bash_result = Bash( command=cli_command, timeout=timeoutByComplexity[planObject.complexity] || 3600000 ) +// Execution ID is now predictable: ${fixedExecutionId} +// Can also extract from output: "ID: implement-auth-2025-12-13-P1" +const cliExecutionId = fixedExecutionId + // Update TodoWrite when execution completes ``` -**Result Collection**: After completion, analyze output and collect result following `executionResult` structure +**Resume on Failure** (with fixed ID): +```javascript +// If execution failed or timed out, offer resume option +if (bash_result.status === 'failed' || bash_result.status === 'timeout') { + console.log(` +⚠️ Execution incomplete. Resume available: + Fixed ID: ${fixedExecutionId} + Lookup: ccw cli detail ${fixedExecutionId} + Resume: ccw cli exec "Continue tasks" --resume ${fixedExecutionId} --tool codex --mode auto --id ${fixedExecutionId}-retry +`) + + // Store for potential retry in same session + batch.resumeFromCliId = fixedExecutionId +} +``` + +**Result Collection**: After completion, analyze output and collect result following `executionResult` structure (include `cliExecutionId` for resume capability) ### Step 4: Progress Tracking @@ -552,6 +585,21 @@ ccw cli exec "[Shared Prompt Template with artifacts]" --tool qwen ccw cli exec "[Verify plan acceptance criteria at ${plan.json}]" --tool codex --mode auto ``` +**Multi-Round Review with Fixed IDs**: +```javascript +// Generate fixed review ID +const reviewId = `${sessionId}-review` + +// First review pass with fixed ID +const reviewResult = Bash(`ccw cli exec "[Review prompt]" --tool gemini --id ${reviewId}`) + +// If issues found, continue review dialog with fixed ID chain +if (hasUnresolvedIssues(reviewResult)) { + // Resume with follow-up questions + Bash(`ccw cli exec "Clarify the security concerns you mentioned" --resume ${reviewId} --tool gemini --id ${reviewId}-followup`) +} +``` + **Implementation Note**: Replace `[Shared Prompt Template with artifacts]` placeholder with actual template content, substituting: - `@{plan.json}` → `@${executionContext.session.artifacts.plan}` - `[@{exploration.json}]` → exploration files from artifacts (if exists) @@ -623,8 +671,10 @@ console.log(`✓ Development index: [${category}] ${entry.title}`) | Empty file | File exists but no content | Error: "File is empty: {path}. Provide task description." | | Invalid Enhanced Task JSON | JSON missing required fields | Warning: "Missing required fields. Treating as plain text." | | Malformed JSON | JSON parsing fails | Treat as plain text (expected for non-JSON files) | -| Execution failure | Agent/Codex crashes | Display error, save partial progress, suggest retry | +| Execution failure | Agent/Codex crashes | Display error, use fixed ID `${sessionId}-${groupId}` for resume: `ccw cli exec "Continue" --resume --id -retry` | +| Execution timeout | CLI exceeded timeout | Use fixed ID for resume with extended timeout | | Codex unavailable | Codex not installed | Show installation instructions, offer Agent execution | +| Fixed ID not found | Custom ID lookup failed | Check `ccw cli history`, verify date directories | ## Data Structures @@ -679,8 +729,20 @@ Collected after each execution call completes: tasksSummary: string, // Brief description of tasks handled completionSummary: string, // What was completed keyOutputs: string, // Files created/modified, key changes - notes: string // Important context for next execution + notes: string, // Important context for next execution + fixedCliId: string | null // Fixed CLI execution ID (e.g., "implement-auth-2025-12-13-P1") } ``` Appended to `previousExecutionResults` array for context continuity in multi-execution scenarios. + +**Fixed ID Pattern**: `${sessionId}-${groupId}` enables predictable lookup without auto-generated timestamps. + +**Resume Usage**: If `status` is "partial" or "failed", use `fixedCliId` to resume: +```bash +# Lookup previous execution +ccw cli detail ${fixedCliId} + +# Resume with new fixed ID for retry +ccw cli exec "Continue from where we left off" --resume ${fixedCliId} --tool codex --mode auto --id ${fixedCliId}-retry +``` diff --git a/.claude/commands/workflow/lite-fix.md b/.claude/commands/workflow/lite-fix.md index 38683edc..ca5150d4 100644 --- a/.claude/commands/workflow/lite-fix.md +++ b/.claude/commands/workflow/lite-fix.md @@ -54,6 +54,14 @@ Phase 3: Fix Planning (NO CODE EXECUTION - planning only) +- Decision (based on Phase 1 severity): |- Low/Medium -> Load schema: cat ~/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json -> Direct Claude planning (following schema) -> fix-plan.json -> MUST proceed to Phase 4 +- High/Critical -> cli-lite-planning-agent -> fix-plan.json -> MUST proceed to Phase 4 + +- CLI Execution ID Assignment (MANDATORY for all tasks): + |- Generate cli_execution_id for each task (format: {sessionId}-{task.id}) + +- Compute cli_execution strategy based on depends_on: + |- No deps -> strategy: "new" + |- 1 dep + sequential -> strategy: "resume", resume_from: parent.cli_execution_id + |- 1 dep + parallel children -> strategy: "fork", resume_from: parent.cli_execution_id + |- N deps -> strategy: "merge", merge_from: [parent1.cli_execution_id, ...] + +- N deps + creates branch -> strategy: "merge_fork", merge_from: [...], new ID Phase 4: Confirmation & Selection |- Display fix-plan summary (tasks, severity, estimated time) @@ -381,7 +389,7 @@ const fixPlan = { summary: "...", root_cause: "...", strategy: "immediate_patch|comprehensive_fix|refactor", - tasks: [...], // Each task: { id, title, scope, ..., depends_on, complexity } + tasks: [...], // Each task: { id, title, scope, ..., depends_on, cli_execution_id, cli_execution } estimated_time: "...", recommended_execution: "Agent", severity: severity, @@ -389,10 +397,66 @@ const fixPlan = { _metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct" } } -// Step 3: Write fix-plan to session folder +// Step 3: ⚠️ MANDATORY - Assign CLI Execution IDs and strategies +assignCliExecutionIds(fixPlan.tasks, sessionId) + +// Step 4: Write fix-plan to session folder Write(`${sessionFolder}/fix-plan.json`, JSON.stringify(fixPlan, null, 2)) -// Step 4: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here +// Step 5: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here +``` + +**CLI Execution ID Assignment Function** (MANDATORY for ALL planning modes): +```javascript +function assignCliExecutionIds(tasks, sessionId) { + // Build dependency graph + const taskMap = new Map(tasks.map(t => [t.id, t])) + const childCount = new Map() // Track how many tasks depend on each task + + tasks.forEach(task => { + (task.depends_on || []).forEach(depId => { + childCount.set(depId, (childCount.get(depId) || 0) + 1) + }) + }) + + tasks.forEach(task => { + // Assign unique CLI execution ID + task.cli_execution_id = `${sessionId}-${task.id}` + + const deps = task.depends_on || [] + + if (deps.length === 0) { + // No dependencies: new conversation + task.cli_execution = { strategy: "new" } + } else if (deps.length === 1) { + const parent = taskMap.get(deps[0]) + const parentChildCount = childCount.get(deps[0]) || 0 + + if (parentChildCount === 1) { + // Single child: resume (continue same conversation) + task.cli_execution = { + strategy: "resume", + resume_from: parent.cli_execution_id + } + } else { + // Multiple children: fork (create new branch) + task.cli_execution = { + strategy: "fork", + resume_from: parent.cli_execution_id + } + } + } else { + // Multiple dependencies: merge + const mergeFrom = deps.map(depId => taskMap.get(depId).cli_execution_id) + task.cli_execution = { + strategy: "merge_fork", // Merge always creates new ID + merge_from: mergeFrom + } + } + }) + + return tasks +} ``` **High/Critical Severity** - Invoke cli-lite-planning-agent: @@ -407,6 +471,15 @@ Generate fix plan and write fix-plan.json. ## Output Schema Reference Execute: cat ~/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json (get schema reference before generating plan) +## CLI Execution ID Requirements (MANDATORY) +After generating tasks, you MUST assign CLI execution IDs and strategies: +1. Each task needs cli_execution_id: {sessionId}-{task.id} +2. Compute cli_execution.strategy based on depends_on: + - No deps → { strategy: "new" } + - 1 dep (single child) → { strategy: "resume", resume_from: parent.cli_execution_id } + - 1 dep (multiple children) → { strategy: "fork", resume_from: parent.cli_execution_id } + - N deps → { strategy: "merge_fork", merge_from: [parent1.cli_execution_id, ...] } + ## Bug Description ${bug_description} diff --git a/.claude/commands/workflow/lite-plan.md b/.claude/commands/workflow/lite-plan.md index d575869d..59837e04 100644 --- a/.claude/commands/workflow/lite-plan.md +++ b/.claude/commands/workflow/lite-plan.md @@ -54,6 +54,14 @@ Phase 3: Planning (NO CODE EXECUTION - planning only) └─ Decision (based on Phase 1 complexity): ├─ Low → Load schema: cat ~/.claude/workflows/cli-templates/schemas/plan-json-schema.json → Direct Claude planning (following schema) → plan.json → MUST proceed to Phase 4 └─ Medium/High → cli-lite-planning-agent → plan.json → MUST proceed to Phase 4 + └─ CLI Execution ID Assignment (MANDATORY for all tasks): + ├─ Generate cli_execution_id for each task (format: {sessionId}-T{N}) + └─ Compute cli_execution strategy based on depends_on: + ├─ No deps → strategy: "new" + ├─ 1 dep + sequential → strategy: "resume", resume_from: parent.cli_execution_id + ├─ 1 dep + parallel children → strategy: "fork", resume_from: parent.cli_execution_id + ├─ N deps → strategy: "merge", merge_from: [parent1.cli_execution_id, ...] + └─ N deps + creates branch → strategy: "merge_fork", merge_from: [...], new ID Phase 4: Confirmation & Selection ├─ Display plan summary (tasks, complexity, estimated time) @@ -373,17 +381,73 @@ manifest.explorations.forEach(exp => { const plan = { summary: "...", approach: "...", - tasks: [...], // Each task: { id, title, scope, ..., depends_on, execution_group, complexity } + tasks: [...], // Each task: { id, title, scope, ..., depends_on, cli_execution_id, cli_execution } estimated_time: "...", recommended_execution: "Agent", complexity: "Low", _metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct" } } -// Step 4: Write plan to session folder +// Step 4: ⚠️ MANDATORY - Assign CLI Execution IDs and strategies +assignCliExecutionIds(plan.tasks, sessionId) + +// Step 5: Write plan to session folder Write(`${sessionFolder}/plan.json`, JSON.stringify(plan, null, 2)) -// Step 5: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here +// Step 6: MUST continue to Phase 4 (Confirmation) - DO NOT execute code here +``` + +**CLI Execution ID Assignment Function** (MANDATORY for ALL planning modes): +```javascript +function assignCliExecutionIds(tasks, sessionId) { + // Build dependency graph + const taskMap = new Map(tasks.map(t => [t.id, t])) + const childCount = new Map() // Track how many tasks depend on each task + + tasks.forEach(task => { + (task.depends_on || []).forEach(depId => { + childCount.set(depId, (childCount.get(depId) || 0) + 1) + }) + }) + + tasks.forEach(task => { + // Assign unique CLI execution ID + task.cli_execution_id = `${sessionId}-${task.id}` + + const deps = task.depends_on || [] + + if (deps.length === 0) { + // No dependencies: new conversation + task.cli_execution = { strategy: "new" } + } else if (deps.length === 1) { + const parent = taskMap.get(deps[0]) + const parentChildCount = childCount.get(deps[0]) || 0 + + if (parentChildCount === 1) { + // Single child: resume (continue same conversation) + task.cli_execution = { + strategy: "resume", + resume_from: parent.cli_execution_id + } + } else { + // Multiple children: fork (create new branch) + task.cli_execution = { + strategy: "fork", + resume_from: parent.cli_execution_id + } + } + } else { + // Multiple dependencies: merge + const mergeFrom = deps.map(depId => taskMap.get(depId).cli_execution_id) + task.cli_execution = { + strategy: "merge_fork", // Merge always creates new ID + merge_from: mergeFrom + } + } + }) + + return tasks +} ``` **Medium/High Complexity** - Invoke cli-lite-planning-agent: @@ -398,6 +462,15 @@ Generate implementation plan and write plan.json. ## Output Schema Reference Execute: cat ~/.claude/workflows/cli-templates/schemas/plan-json-schema.json (get schema reference before generating plan) +## CLI Execution ID Requirements (MANDATORY) +After generating tasks, you MUST assign CLI execution IDs and strategies: +1. Each task needs cli_execution_id: {sessionId}-{task.id} +2. Compute cli_execution.strategy based on depends_on: + - No deps → { strategy: "new" } + - 1 dep (single child) → { strategy: "resume", resume_from: parent.cli_execution_id } + - 1 dep (multiple children) → { strategy: "fork", resume_from: parent.cli_execution_id } + - N deps → { strategy: "merge_fork", merge_from: [parent1.cli_execution_id, ...] } + ## Task Description ${task_description} diff --git a/.claude/commands/workflow/tools/task-generate-agent.md b/.claude/commands/workflow/tools/task-generate-agent.md index 16f72c43..0f2351c8 100644 --- a/.claude/commands/workflow/tools/task-generate-agent.md +++ b/.claude/commands/workflow/tools/task-generate-agent.md @@ -28,6 +28,12 @@ Input Parsing: ├─ Parse flags: --session └─ Validation: session_id REQUIRED +Phase 0: User Configuration (Interactive) + ├─ Question 1: Supplementary materials/guidelines? + ├─ Question 2: Execution method preference (Agent/CLI/Hybrid) + ├─ Question 3: CLI tool preference (if CLI selected) + └─ Store: userConfig for agent prompt + Phase 1: Context Preparation & Module Detection (Command) ├─ Assemble session paths (metadata, context package, output dirs) ├─ Provide metadata (session_id, execution_mode, mcp_capabilities) @@ -57,6 +63,82 @@ Phase 3: Integration (+1 Coordinator, Multi-Module Only) ## Document Generation Lifecycle +### Phase 0: User Configuration (Interactive) + +**Purpose**: Collect user preferences before task generation to ensure generated tasks match execution expectations. + +**User Questions**: +```javascript +AskUserQuestion({ + questions: [ + { + question: "Do you have supplementary materials or guidelines to include?", + header: "Materials", + multiSelect: false, + options: [ + { label: "No additional materials", description: "Use existing context only" }, + { label: "Provide file paths", description: "I'll specify paths to include" }, + { label: "Provide inline content", description: "I'll paste content directly" } + ] + }, + { + question: "Select execution method for generated tasks:", + header: "Execution", + multiSelect: false, + options: [ + { label: "Agent (Recommended)", description: "Claude agent executes tasks directly" }, + { label: "Hybrid", description: "Agent orchestrates, calls CLI for complex steps" }, + { label: "CLI Only", description: "All execution via CLI tools (codex/gemini/qwen)" } + ] + }, + { + question: "If using CLI, which tool do you prefer?", + header: "CLI Tool", + multiSelect: false, + options: [ + { label: "Codex (Recommended)", description: "Best for implementation tasks" }, + { label: "Gemini", description: "Best for analysis and large context" }, + { label: "Qwen", description: "Alternative analysis tool" }, + { label: "Auto", description: "Let agent decide per-task" } + ] + } + ] +}) +``` + +**Handle Materials Response**: +```javascript +if (userConfig.materials === "Provide file paths") { + // Follow-up question for file paths + const pathsResponse = AskUserQuestion({ + questions: [{ + question: "Enter file paths to include (comma-separated or one per line):", + header: "Paths", + multiSelect: false, + options: [ + { label: "Enter paths", description: "Provide paths in text input" } + ] + }] + }) + userConfig.supplementaryPaths = parseUserPaths(pathsResponse) +} +``` + +**Build userConfig**: +```javascript +const userConfig = { + supplementaryMaterials: { + type: "none|paths|inline", + content: [...], // Parsed paths or inline content + }, + executionMethod: "agent|hybrid|cli", + preferredCliTool: "codex|gemini|qwen|auto", + enableResume: true // Always enable resume for CLI executions +} +``` + +**Pass to Agent**: Include `userConfig` in agent prompt for Phase 2A/2B. + ### Phase 1: Context Preparation & Module Detection (Command Responsibility) **Command prepares session paths, metadata, and detects module structure.** @@ -159,10 +241,21 @@ Output: Session ID: {session-id} MCP Capabilities: {exa_code, exa_web, code_index} +## USER CONFIGURATION (from Phase 0) +Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli +Preferred CLI Tool: ${userConfig.preferredCliTool} // codex|gemini|qwen|auto +Supplementary Materials: ${userConfig.supplementaryMaterials} + ## CLI TOOL SELECTION -Determine CLI tool usage per-step based on user's task description: -- If user specifies "use Codex/Gemini/Qwen for X" → Add command field to relevant steps -- Default: Agent execution (no command field) unless user explicitly requests CLI +Based on userConfig.executionMethod: +- "agent": No command field in implementation_approach steps +- "hybrid": Add command field to complex steps only (agent handles simple steps) +- "cli": Add command field to ALL implementation_approach steps + +CLI Resume Support (MANDATORY for all CLI commands): +- Use --resume parameter to continue from previous task execution +- Read previous task's cliExecutionId from session state +- Format: ccw cli exec "[prompt]" --resume ${previousCliId} --tool ${tool} --mode auto ## EXPLORATION CONTEXT (from context-package.exploration_results) - Load exploration_results from context-package.json @@ -186,6 +279,7 @@ Determine CLI tool usage per-step based on user's task description: - Artifacts integration from context package - **focus_paths enhanced with exploration critical_files** - Flow control with pre_analysis steps (include exploration integration_points analysis) + - **CLI Execution IDs and strategies (MANDATORY)** 2. Implementation Plan (IMPL_PLAN.md) - Context analysis and artifact references @@ -197,6 +291,27 @@ Determine CLI tool usage per-step based on user's task description: - Links to task JSONs and summaries - Matches task JSON hierarchy +## CLI EXECUTION ID REQUIREMENTS (MANDATORY) +Each task JSON MUST include: +- **cli_execution_id**: Unique ID for CLI execution (format: `{session_id}-{task_id}`) +- **cli_execution**: Strategy object based on depends_on: + - No deps → `{ "strategy": "new" }` + - 1 dep (single child) → `{ "strategy": "resume", "resume_from": "parent-cli-id" }` + - 1 dep (multiple children) → `{ "strategy": "fork", "resume_from": "parent-cli-id" }` + - N deps → `{ "strategy": "merge_fork", "merge_from": ["id1", "id2", ...] }` + +**CLI Execution Strategy Rules**: +1. **new**: Task has no dependencies - starts fresh CLI conversation +2. **resume**: Task has 1 parent AND that parent has only this child - continues same conversation +3. **fork**: Task has 1 parent BUT parent has multiple children - creates new branch with parent context +4. **merge_fork**: Task has multiple parents - merges all parent contexts into new conversation + +**Execution Command Patterns**: +- new: `ccw cli exec "[prompt]" --tool [tool] --id [cli_execution_id]` +- resume: `ccw cli exec "[prompt]" --resume [resume_from] --tool [tool]` +- fork: `ccw cli exec "[prompt]" --resume [resume_from] --id [cli_execution_id] --tool [tool]` +- merge_fork: `ccw cli exec "[prompt]" --resume [merge_from.join(',')] --id [cli_execution_id] --tool [tool]` + ## QUALITY STANDARDS Hard Constraints: - Task count <= 18 (hard limit - request re-scope if exceeded) diff --git a/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json b/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json index 0b5d8260..ce72c2c1 100644 --- a/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +++ b/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json @@ -130,6 +130,31 @@ "type": "string", "enum": ["low", "medium", "high"], "description": "Risk level of this specific fix task" + }, + "cli_execution_id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Fixed CLI execution ID for this fix task (e.g., 'session-FIX1', 'bugfix-001-diagnosis')" + }, + "cli_execution": { + "type": "object", + "properties": { + "strategy": { + "type": "string", + "enum": ["new", "resume", "fork", "merge_fork"], + "description": "CLI execution strategy: new (no deps), resume (1 dep, continue), fork (1 dep, branch), merge_fork (N deps, combine)" + }, + "resume_from": { + "type": "string", + "description": "Parent task's cli_execution_id (for resume/fork strategies)" + }, + "merge_from": { + "type": "array", + "items": {"type": "string"}, + "description": "Multiple parents' cli_execution_ids (for merge_fork strategy)" + } + }, + "description": "CLI execution strategy based on task dependencies" } } }, diff --git a/.claude/workflows/cli-templates/schemas/plan-json-schema.json b/.claude/workflows/cli-templates/schemas/plan-json-schema.json index b94e2d58..6f02b4b8 100644 --- a/.claude/workflows/cli-templates/schemas/plan-json-schema.json +++ b/.claude/workflows/cli-templates/schemas/plan-json-schema.json @@ -118,6 +118,31 @@ "pattern": "^T[0-9]+$" }, "description": "Task IDs this task depends on (e.g., ['T1', 'T2'])" + }, + "cli_execution_id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Fixed CLI execution ID for this task (e.g., 'session-T1', 'IMPL-001-analysis')" + }, + "cli_execution": { + "type": "object", + "properties": { + "strategy": { + "type": "string", + "enum": ["new", "resume", "fork", "merge_fork"], + "description": "CLI execution strategy: new (no deps), resume (1 dep, continue), fork (1 dep, branch), merge_fork (N deps, combine)" + }, + "resume_from": { + "type": "string", + "description": "Parent task's cli_execution_id (for resume/fork strategies)" + }, + "merge_from": { + "type": "array", + "items": {"type": "string"}, + "description": "Multiple parents' cli_execution_ids (for merge_fork strategy)" + } + }, + "description": "CLI execution strategy based on task dependencies" } } }, diff --git a/.claude/workflows/intelligent-tools-strategy.md b/.claude/workflows/intelligent-tools-strategy.md index 07177f9d..0c63c0e3 100644 --- a/.claude/workflows/intelligent-tools-strategy.md +++ b/.claude/workflows/intelligent-tools-strategy.md @@ -1,48 +1,41 @@ # Intelligent Tools Selection Strategy ## Table of Contents -1. [Quick Start](#-quick-start) -2. [Tool Specifications](#-tool-specifications) -3. [Command Templates](#-command-templates) -4. [Execution Configuration](#-execution-configuration) -5. [Best Practices](#-best-practices) +1. [Quick Reference](#quick-reference) +2. [Tool Specifications](#tool-specifications) +3. [Prompt Template](#prompt-template) +4. [CLI Execution](#cli-execution) +5. [Configuration](#configuration) +6. [Best Practices](#best-practices) --- -## Quick Start +## Quick Reference ### Universal Prompt Template -All CLI tools (Gemini, Qwen, Codex) share this template structure: - ``` PURPOSE: [objective + why + success criteria] TASK: • [step 1] • [step 2] • [step 3] MODE: [analysis|write|auto] CONTEXT: @**/* | Memory: [session/tech/module context] EXPECTED: [format + quality + structure] -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/pattern.txt) | [constraints] | MODE=[permission level] +RULES: $(cat ~/.claude/workflows/cli-templates/prompts/[category]/[template].txt) | [constraints] | MODE=[permission] ``` ### Tool Selection -- **Analysis/Documentation** → Gemini (preferred) or Qwen (fallback) -- **Implementation/Testing** → Codex +| Task Type | Tool | Fallback | +|-----------|------|----------| +| Analysis/Documentation | Gemini | Qwen | +| Implementation/Testing | Codex | - | -### CCW Unified CLI Syntax +### CCW Command Syntax ```bash -# Basic execution ccw cli exec "" --tool --mode - -# With working directory -ccw cli exec "" --tool gemini --cd - -# With additional directories -ccw cli exec "" --tool gemini --includeDirs ../shared,../types - -# Full example -ccw cli exec "" --tool codex --mode auto --cd ./project --includeDirs ./lib +ccw cli exec "" --tool gemini --cd --includeDirs +ccw cli exec "" --resume [id] # Resume previous session ``` ### CLI Subcommands @@ -55,23 +48,12 @@ ccw cli exec "" --tool codex --mode auto --cd ./project --includeDirs ./ | `ccw cli history` | Show execution history | | `ccw cli detail ` | Show execution detail | -### Model Selection - -**Available Models** (override via `--model`): -- Gemini: `gemini-2.5-pro`, `gemini-2.5-flash` -- Qwen: `coder-model`, `vision-model` -- Codex: `gpt-5.1`, `gpt-5.1-codex`, `gpt-5.1-codex-mini` - -**Best Practice**: Omit `--model` for optimal auto-selection - ### Core Principles - **Use tools early and often** - Tools are faster and more thorough -- **When in doubt, use both** - Parallel usage provides comprehensive coverage -- **Default to tools** - Use for most coding tasks, no matter how small - **Unified CLI** - Always use `ccw cli exec` for consistent parameter handling -- **Choose templates by need** - See [Template System](#template-system) for naming conventions and selection guide -- **Write protection** - Require EXPLICIT MODE=write or MODE=auto specification +- **Write protection** - Require EXPLICIT `--mode write` or `--mode auto` +- **No escape characters** - NEVER use `\$`, `\"`, `\'` in CLI commands --- @@ -79,65 +61,48 @@ ccw cli exec "" --tool codex --mode auto --cd ./project --includeDirs ./ ### MODE Options -**analysis** (default) -- Read-only operations, no file modifications -- Analysis output returned as text response -- Use for: code review, architecture analysis, pattern discovery -- CCW: `ccw cli exec "" --mode analysis` - -**write** -- File creation/modification/deletion allowed -- Requires explicit `--mode write` specification -- Use for: documentation generation, code creation, file modifications -- CCW: `ccw cli exec "" --mode write` - -**auto** (Codex only) -- Full autonomous development operations -- Requires explicit `--mode auto` specification -- Use for: feature implementation, bug fixes, autonomous development -- CCW: `ccw cli exec "" --tool codex --mode auto` +| Mode | Permission | Use For | Specification | +|------|------------|---------|---------------| +| `analysis` | Read-only (default) | Code review, architecture analysis, pattern discovery | Auto for Gemini/Qwen | +| `write` | Create/Modify/Delete | Documentation, code creation, file modifications | Requires `--mode write` | +| `auto` | Full operations | Feature implementation, bug fixes, autonomous development | Codex only, requires `--mode auto` | ### Gemini & Qwen **Via CCW**: `ccw cli exec "" --tool gemini` or `--tool qwen` -**Strengths**: Large context window, pattern recognition +**Characteristics**: +- Large context window, pattern recognition +- Best for: Analysis, documentation, code exploration, architecture review +- Default MODE: `analysis` (read-only) +- Priority: Prefer Gemini; use Qwen as fallback -**Best For**: Analysis, documentation generation, code exploration, architecture review +**Models** (override via `--model`): +- Gemini: `gemini-2.5-pro` +- Qwen: `coder-model`, `vision-model` -**Default MODE**: `analysis` (read-only) - -**Priority**: Prefer Gemini; use Qwen as fallback when Gemini unavailable - -**Error Handling**: -- **HTTP 429**: May show error but still return results - check if results exist (results present = success, no results = retry/fallback to Qwen) +**Error Handling**: HTTP 429 may show error but still return results - check if results exist ### Codex **Via CCW**: `ccw cli exec "" --tool codex --mode auto` -**Strengths**: Autonomous development, mathematical reasoning +**Characteristics**: +- Autonomous development, mathematical reasoning +- Best for: Implementation, testing, automation +- No default MODE - must explicitly specify `--mode write` or `--mode auto` -**Best For**: Implementation, testing, automation - -**Default MODE**: No default, must be explicitly specified +**Models**: `gpt-5.2` ### Session Resume -**Resume via `--resume` parameter** (integrated into exec): +**Resume via `--resume` parameter**: + ```bash -# Resume last session with continuation prompt -ccw cli exec "Now add error handling" --resume --tool gemini -ccw cli exec "Continue analyzing security" --resume --tool gemini - -# Resume specific session by ID with prompt -ccw cli exec "Fix the issues you found" --resume --tool gemini - -# Resume last session (empty --resume = last) -ccw cli exec "Continue analysis" --resume +ccw cli exec "Continue analyzing" --resume # Resume last session +ccw cli exec "Fix issues found" --resume # Resume specific session ``` -**Resume Parameter**: | Value | Description | |-------|-------------| | `--resume` (empty) | Resume most recent session | @@ -146,474 +111,294 @@ ccw cli exec "Continue analysis" --resume **Context Assembly** (automatic): ``` === PREVIOUS CONVERSATION === - -USER PROMPT: -[Previous prompt content] - -ASSISTANT RESPONSE: -[Previous output] - +USER PROMPT: [Previous prompt] +ASSISTANT RESPONSE: [Previous output] === CONTINUATION === - -[Your new prompt content here] +[Your new prompt] ``` -**Tool-Specific Behavior**: -- **Codex**: Uses native `codex resume` command -- **Gemini/Qwen**: Assembles previous prompt + response + new prompt as single context +**Tool Behavior**: Codex uses native `codex resume`; Gemini/Qwen assembles context as single prompt --- -## Command Templates +## Prompt Template -### Universal Template Structure +### Template Structure -Every command MUST follow this structure: +Every command MUST include these fields: -- [ ] **PURPOSE** - Clear goal and intent - - State the high-level objective of this execution - - Explain why this task is needed - - Define success criteria - - Example: "Analyze authentication module to identify security vulnerabilities" +| Field | Purpose | Example | +|-------|---------|---------| +| **PURPOSE** | Goal, why needed, success criteria | "Analyze auth module for security vulnerabilities" | +| **TASK** | Actionable steps (• bullet format) | "• Review patterns • Identify risks • Document findings" | +| **MODE** | Permission level | `analysis` / `write` / `auto` | +| **CONTEXT** | File patterns + Memory context | `@src/**/* | Memory: Previous refactoring (abc123)` | +| **EXPECTED** | Deliverable format, quality criteria | "Security report with risk levels and recommendations" | +| **RULES** | Template reference + constraints | `$(cat template.txt) | Focus on auth | analysis=READ-ONLY` | -- [ ] **TASK** - Specific execution task (use list format: • Task item 1 • Task item 2 • Task item 3) - - Break down PURPOSE into concrete, actionable steps - - Use bullet points (•) for multiple sub-tasks - - Order tasks by execution sequence - - Example: "• Review auth implementation patterns • Identify potential security risks • Document findings with recommendations" - -- [ ] **MODE** - Execution mode and permission level - - `analysis` (default): Read-only operations, no file modifications - - `write`: File creation/modification/deletion allowed (requires explicit specification) - - `auto`: Full autonomous development operations (Codex only, requires explicit specification) - - Example: "MODE: analysis" or "MODE: write" - -- [ ] **CONTEXT** - File references and memory context from previous sessions - - **File Patterns**: Use @ syntax for file references (default: `@**/*` for all files) - - `@**/*` - All files in current directory tree - - `@src/**/*.ts` - TypeScript files in src directory - - `@../shared/**/*` - Files from sibling directory (requires `--includeDirs`) - - **Memory Context**: Reference previous session findings and context - - Related tasks: `Building on previous analysis from [session/commit]` - - Tech stack: `Using patterns from [tech-stack-name] documentation` - - Cross-reference: `Related to implementation in [module/file]` - - **Memory Sources**: Include relevant memory sources - - Documentation: `CLAUDE.md`, module-specific docs - - Example: "CONTEXT: @src/auth/**/* @CLAUDE.md | Memory: Building on previous auth refactoring (commit abc123)" - -- [ ] **EXPECTED** - Clear expected results - - Specify deliverable format (report, code, documentation, list) - - Define quality criteria - - State output structure requirements - - Example: "Comprehensive security report with categorized findings, risk levels, and actionable recommendations" - -- [ ] **RULES** - Template reference and constraints (include mode constraints: analysis=READ-ONLY | write=CREATE/MODIFY/DELETE | auto=FULL operations) - - Reference templates: `$(cat ~/.claude/workflows/cli-templates/prompts/[category]/[template].txt)` - - Specify constraints and boundaries - - Include mode-specific constraints: - - `analysis=READ-ONLY` - No file modifications - - `write=CREATE/MODIFY/DELETE` - File operations allowed - - `auto=FULL operations` - Autonomous development - - Example: "$(cat ~/.claude/workflows/cli-templates/prompts/analysis/security.txt) | Focus on authentication flows only | analysis=READ-ONLY" - -### Standard Prompt Template - -``` -PURPOSE: [clear goal - state objective, why needed, success criteria] -TASK: -• [specific task - actionable step 1] -• [specific task - actionable step 2] -• [specific task - actionable step 3] -MODE: [analysis|write|auto] -CONTEXT: @**/* | Memory: [previous session findings, related implementations, tech stack patterns, workflow context] -EXPECTED: [deliverable format, quality criteria, output structure, testing requirements (if applicable)] -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/[category]/[0X-template-name].txt) | [additional constraints] | [MODE]=[READ-ONLY|CREATE/MODIFY/DELETE|FULL operations] -``` - -### CCW CLI Execution - -Use the **[Standard Prompt Template](#standard-prompt-template)** for all tools. CCW provides unified command syntax. - -#### Basic Command Format - -```bash -ccw cli exec "" [options] -``` - -#### Common Options - -| Option | Description | Default | -|--------|-------------|---------| -| `--tool ` | CLI tool: gemini, qwen, codex | gemini | -| `--mode ` | Mode: analysis, write, auto | analysis | -| `--model ` | Model override | auto-select | -| `--cd ` | Working directory | current dir | -| `--includeDirs ` | Additional directories (comma-separated) | none | -| `--timeout ` | Timeout in milliseconds | 300000 | -| `--no-stream` | Disable streaming output | false | - -#### Command Examples - -```bash -# Analysis Mode (default, read-only) - Gemini -ccw cli exec " -PURPOSE: Analyze authentication with shared utilities context -TASK: Review auth implementation and its dependencies -MODE: analysis -CONTEXT: @**/* @../shared/**/* -EXPECTED: Complete analysis with cross-directory dependencies -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | analysis=READ-ONLY -" --tool gemini --cd src/auth --includeDirs ../shared,../types - -# Write Mode - Gemini with file modifications -ccw cli exec " -PURPOSE: Generate documentation for API module -TASK: • Create API docs • Add usage examples • Update README -MODE: write -CONTEXT: @src/api/**/* -EXPECTED: Complete API documentation -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt) | write=CREATE/MODIFY/DELETE -" --tool gemini --mode write --cd src - -# Auto Mode - Codex for implementation -ccw cli exec " -PURPOSE: Implement authentication module -TASK: • Create auth service • Add user validation • Setup JWT tokens -MODE: auto -CONTEXT: @**/* | Memory: Following security patterns from project standards -EXPECTED: Complete auth module with tests -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt) | auto=FULL operations -" --tool codex --mode auto --cd project - -# Fallback to Qwen -ccw cli exec " -PURPOSE: Analyze code patterns -TASK: Review implementation patterns -MODE: analysis -CONTEXT: @**/* -EXPECTED: Pattern analysis report -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | analysis=READ-ONLY -" --tool qwen -``` - -#### Tool Fallback Strategy - -```bash -# Primary: Gemini -ccw cli exec "" --tool gemini - -# Fallback: Qwen (if Gemini fails or unavailable) -ccw cli exec "" --tool qwen - -# Check tool availability -ccw cli status -``` - -### Directory Context Configuration - -**CCW Directory Options**: -- `--cd `: Set working directory for execution -- `--includeDirs `: Include additional directories - -#### Critical Directory Scope Rules - -**When using `--cd` to set working directory**: -- @ references ONLY apply to that directory and subdirectories -- `@**/*` = All files within working directory tree -- `@*.ts` = TypeScript files in working directory tree -- `@src/**/*` = Files within src subdirectory -- CANNOT reference parent/sibling directories via @ alone - -**To reference files outside working directory (TWO-STEP REQUIREMENT)**: -1. Add `--includeDirs` parameter to make external directories ACCESSIBLE -2. Explicitly reference external files in CONTEXT field with @ patterns -3. Both steps are MANDATORY - -Example: -```bash -ccw cli exec "CONTEXT: @**/* @../shared/**/*" --tool gemini --cd src/auth --includeDirs ../shared -``` - -**Rule**: If CONTEXT contains `@../dir/**/*`, command MUST include `--includeDirs ../dir` - -#### Multi-Directory Examples - -```bash -# Single additional directory -ccw cli exec "" --tool gemini --cd src/auth --includeDirs ../shared - -# Multiple additional directories -ccw cli exec "" --tool gemini --cd src/auth --includeDirs ../shared,../types,../utils - -# With full prompt template -ccw cli exec " -PURPOSE: Analyze authentication with shared utilities context -TASK: Review auth implementation and its dependencies -MODE: analysis -CONTEXT: @**/* @../shared/**/* @../types/**/* -EXPECTED: Complete analysis with cross-directory dependencies -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Focus on integration patterns | analysis=READ-ONLY -" --tool gemini --cd src/auth --includeDirs ../shared,../types -``` - -### CONTEXT Field Configuration - -CONTEXT field consists of: **File Patterns** + **Memory Context** - -#### File Pattern Reference - -**Default**: `@**/*` (all files - use as default for comprehensive context) - -**Common Patterns**: -- Source files: `@src/**/*` -- TypeScript: `@*.ts @*.tsx` -- With docs: `@CLAUDE.md @**/*CLAUDE.md` -- Tests: `@src/**/*.test.*` - -#### Memory Context Integration - -**Purpose**: Leverage previous session findings, related implementations, and established patterns to provide continuity +### CONTEXT Configuration **Format**: `CONTEXT: [file patterns] | Memory: [memory context]` +#### File Patterns + +| Pattern | Scope | +|---------|-------| +| `@**/*` | All files (default) | +| `@src/**/*.ts` | TypeScript in src | +| `@../shared/**/*` | Sibling directory (requires `--includeDirs`) | +| `@CLAUDE.md` | Specific file | + +#### Memory Context + +Include when building on previous work: + +```bash +# Cross-task reference +Memory: Building on auth refactoring (commit abc123), implementing refresh tokens + +# Cross-module integration +Memory: Integration with auth module, using shared error patterns from @shared/utils/errors.ts +``` + **Memory Sources**: +- **Related Tasks**: Previous refactoring, extensions, conflict resolution +- **Tech Stack Patterns**: Framework conventions, security guidelines +- **Cross-Module References**: Integration points, shared utilities, type dependencies -1. **Related Tasks** - Cross-task context - - Previous refactoring, task extensions, conflict resolution +#### Pattern Discovery Workflow -2. **Tech Stack Patterns** - Framework and library conventions - - React hooks patterns, TypeScript utilities, security guidelines - -3. **Cross-Module References** - Inter-module dependencies - - Integration points, shared utilities, type dependencies - -**Memory Context Examples**: +For complex requirements, discover files BEFORE CLI execution: ```bash -# Example 1: Building on related task -CONTEXT: @src/auth/**/* @CLAUDE.md | Memory: Building on previous auth refactoring (commit abc123), implementing refresh token mechanism following React hooks patterns - -# Example 2: Cross-module integration -CONTEXT: @src/payment/**/* @src/shared/types/**/* | Memory: Integration with auth module from previous implementation, using shared error handling patterns from @shared/utils/errors.ts -``` - -**Best Practices**: -- **Always include memory context** when building on previous work -- **Reference commits/tasks**: Use commit hashes or task IDs for traceability -- **Document dependencies** with explicit file references -- **Cross-reference implementations** with file paths -- **Use consistent format**: `CONTEXT: [file patterns] | Memory: [memory context]` - -#### Complex Pattern Discovery - -For complex file pattern requirements, use semantic discovery BEFORE CLI execution: - -**Tools**: -- `rg (ripgrep)` - Content-based file discovery with regex -- `mcp__code-index__search_code_advanced` - Semantic file search - -**Workflow**: Discover → Extract precise paths → Build CONTEXT field - -**Example**: -```bash -# Step 1: Discover files semantically +# Step 1: Discover files rg "export.*Component" --files-with-matches --type ts -mcp__code-index__search_code_advanced(pattern="interface.*Props", file_pattern="*.tsx") -# Step 2: Build precise CONTEXT with file patterns + memory -CONTEXT: @src/components/Auth.tsx @src/types/auth.d.ts @src/hooks/useAuth.ts | Memory: Previous refactoring identified type inconsistencies, following React hooks patterns +# Step 2: Build CONTEXT +CONTEXT: @components/Auth.tsx @types/auth.d.ts | Memory: Previous type refactoring -# Step 3: Execute CLI with precise references -ccw cli exec " -PURPOSE: Analyze authentication components for type safety improvements -TASK: -• Review auth component patterns and props interfaces -• Identify type inconsistencies in auth components -• Recommend improvements following React best practices -MODE: analysis -CONTEXT: @components/Auth.tsx @types/auth.d.ts @hooks/useAuth.ts | Memory: Previous refactoring identified type inconsistencies, following React hooks patterns, related implementation in @hooks/useAuth.ts (commit abc123) -EXPECTED: Comprehensive analysis report with type safety recommendations, code examples, and references to previous findings -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Focus on type safety and component composition | analysis=READ-ONLY -" --tool gemini --cd src +# Step 3: Execute CLI +ccw cli exec "..." --tool gemini --cd src ``` -### RULES Field Configuration +### RULES Configuration -**Basic Format**: `RULES: $(cat ~/.claude/workflows/cli-templates/prompts/[category]/[template].txt) | [constraints]` +**Format**: `RULES: $(cat ~/.claude/workflows/cli-templates/prompts/[category]/[template].txt) | [constraints]` **Command Substitution Rules**: -- **Template reference only, never read**: Use `$(cat ...)` directly, do NOT read template content first -- **NEVER use escape characters**: `\$`, `\"`, `\'` will break command substitution -- **In prompt context**: Path needs NO quotes (tilde expands correctly) -- **Correct**: `RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/01-trace-code-execution.txt)` -- **WRONG**: `RULES: \$(cat ...)` or `RULES: $(cat \"...\")` -- **Why**: Shell executes `$(...)` in subshell where path is safe +- Use `$(cat ...)` directly - do NOT read template content first +- NEVER use escape characters: `\$`, `\"`, `\'` +- Tilde expands correctly in prompt context **Examples**: -- General template: `$(cat ~/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt) | Focus on authentication module` -- Multiple: `$(cat template1.txt) $(cat template2.txt) | Enterprise standards` -- No template: `Focus on security patterns, include dependency analysis` +```bash +# With template +RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt) | Focus on auth + +# Multiple templates +RULES: $(cat template1.txt) $(cat template2.txt) | Enterprise standards + +# No template +RULES: Focus on security patterns, include dependency analysis | analysis=READ-ONLY +``` ### Template System -**Base**: `~/.claude/workflows/cli-templates/ +**Base Path**: `~/.claude/workflows/cli-templates/prompts/` **Naming Convention**: -- `00-*` - **Universal fallback templates** (use when no specific template matches) -- `01-*` - Universal, high-frequency templates -- `02-*` - Common specialized templates -- `03-*` - Domain-specific, less frequent templates - -**Note**: Number prefix indicates category and frequency, not required usage order. Choose based on task needs. +- `00-*` - Universal fallbacks (when no specific match) +- `01-*` - Universal, high-frequency +- `02-*` - Common specialized +- `03-*` - Domain-specific **Universal Templates**: -When no specific template matches your task requirements, use one of these universal templates based on the desired execution style: - -1. **Rigorous Style** (`universal/00-universal-rigorous-style.txt`) - - **Use for**: Precision-critical tasks requiring systematic methodology - -2. **Creative Style** (`universal/00-universal-creative-style.txt`) - - **Use for**: Exploratory tasks requiring innovative solutions - -**Selection Guide**: -- **Rigorous**: When correctness, reliability, and compliance are paramount -- **Creative**: When innovation, flexibility, and elegant solutions are needed -- **Specific template**: When task matches predefined category (analysis, development, planning, etc.) +| Template | Use For | +|----------|---------| +| `universal/00-universal-rigorous-style.txt` | Precision-critical, systematic methodology | +| `universal/00-universal-creative-style.txt` | Exploratory, innovative solutions | **Task-Template Matrix**: -| Task Type | Tool | Template | -|-----------|------|----------| -| **Universal Fallbacks** | | | -| Precision-Critical Tasks | Gemini/Qwen/Codex | `universal/00-universal-rigorous-style.txt` | -| Exploratory/Innovative Tasks | Gemini/Qwen/Codex | `universal/00-universal-creative-style.txt` | -| **Analysis Tasks** | | | -| Execution Tracing | Gemini (Qwen fallback) | `analysis/01-trace-code-execution.txt` | -| Bug Diagnosis | Gemini (Qwen fallback) | `analysis/01-diagnose-bug-root-cause.txt` | -| Code Pattern Analysis | Gemini (Qwen fallback) | `analysis/02-analyze-code-patterns.txt` | -| Document Analysis | Gemini (Qwen fallback) | `analysis/02-analyze-technical-document.txt` | -| Architecture Review | Gemini (Qwen fallback) | `analysis/02-review-architecture.txt` | -| Code Review | Gemini (Qwen fallback) | `analysis/02-review-code-quality.txt` | -| Performance Analysis | Gemini (Qwen fallback) | `analysis/03-analyze-performance.txt` | -| Security Assessment | Gemini (Qwen fallback) | `analysis/03-assess-security-risks.txt` | -| Quality Standards | Gemini (Qwen fallback) | `analysis/03-review-quality-standards.txt` | -| **Planning Tasks** | | | -| Architecture Planning | Gemini (Qwen fallback) | `planning/01-plan-architecture-design.txt` | -| Task Breakdown | Gemini (Qwen fallback) | `planning/02-breakdown-task-steps.txt` | -| Component Design | Gemini (Qwen fallback) | `planning/02-design-component-spec.txt` | -| Concept Evaluation | Gemini (Qwen fallback) | `planning/03-evaluate-concept-feasibility.txt` | -| Migration Planning | Gemini (Qwen fallback) | `planning/03-plan-migration-strategy.txt` | -| **Development Tasks** | | | -| Feature Development | Codex | `development/02-implement-feature.txt` | -| Refactoring | Codex | `development/02-refactor-codebase.txt` | -| Test Generation | Codex | `development/02-generate-tests.txt` | -| Component Implementation | Codex | `development/02-implement-component-ui.txt` | -| Debugging | Codex | `development/03-debug-runtime-issues.txt` | +| Task Type | Template | +|-----------|----------| +| **Analysis** | | +| Execution Tracing | `analysis/01-trace-code-execution.txt` | +| Bug Diagnosis | `analysis/01-diagnose-bug-root-cause.txt` | +| Code Patterns | `analysis/02-analyze-code-patterns.txt` | +| Document Analysis | `analysis/02-analyze-technical-document.txt` | +| Architecture Review | `analysis/02-review-architecture.txt` | +| Code Review | `analysis/02-review-code-quality.txt` | +| Performance | `analysis/03-analyze-performance.txt` | +| Security | `analysis/03-assess-security-risks.txt` | +| **Planning** | | +| Architecture | `planning/01-plan-architecture-design.txt` | +| Task Breakdown | `planning/02-breakdown-task-steps.txt` | +| Component Design | `planning/02-design-component-spec.txt` | +| Migration | `planning/03-plan-migration-strategy.txt` | +| **Development** | | +| Feature | `development/02-implement-feature.txt` | +| Refactoring | `development/02-refactor-codebase.txt` | +| Tests | `development/02-generate-tests.txt` | +| UI Component | `development/02-implement-component-ui.txt` | +| Debugging | `development/03-debug-runtime-issues.txt` | + --- -## Execution Configuration +## CLI Execution -### Dynamic Timeout Allocation +### Command Options -**Minimum timeout: 5 minutes (300000ms)** - Never set below this threshold. +| Option | Description | Default | +|--------|-------------|---------| +| `--tool ` | gemini, qwen, codex | gemini | +| `--mode ` | analysis, write, auto | analysis | +| `--model ` | Model override | auto-select | +| `--cd ` | Working directory | current | +| `--includeDirs ` | Additional directories (comma-separated) | none | +| `--timeout ` | Timeout in milliseconds | 300000 | +| `--resume [id]` | Resume previous session | - | +| `--no-stream` | Disable streaming | false | -**Timeout Ranges**: -- **Simple** (analysis, search): 5-10min (300000-600000ms) -- **Medium** (refactoring, documentation): 10-20min (600000-1200000ms) -- **Complex** (implementation, migration): 20-60min (1200000-3600000ms) -- **Heavy** (large codebase, multi-file): 60-120min (3600000-7200000ms) +### Directory Configuration -**Codex Multiplier**: 3x of allocated time (minimum 15min / 900000ms) +#### Working Directory (`--cd`) + +When using `--cd`: +- `@**/*` = Files within working directory tree only +- CANNOT reference parent/sibling via @ alone +- Must use `--includeDirs` for external directories + +#### Include Directories (`--includeDirs`) + +**TWO-STEP requirement for external files**: +1. Add `--includeDirs` parameter +2. Reference in CONTEXT with @ patterns -**CCW Timeout Usage**: ```bash -ccw cli exec "" --tool gemini --timeout 600000 # 10 minutes -ccw cli exec "" --tool codex --timeout 1800000 # 30 minutes +# Single directory +ccw cli exec "CONTEXT: @**/* @../shared/**/*" --cd src/auth --includeDirs ../shared + +# Multiple directories +ccw cli exec "..." --cd src/auth --includeDirs ../shared,../types,../utils ``` -**Auto-detection**: Analyze PURPOSE and TASK fields to determine timeout +**Rule**: If CONTEXT contains `@../dir/**/*`, MUST include `--includeDirs ../dir` + +**Benefits**: Excludes unrelated directories, reduces token usage + +### CCW Parameter Mapping + +CCW automatically maps to tool-specific syntax: + +| CCW Parameter | Gemini/Qwen | Codex | +|---------------|-------------|-------| +| `--cd ` | `cd &&` | `-C ` | +| `--includeDirs ` | `--include-directories` | `--add-dir` (per dir) | +| `--mode write` | `--approval-mode yolo` | `-s danger-full-access` | +| `--mode auto` | N/A | `-s danger-full-access` | + +### Command Examples + +```bash +# Analysis (default) +ccw cli exec " +PURPOSE: Analyze authentication +TASK: • Review patterns • Identify risks +MODE: analysis +CONTEXT: @**/* @../shared/**/* +EXPECTED: Analysis report +RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | analysis=READ-ONLY +" --tool gemini --cd src/auth --includeDirs ../shared + +# Write mode +ccw cli exec " +PURPOSE: Generate API docs +TASK: • Create docs • Add examples +MODE: write +CONTEXT: @src/api/**/* +EXPECTED: Complete documentation +RULES: $(cat ~/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt) | write=CREATE/MODIFY/DELETE +" --tool gemini --mode write + +# Auto mode (Codex) +ccw cli exec " +PURPOSE: Implement auth module +TASK: • Create service • Add validation • Setup JWT +MODE: auto +CONTEXT: @**/* | Memory: Following project security patterns +EXPECTED: Complete module with tests +RULES: $(cat ~/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt) | auto=FULL +" --tool codex --mode auto + +# Fallback strategy +ccw cli exec "" --tool gemini # Primary +ccw cli exec "" --tool qwen # Fallback +``` + +--- + +## Configuration + +### Timeout Allocation + +**Minimum**: 5 minutes (300000ms) + +| Complexity | Range | Examples | +|------------|-------|----------| +| Simple | 5-10min (300000-600000ms) | Analysis, search | +| Medium | 10-20min (600000-1200000ms) | Refactoring, documentation | +| Complex | 20-60min (1200000-3600000ms) | Implementation, migration | +| Heavy | 60-120min (3600000-7200000ms) | Large codebase, multi-file | + +**Codex Multiplier**: 3x allocated time (minimum 15min / 900000ms) + +```bash +ccw cli exec "" --tool gemini --timeout 600000 # 10 min +ccw cli exec "" --tool codex --timeout 1800000 # 30 min +``` ### Permission Framework -**Single-Use Explicit Authorization**: Each CLI execution requires explicit user command instruction - one command authorizes ONE execution only. Analysis does NOT authorize write operations. Previous authorization does NOT carry over. Each operation needs NEW explicit user directive. +**Single-Use Authorization**: Each execution requires explicit user instruction. Previous authorization does NOT carry over. **Mode Hierarchy**: -- **analysis** (default): Read-only, safe for auto-execution -- **write**: Requires explicit `--mode write` specification -- **auto**: Requires explicit `--mode auto` specification +- `analysis` (default): Read-only, safe for auto-execution +- `write`: Requires explicit `--mode write` +- `auto`: Requires explicit `--mode auto` - **Exception**: User provides clear instructions like "modify", "create", "implement" -**CCW Mode Permissions**: -```bash -# Analysis (default, no special permissions) -ccw cli exec "" --tool gemini - -# Write mode (enables file modifications) -ccw cli exec "" --tool gemini --mode write - -# Auto mode (full autonomous operations, Codex only) -ccw cli exec "" --tool codex --mode auto -``` - -**Default**: All tools default to analysis/read-only mode - --- ## Best Practices ### Workflow Principles -- **Use CCW unified interface** - `ccw cli exec` for all tool executions -- **Start with templates** - Use predefined templates for consistency -- **Be specific** - Clear PURPOSE, TASK, and EXPECTED fields with detailed descriptions -- **Include constraints** - File patterns, scope, requirements in RULES -- **Leverage memory context** - ALWAYS include Memory field when building on previous work - - Cross-reference tasks with file paths and commit hashes - - Document dependencies with explicit file references - - Reference related implementations and patterns -- **Discover patterns first** - Use rg/MCP for complex file discovery before CLI execution -- **Build precise CONTEXT** - Convert discovery to explicit file references with memory - - Format: `CONTEXT: [file patterns] | Memory: [memory context]` - - File patterns: `@**/*` (default) or specific patterns - - Memory: Previous sessions, tech stack patterns, cross-references -- **Document context** - Always reference CLAUDE.md and relevant documentation +- **Use CCW unified interface** for all executions +- **Start with templates** for consistency +- **Be specific** - Clear PURPOSE, TASK, EXPECTED fields +- **Include constraints** - File patterns, scope in RULES +- **Leverage memory context** when building on previous work +- **Discover patterns first** - Use rg/MCP before CLI execution - **Default to full context** - Use `@**/*` unless specific files needed -- **No escape characters** - NEVER use `\$`, `\"`, `\'` in CLI commands - -### Context Optimization Strategy - -**Directory Navigation**: Use `--cd [directory]` to focus on specific directory - -**When to set working directory**: -- Specific directory mentioned → Use `--cd directory` -- Focused analysis needed → Target specific directory -- Multi-directory scope → Use `--cd` + `--includeDirs` - -**When to use `--includeDirs`**: -- Working in subdirectory but need parent/sibling context -- Cross-directory dependency analysis required -- Multiple related modules need simultaneous access -- **Key benefit**: Excludes unrelated directories, reduces token usage ### Workflow Integration -When planning any coding task, **ALWAYS** integrate CLI tools via CCW: - -1. **Understanding Phase**: `ccw cli exec "" --tool gemini` -2. **Architecture Phase**: `ccw cli exec "" --tool gemini` -3. **Implementation Phase**: `ccw cli exec "" --tool codex --mode auto` -4. **Quality Phase**: `ccw cli exec "" --tool codex --mode write` +| Phase | Command | +|-------|---------| +| Understanding | `ccw cli exec "" --tool gemini` | +| Architecture | `ccw cli exec "" --tool gemini` | +| Implementation | `ccw cli exec "" --tool codex --mode auto` | +| Quality | `ccw cli exec "" --tool codex --mode write` | ### Planning Checklist -For every development task: - [ ] **Purpose defined** - Clear goal and intent -- [ ] **Mode selected** - Execution mode (`--mode analysis|write|auto`) -- [ ] **Context gathered** - File references and session memory documented (default `@**/*`) -- [ ] **Directory navigation** - Determine if `--cd` or `--cd + --includeDirs` needed -- [ ] **Tool selected** - `--tool gemini|qwen|codex` based on task type -- [ ] **Template applied** - Use Standard Prompt Template -- [ ] **Constraints specified** - File patterns, scope, requirements -- [ ] **Timeout configured** - `--timeout` based on task complexity - +- [ ] **Mode selected** - `--mode analysis|write|auto` +- [ ] **Context gathered** - File references + memory (default `@**/*`) +- [ ] **Directory navigation** - `--cd` and/or `--includeDirs` +- [ ] **Tool selected** - `--tool gemini|qwen|codex` +- [ ] **Template applied** - Standard Prompt Template +- [ ] **Constraints specified** - Scope, requirements +- [ ] **Timeout configured** - Based on complexity diff --git a/ccw/src/cli.ts b/ccw/src/cli.ts index 138f5966..0b425363 100644 --- a/ccw/src/cli.ts +++ b/ccw/src/cli.ts @@ -161,6 +161,7 @@ export function run(argv: string[]): void { .option('--limit ', 'History limit') .option('--status ', 'Filter by status') .option('--resume [id]', 'Resume previous session (empty=last, or execution ID)') + .option('--id ', 'Custom execution ID (e.g., IMPL-001-step1)') .action((subcommand, args, options) => cliCommand(subcommand, args, options)); program.parse(argv); diff --git a/ccw/src/commands/cli.ts b/ccw/src/commands/cli.ts index a0c5cae1..00e23f5e 100644 --- a/ccw/src/commands/cli.ts +++ b/ccw/src/commands/cli.ts @@ -8,7 +8,8 @@ import { cliExecutorTool, getCliToolsStatus, getExecutionHistory, - getExecutionDetail + getExecutionDetail, + getConversationDetail } from '../tools/cli-executor.js'; interface CliExecOptions { @@ -20,6 +21,7 @@ interface CliExecOptions { timeout?: string; noStream?: boolean; resume?: string | boolean; // true = last, string = execution ID + id?: string; // Custom execution ID (e.g., IMPL-001-step1) } interface HistoryOptions { @@ -61,11 +63,30 @@ async function execAction(prompt: string | undefined, options: CliExecOptions): process.exit(1); } - const { tool = 'gemini', mode = 'analysis', model, cd, includeDirs, timeout, noStream, resume } = options; + const { tool = 'gemini', mode = 'analysis', model, cd, includeDirs, timeout, noStream, resume, id } = options; + + // Parse resume IDs for merge scenario + const resumeIds = resume && typeof resume === 'string' ? resume.split(',').map(s => s.trim()).filter(Boolean) : []; + const isMerge = resumeIds.length > 1; // Show execution mode - const resumeInfo = resume ? (typeof resume === 'string' ? ` resuming ${resume}` : ' resuming last') : ''; - console.log(chalk.cyan(`\n Executing ${tool} (${mode} mode${resumeInfo})...\n`)); + let resumeInfo = ''; + if (isMerge) { + resumeInfo = ` merging ${resumeIds.length} conversations`; + } else if (resume) { + resumeInfo = typeof resume === 'string' ? ` resuming ${resume}` : ' resuming last'; + } + const idInfo = id ? ` [${id}]` : ''; + console.log(chalk.cyan(`\n Executing ${tool} (${mode} mode${resumeInfo})${idInfo}...\n`)); + + // Show merge details + if (isMerge) { + console.log(chalk.gray(' Merging conversations:')); + for (const rid of resumeIds) { + console.log(chalk.gray(` • ${rid}`)); + } + console.log(); + } // Streaming output handler const onOutput = noStream ? null : (chunk: any) => { @@ -81,7 +102,8 @@ async function execAction(prompt: string | undefined, options: CliExecOptions): cd, includeDirs, timeout: timeout ? parseInt(timeout, 10) : 300000, - resume // pass resume parameter + resume, + id // custom execution ID }, onOutput); // If not streaming, print output now @@ -89,12 +111,25 @@ async function execAction(prompt: string | undefined, options: CliExecOptions): console.log(result.stdout); } - // Print summary with execution ID for resume + // Print summary with execution ID and turn info console.log(); if (result.success) { - console.log(chalk.green(` ✓ Completed in ${(result.execution.duration_ms / 1000).toFixed(1)}s`)); + const turnInfo = result.conversation.turn_count > 1 + ? ` (turn ${result.conversation.turn_count})` + : ''; + console.log(chalk.green(` ✓ Completed in ${(result.execution.duration_ms / 1000).toFixed(1)}s${turnInfo}`)); console.log(chalk.gray(` ID: ${result.execution.id}`)); - console.log(chalk.dim(` Resume: ccw cli exec "..." --resume ${result.execution.id}`)); + if (isMerge && !id) { + // Merge without custom ID: updated all source conversations + console.log(chalk.gray(` Updated ${resumeIds.length} conversations: ${resumeIds.join(', ')}`)); + } else if (isMerge && id) { + // Merge with custom ID: created new merged conversation + console.log(chalk.gray(` Created merged conversation from ${resumeIds.length} sources`)); + } + if (result.conversation.turn_count > 1) { + console.log(chalk.gray(` Total: ${result.conversation.turn_count} turns, ${(result.conversation.total_duration_ms / 1000).toFixed(1)}s`)); + } + console.log(chalk.dim(` Continue: ccw cli exec "..." --resume ${result.execution.id}`)); } else { console.log(chalk.red(` ✗ Failed (${result.execution.status})`)); console.log(chalk.gray(` ID: ${result.execution.id}`)); @@ -135,9 +170,10 @@ async function historyAction(options: HistoryOptions): Promise { ? `${(exec.duration_ms / 1000).toFixed(1)}s` : `${exec.duration_ms}ms`; - const timeAgo = getTimeAgo(new Date(exec.timestamp)); + const timeAgo = getTimeAgo(new Date(exec.updated_at || exec.timestamp)); + const turnInfo = exec.turn_count && exec.turn_count > 1 ? chalk.cyan(` [${exec.turn_count} turns]`) : ''; - console.log(` ${statusIcon} ${chalk.bold.white(exec.tool.padEnd(8))} ${chalk.gray(timeAgo.padEnd(12))} ${chalk.gray(duration.padEnd(8))}`); + console.log(` ${statusIcon} ${chalk.bold.white(exec.tool.padEnd(8))} ${chalk.gray(timeAgo.padEnd(12))} ${chalk.gray(duration.padEnd(8))}${turnInfo}`); console.log(chalk.gray(` ${exec.prompt_preview}`)); console.log(chalk.dim(` ID: ${exec.id}`)); console.log(); @@ -145,49 +181,60 @@ async function historyAction(options: HistoryOptions): Promise { } /** - * Show execution detail - * @param {string} executionId - Execution ID + * Show conversation detail with all turns + * @param {string} conversationId - Conversation ID */ -async function detailAction(executionId: string | undefined): Promise { - if (!executionId) { - console.error(chalk.red('Error: Execution ID is required')); - console.error(chalk.gray('Usage: ccw cli detail ')); +async function detailAction(conversationId: string | undefined): Promise { + if (!conversationId) { + console.error(chalk.red('Error: Conversation ID is required')); + console.error(chalk.gray('Usage: ccw cli detail ')); process.exit(1); } - const detail = getExecutionDetail(process.cwd(), executionId); + const conversation = getConversationDetail(process.cwd(), conversationId); - if (!detail) { - console.error(chalk.red(`Error: Execution not found: ${executionId}`)); + if (!conversation) { + console.error(chalk.red(`Error: Conversation not found: ${conversationId}`)); process.exit(1); } - console.log(chalk.bold.cyan('\n Execution Detail\n')); - console.log(` ${chalk.gray('ID:')} ${detail.id}`); - console.log(` ${chalk.gray('Tool:')} ${detail.tool}`); - console.log(` ${chalk.gray('Model:')} ${detail.model}`); - console.log(` ${chalk.gray('Mode:')} ${detail.mode}`); - console.log(` ${chalk.gray('Status:')} ${detail.status}`); - console.log(` ${chalk.gray('Duration:')} ${detail.duration_ms}ms`); - console.log(` ${chalk.gray('Timestamp:')} ${detail.timestamp}`); - - console.log(chalk.bold.cyan('\n Prompt:\n')); - console.log(chalk.gray(' ' + detail.prompt.split('\n').join('\n '))); - - if (detail.output.stdout) { - console.log(chalk.bold.cyan('\n Output:\n')); - console.log(detail.output.stdout); + console.log(chalk.bold.cyan('\n Conversation Detail\n')); + console.log(` ${chalk.gray('ID:')} ${conversation.id}`); + console.log(` ${chalk.gray('Tool:')} ${conversation.tool}`); + console.log(` ${chalk.gray('Model:')} ${conversation.model}`); + console.log(` ${chalk.gray('Mode:')} ${conversation.mode}`); + console.log(` ${chalk.gray('Status:')} ${conversation.latest_status}`); + console.log(` ${chalk.gray('Turns:')} ${conversation.turn_count}`); + console.log(` ${chalk.gray('Duration:')} ${(conversation.total_duration_ms / 1000).toFixed(1)}s total`); + console.log(` ${chalk.gray('Created:')} ${conversation.created_at}`); + if (conversation.turn_count > 1) { + console.log(` ${chalk.gray('Updated:')} ${conversation.updated_at}`); } - if (detail.output.stderr) { - console.log(chalk.bold.red('\n Errors:\n')); - console.log(detail.output.stderr); - } - - if (detail.output.truncated) { - console.log(chalk.yellow('\n Note: Output was truncated due to size.')); + // Show all turns + for (const turn of conversation.turns) { + console.log(chalk.bold.cyan(`\n ═══ Turn ${turn.turn} ═══`)); + console.log(chalk.gray(` ${turn.timestamp} | ${turn.status} | ${(turn.duration_ms / 1000).toFixed(1)}s`)); + + console.log(chalk.bold.white('\n Prompt:')); + console.log(chalk.gray(' ' + turn.prompt.split('\n').join('\n '))); + + if (turn.output.stdout) { + console.log(chalk.bold.white('\n Output:')); + console.log(turn.output.stdout); + } + + if (turn.output.stderr) { + console.log(chalk.bold.red('\n Errors:')); + console.log(turn.output.stderr); + } + + if (turn.output.truncated) { + console.log(chalk.yellow('\n Note: Output was truncated due to size.')); + } } + console.log(chalk.dim(`\n Continue: ccw cli exec "..." --resume ${conversation.id}`)); console.log(); } @@ -256,6 +303,7 @@ export async function cliCommand( console.log(chalk.gray(' --timeout Timeout in milliseconds (default: 300000)')); console.log(chalk.gray(' --no-stream Disable streaming output')); console.log(chalk.gray(' --resume [id] Resume previous session (empty=last, or execution ID)')); + console.log(chalk.gray(' --id Custom execution ID (e.g., IMPL-001-step1)')); console.log(); console.log(' History Options:'); console.log(chalk.gray(' --limit Number of results (default: 20)')); diff --git a/ccw/src/core/server.ts b/ccw/src/core/server.ts index 622e09fe..4b4cd6dc 100644 --- a/ccw/src/core/server.ts +++ b/ccw/src/core/server.ts @@ -8,7 +8,7 @@ import { createHash } from 'crypto'; import { scanSessions } from './session-scanner.js'; import { aggregateData } from './data-aggregator.js'; import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js'; -import { getCliToolsStatus, getExecutionHistory, getExecutionDetail, deleteExecution, executeCliTool } from '../tools/cli-executor.js'; +import { getCliToolsStatus, getExecutionHistory, getExecutionDetail, getConversationDetail, deleteExecution, executeCliTool } from '../tools/cli-executor.js'; import { getAllManifests } from './manifest.js'; import { checkVenvStatus, bootstrapVenv, executeCodexLens, checkSemanticStatus, installSemantic } from '../tools/codex-lens.js'; import { listTools } from '../tools/index.js'; @@ -673,16 +673,16 @@ export async function startServer(options: ServerOptions = {}): Promise 1 + ? `${exec.turn_count} turns` + : ''; return `
${exec.tool} + ${turnBadge} ${timeAgo}
@@ -163,50 +167,102 @@ function renderToolFilter() { // ========== Execution Detail Modal ========== async function showExecutionDetail(executionId, sourceDir) { - const detail = await loadExecutionDetail(executionId, sourceDir); - if (!detail) { - showRefreshToast('Execution not found', 'error'); + const conversation = await loadExecutionDetail(executionId, sourceDir); + if (!conversation) { + showRefreshToast('Conversation not found', 'error'); return; } + // Handle both old (single execution) and new (conversation) formats + const isConversation = conversation.turns && Array.isArray(conversation.turns); + const turnCount = isConversation ? conversation.turn_count : 1; + const totalDuration = isConversation ? conversation.total_duration_ms : conversation.duration_ms; + const latestStatus = isConversation ? conversation.latest_status : conversation.status; + const createdAt = isConversation ? conversation.created_at : conversation.timestamp; + + // Build turns HTML + let turnsHtml = ''; + if (isConversation && conversation.turns.length > 0) { + turnsHtml = conversation.turns.map((turn, idx) => ` +
+
+ Turn ${turn.turn} + ${turn.status} + ${formatDuration(turn.duration_ms)} +
+
+

Prompt

+
${escapeHtml(turn.prompt)}
+
+ ${turn.output.stdout ? ` +
+

Output

+
${escapeHtml(turn.output.stdout)}
+
+ ` : ''} + ${turn.output.stderr ? ` +
+

Errors

+
${escapeHtml(turn.output.stderr)}
+
+ ` : ''} + ${turn.output.truncated ? ` +

+ + Output was truncated due to size. +

+ ` : ''} +
+ `).join('
'); + } else { + // Legacy single execution format + const detail = conversation; + turnsHtml = ` +
+

Prompt

+
${escapeHtml(detail.prompt)}
+
+ ${detail.output.stdout ? ` +
+

Output

+
${escapeHtml(detail.output.stdout)}
+
+ ` : ''} + ${detail.output.stderr ? ` +
+

Errors

+
${escapeHtml(detail.output.stderr)}
+
+ ` : ''} + ${detail.output.truncated ? ` +

+ + Output was truncated due to size. +

+ ` : ''} + `; + } + const modalContent = `
- ${detail.tool} - ${detail.status} - ${formatDuration(detail.duration_ms)} + ${conversation.tool} + ${turnCount > 1 ? `${turnCount} turns` : ''} + ${latestStatus} + ${formatDuration(totalDuration)}
- ${detail.model || 'default'} - ${detail.mode} - ${new Date(detail.timestamp).toLocaleString()} + ${conversation.model || 'default'} + ${conversation.mode} + ${new Date(createdAt).toLocaleString()}
-
-

Prompt

-
${escapeHtml(detail.prompt)}
+
+ ${turnsHtml}
- ${detail.output.stdout ? ` -
-

Output

-
${escapeHtml(detail.output.stdout)}
-
- ` : ''} - ${detail.output.stderr ? ` -
-

Errors

-
${escapeHtml(detail.output.stderr)}
-
- ` : ''} - ${detail.output.truncated ? ` -

- - Output was truncated due to size. -

- ` : ''}
-
`; - showModal('Execution Detail', modalContent); + showModal('Conversation Detail', modalContent); } // ========== Actions ========== @@ -269,7 +325,7 @@ async function deleteExecution(executionId) { } } -// ========== Copy Prompt ========== +// ========== Copy Functions ========== async function copyExecutionPrompt(executionId) { const detail = await loadExecutionDetail(executionId); if (!detail) { @@ -287,6 +343,17 @@ async function copyExecutionPrompt(executionId) { } } +async function copyConversationId(conversationId) { + if (navigator.clipboard) { + try { + await navigator.clipboard.writeText(conversationId); + showRefreshToast('ID copied to clipboard', 'success'); + } catch (err) { + showRefreshToast('Failed to copy', 'error'); + } + } +} + // ========== Helpers ========== function formatDuration(ms) { if (ms >= 60000) { diff --git a/ccw/src/tools/cli-executor.ts b/ccw/src/tools/cli-executor.ts index 739faa72..d9029ed3 100644 --- a/ccw/src/tools/cli-executor.ts +++ b/ccw/src/tools/cli-executor.ts @@ -21,7 +21,8 @@ const ParamsSchema = z.object({ cd: z.string().optional(), includeDirs: z.string().optional(), timeout: z.number().default(300000), - resume: z.union([z.boolean(), z.string()]).optional(), // true = last, string = execution ID + resume: z.union([z.boolean(), z.string()]).optional(), // true = last, string = single ID or comma-separated IDs + id: z.string().optional(), // Custom execution ID (e.g., IMPL-001-step1) }); type Params = z.infer; @@ -31,6 +32,36 @@ interface ToolAvailability { path: string | null; } +// Single turn in a conversation +interface ConversationTurn { + turn: number; + timestamp: string; + prompt: string; + duration_ms: number; + status: 'success' | 'error' | 'timeout'; + exit_code: number | null; + output: { + stdout: string; + stderr: string; + truncated: boolean; + }; +} + +// Multi-turn conversation record +interface ConversationRecord { + id: string; + created_at: string; + updated_at: string; + tool: string; + model: string; + mode: string; + total_duration_ms: number; + turn_count: number; + latest_status: 'success' | 'error' | 'timeout'; + turns: ConversationTurn[]; +} + +// Legacy single execution record (for backward compatibility) interface ExecutionRecord { id: string; timestamp: string; @@ -53,10 +84,12 @@ interface HistoryIndex { total_executions: number; executions: { id: string; - timestamp: string; + timestamp: string; // created_at for conversations + updated_at?: string; // last update time tool: string; status: string; duration_ms: number; + turn_count?: number; // number of turns in conversation prompt_preview: string; }[]; } @@ -64,6 +97,7 @@ interface HistoryIndex { interface ExecutionOutput { success: boolean; execution: ExecutionRecord; + conversation: ConversationRecord; // Full conversation record stdout: string; stderr: string; } @@ -206,33 +240,47 @@ function loadHistoryIndex(historyDir: string): HistoryIndex { } /** - * Save execution to history + * Save conversation to history (create new or append turn) */ -function saveExecution(historyDir: string, execution: ExecutionRecord): void { - // Create date-based subdirectory - const dateStr = new Date().toISOString().split('T')[0]; +function saveConversation(historyDir: string, conversation: ConversationRecord): void { + // Create date-based subdirectory using created_at date + const dateStr = conversation.created_at.split('T')[0]; const dateDir = join(historyDir, dateStr); if (!existsSync(dateDir)) { mkdirSync(dateDir, { recursive: true }); } - // Save execution record - const filename = `${execution.id}.json`; - writeFileSync(join(dateDir, filename), JSON.stringify(execution, null, 2), 'utf8'); + // Save conversation record + const filename = `${conversation.id}.json`; + writeFileSync(join(dateDir, filename), JSON.stringify(conversation, null, 2), 'utf8'); // Update index const index = loadHistoryIndex(historyDir); - index.total_executions++; - // Add to executions (keep last 100 in index) - index.executions.unshift({ - id: execution.id, - timestamp: execution.timestamp, - tool: execution.tool, - status: execution.status, - duration_ms: execution.duration_ms, - prompt_preview: execution.prompt.substring(0, 100) + (execution.prompt.length > 100 ? '...' : '') - }); + // Check if this conversation already exists in index + const existingIdx = index.executions.findIndex(e => e.id === conversation.id); + const latestTurn = conversation.turns[conversation.turns.length - 1]; + + const indexEntry = { + id: conversation.id, + timestamp: conversation.created_at, + updated_at: conversation.updated_at, + tool: conversation.tool, + status: conversation.latest_status, + duration_ms: conversation.total_duration_ms, + turn_count: conversation.turn_count, + prompt_preview: latestTurn.prompt.substring(0, 100) + (latestTurn.prompt.length > 100 ? '...' : '') + }; + + if (existingIdx >= 0) { + // Update existing entry and move to top + index.executions.splice(existingIdx, 1); + index.executions.unshift(indexEntry); + } else { + // Add new entry + index.total_executions++; + index.executions.unshift(indexEntry); + } if (index.executions.length > 100) { index.executions = index.executions.slice(0, 100); @@ -241,6 +289,137 @@ function saveExecution(historyDir: string, execution: ExecutionRecord): void { writeFileSync(join(historyDir, 'index.json'), JSON.stringify(index, null, 2), 'utf8'); } +/** + * Load existing conversation by ID + */ +function loadConversation(historyDir: string, conversationId: string): ConversationRecord | null { + // Search in all date directories + if (existsSync(historyDir)) { + const dateDirs = readdirSync(historyDir).filter(d => { + const dirPath = join(historyDir, d); + return statSync(dirPath).isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(d); + }); + + // Search newest first + for (const dateDir of dateDirs.sort().reverse()) { + const filePath = join(historyDir, dateDir, `${conversationId}.json`); + if (existsSync(filePath)) { + try { + const data = JSON.parse(readFileSync(filePath, 'utf8')); + // Check if it's a conversation record (has turns array) + if (data.turns && Array.isArray(data.turns)) { + return data as ConversationRecord; + } + // Convert legacy ExecutionRecord to ConversationRecord + return convertToConversation(data); + } catch { + continue; + } + } + } + } + return null; +} + +/** + * Convert legacy ExecutionRecord to ConversationRecord + */ +function convertToConversation(record: ExecutionRecord): ConversationRecord { + return { + id: record.id, + created_at: record.timestamp, + updated_at: record.timestamp, + tool: record.tool, + model: record.model, + mode: record.mode, + total_duration_ms: record.duration_ms, + turn_count: 1, + latest_status: record.status, + turns: [{ + turn: 1, + timestamp: record.timestamp, + prompt: record.prompt, + duration_ms: record.duration_ms, + status: record.status, + exit_code: record.exit_code, + output: record.output + }] + }; +} + +/** + * Merge multiple conversations into a unified context + * Returns merged turns sorted by timestamp with source tracking + */ +interface MergedTurn extends ConversationTurn { + source_id: string; // Original conversation ID +} + +interface MergeResult { + mergedTurns: MergedTurn[]; + sourceConversations: ConversationRecord[]; + totalDuration: number; +} + +function mergeConversations(conversations: ConversationRecord[]): MergeResult { + const mergedTurns: MergedTurn[] = []; + + // Collect all turns with source tracking + for (const conv of conversations) { + for (const turn of conv.turns) { + mergedTurns.push({ + ...turn, + source_id: conv.id + }); + } + } + + // Sort by timestamp + mergedTurns.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + + // Re-number turns + mergedTurns.forEach((turn, idx) => { + turn.turn = idx + 1; + }); + + // Calculate total duration + const totalDuration = mergedTurns.reduce((sum, t) => sum + t.duration_ms, 0); + + return { + mergedTurns, + sourceConversations: conversations, + totalDuration + }; +} + +/** + * Build prompt from merged conversations + */ +function buildMergedPrompt(mergeResult: MergeResult, newPrompt: string): string { + const parts: string[] = []; + + parts.push('=== MERGED CONVERSATION HISTORY ==='); + parts.push(`(From ${mergeResult.sourceConversations.length} conversations: ${mergeResult.sourceConversations.map(c => c.id).join(', ')})`); + parts.push(''); + + // Add all merged turns with source tracking + for (const turn of mergeResult.mergedTurns) { + parts.push(`--- Turn ${turn.turn} [${turn.source_id}] ---`); + parts.push('USER:'); + parts.push(turn.prompt); + parts.push(''); + parts.push('ASSISTANT:'); + parts.push(turn.output.stdout || '[No output recorded]'); + parts.push(''); + } + + parts.push('=== NEW REQUEST ==='); + parts.push(''); + parts.push(newPrompt); + + return parts.join('\n'); +} + /** * Execute CLI tool with streaming output */ @@ -253,17 +432,95 @@ async function executeCliTool( throw new Error(`Invalid params: ${parsed.error.message}`); } - const { tool, prompt, mode, model, cd, includeDirs, timeout, resume } = parsed.data; + const { tool, prompt, mode, model, cd, includeDirs, timeout, resume, id: customId } = parsed.data; - // Determine working directory early (needed for resume lookup) + // Determine working directory early (needed for conversation lookup) const workingDir = cd || process.cwd(); + const historyDir = ensureHistoryDir(workingDir); - // Build final prompt (with resume context if applicable) + // Determine conversation ID and load existing conversation + // Logic: + // - If --resume (multiple IDs): merge conversations + // - With --id: create new merged conversation + // - Without --id: append to ALL source conversations + // - If --resume AND --id : fork - read context from resume ID, create new conversation with newId + // - If --id provided (no resume): use that ID (create new or append) + // - If --resume without --id: use resume ID (append to existing) + // - No params: create new with auto-generated ID + let conversationId: string; + let existingConversation: ConversationRecord | null = null; + let contextConversation: ConversationRecord | null = null; // For fork scenario + let mergeResult: MergeResult | null = null; // For merge scenario + let sourceConversations: ConversationRecord[] = []; // All source conversations for merge + + // Parse resume IDs (can be comma-separated for merge) + const resumeIds: string[] = resume + ? (typeof resume === 'string' ? resume.split(',').map(id => id.trim()).filter(Boolean) : []) + : []; + const isMerge = resumeIds.length > 1; + const resumeId = resumeIds.length === 1 ? resumeIds[0] : null; + + if (isMerge) { + // Merge scenario: multiple resume IDs + sourceConversations = resumeIds + .map(id => loadConversation(historyDir, id)) + .filter((c): c is ConversationRecord => c !== null); + + if (sourceConversations.length === 0) { + throw new Error('No valid conversations found for merge'); + } + + mergeResult = mergeConversations(sourceConversations); + + if (customId) { + // Create new merged conversation with custom ID + conversationId = customId; + existingConversation = loadConversation(historyDir, customId); + } else { + // Will append to ALL source conversations (handled in save logic) + // Use first source conversation ID as primary + conversationId = sourceConversations[0].id; + existingConversation = sourceConversations[0]; + } + } else if (customId && resumeId) { + // Fork: read context from resume ID, but create new conversation with custom ID + conversationId = customId; + contextConversation = loadConversation(historyDir, resumeId); + existingConversation = loadConversation(historyDir, customId); + } else if (customId) { + // Use custom ID - may be new or existing + conversationId = customId; + existingConversation = loadConversation(historyDir, customId); + } else if (resumeId) { + // Resume single ID without new ID - append to existing conversation + conversationId = resumeId; + existingConversation = loadConversation(historyDir, resumeId); + } else if (resume) { + // resume=true: get last conversation for this tool + const history = getExecutionHistory(workingDir, { limit: 1, tool }); + if (history.executions.length > 0) { + conversationId = history.executions[0].id; + existingConversation = loadConversation(historyDir, conversationId); + } else { + // No previous conversation, create new + conversationId = `${Date.now()}-${tool}`; + } + } else { + // New conversation with auto-generated ID + conversationId = `${Date.now()}-${tool}`; + } + + // Build final prompt with conversation context + // For merge: use merged context from all source conversations + // For fork: use contextConversation (from resume ID) for prompt context + // For append: use existingConversation (from target ID) let finalPrompt = prompt; - if (resume) { - const previousExecution = getPreviousExecution(workingDir, tool, resume); - if (previousExecution) { - finalPrompt = buildContinuationPrompt(previousExecution, prompt); + if (mergeResult && mergeResult.mergedTurns.length > 0) { + finalPrompt = buildMergedPrompt(mergeResult, prompt); + } else { + const conversationForContext = contextConversation || existingConversation; + if (conversationForContext && conversationForContext.turns.length > 0) { + finalPrompt = buildMultiTurnPrompt(conversationForContext, prompt); } } @@ -283,8 +540,6 @@ async function executeCliTool( include: includeDirs }); - // Create execution record - const executionId = `${Date.now()}-${tool}`; const startTime = Date.now(); return new Promise((resolve, reject) => { @@ -356,9 +611,135 @@ async function executeCliTool( } } - // Create execution record + // Create new turn + const newTurnOutput = { + stdout: stdout.substring(0, 10240), // Truncate to 10KB + stderr: stderr.substring(0, 2048), // Truncate to 2KB + truncated: stdout.length > 10240 || stderr.length > 2048 + }; + + // Determine base turn number for merge scenarios + const baseTurnNumber = isMerge && mergeResult + ? mergeResult.mergedTurns.length + 1 + : (existingConversation ? existingConversation.turns.length + 1 : 1); + + const newTurn: ConversationTurn = { + turn: baseTurnNumber, + timestamp: new Date(startTime).toISOString(), + prompt, + duration_ms: duration, + status, + exit_code: code, + output: newTurnOutput + }; + + // Create or update conversation record + let conversation: ConversationRecord; + + if (isMerge && mergeResult && !customId) { + // Merge without --id: append to ALL source conversations + // Save new turn to each source conversation + const savedConversations: ConversationRecord[] = []; + for (const srcConv of sourceConversations) { + const turnForSrc: ConversationTurn = { + ...newTurn, + turn: srcConv.turns.length + 1 // Use each conversation's turn count + }; + const updatedConv: ConversationRecord = { + ...srcConv, + updated_at: new Date().toISOString(), + total_duration_ms: srcConv.total_duration_ms + duration, + turn_count: srcConv.turns.length + 1, + latest_status: status, + turns: [...srcConv.turns, turnForSrc] + }; + savedConversations.push(updatedConv); + } + // Use first conversation as primary + conversation = savedConversations[0]; + // Save all source conversations + try { + for (const conv of savedConversations) { + saveConversation(historyDir, conv); + } + } catch (err) { + console.error('[CLI Executor] Failed to save merged histories:', (err as Error).message); + } + } else if (isMerge && mergeResult && customId) { + // Merge with --id: create new conversation with merged turns + new turn + // Convert merged turns to regular turns (without source_id) + const mergedTurns: ConversationTurn[] = mergeResult.mergedTurns.map((mt, idx) => ({ + turn: idx + 1, + timestamp: mt.timestamp, + prompt: mt.prompt, + duration_ms: mt.duration_ms, + status: mt.status, + exit_code: mt.exit_code, + output: mt.output + })); + + conversation = existingConversation + ? { + ...existingConversation, + updated_at: new Date().toISOString(), + total_duration_ms: existingConversation.total_duration_ms + duration, + turn_count: existingConversation.turns.length + 1, + latest_status: status, + turns: [...existingConversation.turns, newTurn] + } + : { + id: conversationId, + created_at: new Date(startTime).toISOString(), + updated_at: new Date().toISOString(), + tool, + model: model || 'default', + mode, + total_duration_ms: mergeResult.totalDuration + duration, + turn_count: mergedTurns.length + 1, + latest_status: status, + turns: [...mergedTurns, newTurn] + }; + // Save merged conversation + try { + saveConversation(historyDir, conversation); + } catch (err) { + console.error('[CLI Executor] Failed to save merged conversation:', (err as Error).message); + } + } else { + // Normal scenario: single conversation + conversation = existingConversation + ? { + ...existingConversation, + updated_at: new Date().toISOString(), + total_duration_ms: existingConversation.total_duration_ms + duration, + turn_count: existingConversation.turns.length + 1, + latest_status: status, + turns: [...existingConversation.turns, newTurn] + } + : { + id: conversationId, + created_at: new Date(startTime).toISOString(), + updated_at: new Date().toISOString(), + tool, + model: model || 'default', + mode, + total_duration_ms: duration, + turn_count: 1, + latest_status: status, + turns: [newTurn] + }; + // Try to save conversation to history + try { + saveConversation(historyDir, conversation); + } catch (err) { + // Non-fatal: continue even if history save fails + console.error('[CLI Executor] Failed to save history:', (err as Error).message); + } + } + + // Create legacy execution record for backward compatibility const execution: ExecutionRecord = { - id: executionId, + id: conversationId, timestamp: new Date(startTime).toISOString(), tool, model: model || 'default', @@ -367,25 +748,13 @@ async function executeCliTool( status, exit_code: code, duration_ms: duration, - output: { - stdout: stdout.substring(0, 10240), // Truncate to 10KB - stderr: stderr.substring(0, 2048), // Truncate to 2KB - truncated: stdout.length > 10240 || stderr.length > 2048 - } + output: newTurnOutput }; - // Try to save to history - try { - const historyDir = ensureHistoryDir(workingDir); - saveExecution(historyDir, execution); - } catch (err) { - // Non-fatal: continue even if history save fails - console.error('[CLI Executor] Failed to save history:', (err as Error).message); - } - resolve({ success: status === 'success', execution, + conversation, stdout, stderr }); @@ -576,27 +945,34 @@ export function getExecutionHistory(baseDir: string, options: { } /** - * Get execution detail by ID + * Get conversation detail by ID (returns ConversationRecord) + */ +export function getConversationDetail(baseDir: string, conversationId: string): ConversationRecord | null { + const historyDir = join(baseDir, '.workflow', '.cli-history'); + return loadConversation(historyDir, conversationId); +} + +/** + * Get execution detail by ID (legacy, returns ExecutionRecord for backward compatibility) */ export function getExecutionDetail(baseDir: string, executionId: string): ExecutionRecord | null { - const historyDir = join(baseDir, '.workflow', '.cli-history'); + const conversation = getConversationDetail(baseDir, executionId); + if (!conversation) return null; - // Parse date from execution ID - const timestamp = parseInt(executionId.split('-')[0], 10); - const date = new Date(timestamp); - const dateStr = date.toISOString().split('T')[0]; - - const filePath = join(historyDir, dateStr, `${executionId}.json`); - - if (existsSync(filePath)) { - try { - return JSON.parse(readFileSync(filePath, 'utf8')); - } catch { - return null; - } - } - - return null; + // Convert to legacy ExecutionRecord format (using latest turn) + const latestTurn = conversation.turns[conversation.turns.length - 1]; + return { + id: conversation.id, + timestamp: conversation.created_at, + tool: conversation.tool, + model: conversation.model, + mode: conversation.mode, + prompt: latestTurn.prompt, + status: conversation.latest_status, + exit_code: latestTurn.exit_code, + duration_ms: conversation.total_duration_ms, + output: latestTurn.output + }; } /** @@ -649,7 +1025,34 @@ export async function getCliToolsStatus(): Promise