mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat(cli): add support for custom execution IDs and multi-turn conversations
- Introduced `--id <id>` option in CLI for custom execution IDs. - Enhanced CLI command handling to support multi-turn conversations. - Updated execution and conversation detail retrieval to accommodate new structure. - Implemented merging of multiple conversations with tracking of source IDs. - Improved history management to save and load conversation records. - Added styles for displaying multi-turn conversation details in the dashboard. - Refactored existing execution detail functions for backward compatibility.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <fixed-id> --id <fixed-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
|
||||
```
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 "<prompt>" --tool <gemini|qwen|codex> --mode <analysis|write|auto>
|
||||
|
||||
# With working directory
|
||||
ccw cli exec "<prompt>" --tool gemini --cd <path>
|
||||
|
||||
# With additional directories
|
||||
ccw cli exec "<prompt>" --tool gemini --includeDirs ../shared,../types
|
||||
|
||||
# Full example
|
||||
ccw cli exec "<prompt>" --tool codex --mode auto --cd ./project --includeDirs ./lib
|
||||
ccw cli exec "<prompt>" --tool gemini --cd <path> --includeDirs <dirs>
|
||||
ccw cli exec "<prompt>" --resume [id] # Resume previous session
|
||||
```
|
||||
|
||||
### CLI Subcommands
|
||||
@@ -55,23 +48,12 @@ ccw cli exec "<prompt>" --tool codex --mode auto --cd ./project --includeDirs ./
|
||||
| `ccw cli history` | Show execution history |
|
||||
| `ccw cli detail <id>` | 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 "<prompt>" --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 "<prompt>" --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 "<prompt>" --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 "<prompt>" --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 "<prompt>" --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 "<prompt>" --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 <execution-id> --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 <id> # 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 "<Standard Prompt Template>" [options]
|
||||
```
|
||||
|
||||
#### Common Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--tool <tool>` | CLI tool: gemini, qwen, codex | gemini |
|
||||
| `--mode <mode>` | Mode: analysis, write, auto | analysis |
|
||||
| `--model <model>` | Model override | auto-select |
|
||||
| `--cd <path>` | Working directory | current dir |
|
||||
| `--includeDirs <dirs>` | Additional directories (comma-separated) | none |
|
||||
| `--timeout <ms>` | 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 "<prompt>" --tool gemini
|
||||
|
||||
# Fallback: Qwen (if Gemini fails or unavailable)
|
||||
ccw cli exec "<prompt>" --tool qwen
|
||||
|
||||
# Check tool availability
|
||||
ccw cli status
|
||||
```
|
||||
|
||||
### Directory Context Configuration
|
||||
|
||||
**CCW Directory Options**:
|
||||
- `--cd <path>`: Set working directory for execution
|
||||
- `--includeDirs <dir1,dir2>`: 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 "<prompt>" --tool gemini --cd src/auth --includeDirs ../shared
|
||||
|
||||
# Multiple additional directories
|
||||
ccw cli exec "<prompt>" --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 <tool>` | gemini, qwen, codex | gemini |
|
||||
| `--mode <mode>` | analysis, write, auto | analysis |
|
||||
| `--model <model>` | Model override | auto-select |
|
||||
| `--cd <path>` | Working directory | current |
|
||||
| `--includeDirs <dirs>` | Additional directories (comma-separated) | none |
|
||||
| `--timeout <ms>` | 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 "<prompt>" --tool gemini --timeout 600000 # 10 minutes
|
||||
ccw cli exec "<prompt>" --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 <path>` | `cd <path> &&` | `-C <path>` |
|
||||
| `--includeDirs <dirs>` | `--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 "<prompt>" --tool gemini # Primary
|
||||
ccw cli exec "<prompt>" --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 "<prompt>" --tool gemini --timeout 600000 # 10 min
|
||||
ccw cli exec "<prompt>" --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 "<prompt>" --tool gemini
|
||||
|
||||
# Write mode (enables file modifications)
|
||||
ccw cli exec "<prompt>" --tool gemini --mode write
|
||||
|
||||
# Auto mode (full autonomous operations, Codex only)
|
||||
ccw cli exec "<prompt>" --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 "<prompt>" --tool gemini`
|
||||
2. **Architecture Phase**: `ccw cli exec "<prompt>" --tool gemini`
|
||||
3. **Implementation Phase**: `ccw cli exec "<prompt>" --tool codex --mode auto`
|
||||
4. **Quality Phase**: `ccw cli exec "<prompt>" --tool codex --mode write`
|
||||
| Phase | Command |
|
||||
|-------|---------|
|
||||
| Understanding | `ccw cli exec "<prompt>" --tool gemini` |
|
||||
| Architecture | `ccw cli exec "<prompt>" --tool gemini` |
|
||||
| Implementation | `ccw cli exec "<prompt>" --tool codex --mode auto` |
|
||||
| Quality | `ccw cli exec "<prompt>" --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
|
||||
|
||||
@@ -161,6 +161,7 @@ export function run(argv: string[]): void {
|
||||
.option('--limit <n>', 'History limit')
|
||||
.option('--status <status>', 'Filter by status')
|
||||
.option('--resume [id]', 'Resume previous session (empty=last, or execution ID)')
|
||||
.option('--id <id>', 'Custom execution ID (e.g., IMPL-001-step1)')
|
||||
.action((subcommand, args, options) => cliCommand(subcommand, args, options));
|
||||
|
||||
program.parse(argv);
|
||||
|
||||
@@ -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<void> {
|
||||
? `${(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<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
if (!executionId) {
|
||||
console.error(chalk.red('Error: Execution ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw cli detail <execution-id>'));
|
||||
async function detailAction(conversationId: string | undefined): Promise<void> {
|
||||
if (!conversationId) {
|
||||
console.error(chalk.red('Error: Conversation ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw cli detail <conversation-id>'));
|
||||
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 <ms> 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 <id> Custom execution ID (e.g., IMPL-001-step1)'));
|
||||
console.log();
|
||||
console.log(' History Options:');
|
||||
console.log(chalk.gray(' --limit <n> Number of results (default: 20)'));
|
||||
|
||||
@@ -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<http.Ser
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle GET request
|
||||
const detail = getExecutionDetail(projectPath, executionId);
|
||||
if (!detail) {
|
||||
// Handle GET request - return full conversation with all turns
|
||||
const conversation = getConversationDetail(projectPath, executionId);
|
||||
if (!conversation) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Execution not found' }));
|
||||
res.end(JSON.stringify({ error: 'Conversation not found' }));
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(detail));
|
||||
res.end(JSON.stringify(conversation));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2126,3 +2126,87 @@
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
* Multi-Turn Conversation Styles
|
||||
* ======================================== */
|
||||
|
||||
/* Turn Badge in History List */
|
||||
.cli-turn-badge {
|
||||
font-size: 0.5625rem;
|
||||
font-weight: 600;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: hsl(var(--primary) / 0.12);
|
||||
color: hsl(var(--primary));
|
||||
border-radius: 9999px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Turns Container in Detail Modal */
|
||||
.cli-turns-container {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Turn Section */
|
||||
.cli-turn-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cli-turn-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cli-turn-number {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0.625rem;
|
||||
background: hsl(var(--primary) / 0.1);
|
||||
color: hsl(var(--primary));
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.cli-turn-status {
|
||||
font-size: 0.625rem;
|
||||
font-weight: 600;
|
||||
padding: 0.1875rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cli-turn-status.status-success {
|
||||
background: hsl(var(--success) / 0.12);
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.cli-turn-status.status-error {
|
||||
background: hsl(var(--destructive) / 0.12);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.cli-turn-status.status-timeout {
|
||||
background: hsl(var(--warning) / 0.12);
|
||||
color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.cli-turn-duration {
|
||||
font-size: 0.6875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Turn Divider */
|
||||
.cli-turn-divider {
|
||||
border: none;
|
||||
border-top: 1px dashed hsl(var(--border));
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
/* Error Section (smaller in multi-turn) */
|
||||
.cli-detail-error-section .cli-detail-error {
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
@@ -90,13 +90,17 @@ function renderCliHistory() {
|
||||
const statusClass = exec.status === 'success' ? 'text-success' :
|
||||
exec.status === 'timeout' ? 'text-warning' : 'text-destructive';
|
||||
const duration = formatDuration(exec.duration_ms);
|
||||
const timeAgo = getTimeAgo(new Date(exec.timestamp));
|
||||
const timeAgo = getTimeAgo(new Date(exec.updated_at || exec.timestamp));
|
||||
const turnBadge = exec.turn_count && exec.turn_count > 1
|
||||
? `<span class="cli-turn-badge">${exec.turn_count} turns</span>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="cli-history-item">
|
||||
<div class="cli-history-item-content" onclick="showExecutionDetail('${exec.id}')">
|
||||
<div class="cli-history-item-header">
|
||||
<span class="cli-tool-tag cli-tool-${exec.tool}">${exec.tool}</span>
|
||||
${turnBadge}
|
||||
<span class="cli-history-time">${timeAgo}</span>
|
||||
<i data-lucide="${statusIcon}" class="w-3.5 h-3.5 ${statusClass}"></i>
|
||||
</div>
|
||||
@@ -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) => `
|
||||
<div class="cli-turn-section">
|
||||
<div class="cli-turn-header">
|
||||
<span class="cli-turn-number">Turn ${turn.turn}</span>
|
||||
<span class="cli-turn-status status-${turn.status}">${turn.status}</span>
|
||||
<span class="cli-turn-duration">${formatDuration(turn.duration_ms)}</span>
|
||||
</div>
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="message-square"></i> Prompt</h4>
|
||||
<pre class="cli-detail-prompt">${escapeHtml(turn.prompt)}</pre>
|
||||
</div>
|
||||
${turn.output.stdout ? `
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="terminal"></i> Output</h4>
|
||||
<pre class="cli-detail-output">${escapeHtml(turn.output.stdout)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
${turn.output.stderr ? `
|
||||
<div class="cli-detail-section cli-detail-error-section">
|
||||
<h4><i data-lucide="alert-triangle"></i> Errors</h4>
|
||||
<pre class="cli-detail-error">${escapeHtml(turn.output.stderr)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
${turn.output.truncated ? `
|
||||
<p class="text-warning" style="font-size: 0.75rem; margin-top: 0.5rem;">
|
||||
<i data-lucide="info" class="w-3 h-3" style="display: inline;"></i>
|
||||
Output was truncated due to size.
|
||||
</p>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('<hr class="cli-turn-divider">');
|
||||
} else {
|
||||
// Legacy single execution format
|
||||
const detail = conversation;
|
||||
turnsHtml = `
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="message-square"></i> Prompt</h4>
|
||||
<pre class="cli-detail-prompt">${escapeHtml(detail.prompt)}</pre>
|
||||
</div>
|
||||
${detail.output.stdout ? `
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="terminal"></i> Output</h4>
|
||||
<pre class="cli-detail-output">${escapeHtml(detail.output.stdout)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
${detail.output.stderr ? `
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="alert-triangle"></i> Errors</h4>
|
||||
<pre class="cli-detail-error">${escapeHtml(detail.output.stderr)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
${detail.output.truncated ? `
|
||||
<p class="text-warning" style="font-size: 0.75rem; margin-top: 0.5rem;">
|
||||
<i data-lucide="info" class="w-3 h-3" style="display: inline;"></i>
|
||||
Output was truncated due to size.
|
||||
</p>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
const modalContent = `
|
||||
<div class="cli-detail-header">
|
||||
<div class="cli-detail-info">
|
||||
<span class="cli-tool-tag cli-tool-${detail.tool}">${detail.tool}</span>
|
||||
<span class="cli-detail-status status-${detail.status}">${detail.status}</span>
|
||||
<span class="text-muted-foreground">${formatDuration(detail.duration_ms)}</span>
|
||||
<span class="cli-tool-tag cli-tool-${conversation.tool}">${conversation.tool}</span>
|
||||
${turnCount > 1 ? `<span class="cli-turn-badge">${turnCount} turns</span>` : ''}
|
||||
<span class="cli-detail-status status-${latestStatus}">${latestStatus}</span>
|
||||
<span class="text-muted-foreground">${formatDuration(totalDuration)}</span>
|
||||
</div>
|
||||
<div class="cli-detail-meta">
|
||||
<span><i data-lucide="cpu" class="w-3 h-3"></i> ${detail.model || 'default'}</span>
|
||||
<span><i data-lucide="toggle-right" class="w-3 h-3"></i> ${detail.mode}</span>
|
||||
<span><i data-lucide="calendar" class="w-3 h-3"></i> ${new Date(detail.timestamp).toLocaleString()}</span>
|
||||
<span><i data-lucide="cpu" class="w-3 h-3"></i> ${conversation.model || 'default'}</span>
|
||||
<span><i data-lucide="toggle-right" class="w-3 h-3"></i> ${conversation.mode}</span>
|
||||
<span><i data-lucide="calendar" class="w-3 h-3"></i> ${new Date(createdAt).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="message-square"></i> Prompt</h4>
|
||||
<pre class="cli-detail-prompt">${escapeHtml(detail.prompt)}</pre>
|
||||
<div class="cli-turns-container">
|
||||
${turnsHtml}
|
||||
</div>
|
||||
${detail.output.stdout ? `
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="terminal"></i> Output</h4>
|
||||
<pre class="cli-detail-output">${escapeHtml(detail.output.stdout)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
${detail.output.stderr ? `
|
||||
<div class="cli-detail-section">
|
||||
<h4><i data-lucide="alert-triangle"></i> Errors</h4>
|
||||
<pre class="cli-detail-error">${escapeHtml(detail.output.stderr)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
${detail.output.truncated ? `
|
||||
<p class="text-warning" style="font-size: 0.75rem; margin-top: 0.5rem;">
|
||||
<i data-lucide="info" class="w-3 h-3" style="display: inline;"></i>
|
||||
Output was truncated due to size.
|
||||
</p>
|
||||
` : ''}
|
||||
<div class="cli-detail-actions">
|
||||
<button class="btn btn-sm btn-outline" onclick="copyExecutionPrompt('${executionId}')">
|
||||
<i data-lucide="copy" class="w-3.5 h-3.5"></i> Copy Prompt
|
||||
<button class="btn btn-sm btn-outline" onclick="copyConversationId('${executionId}')">
|
||||
<i data-lucide="copy" class="w-3.5 h-3.5"></i> Copy ID
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline btn-danger" onclick="confirmDeleteExecution('${executionId}'); closeModal();">
|
||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i> Delete
|
||||
@@ -214,7 +270,7 @@ async function showExecutionDetail(executionId, sourceDir) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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<typeof ParamsSchema>;
|
||||
@@ -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 <id1,id2,...> (multiple IDs): merge conversations
|
||||
// - With --id: create new merged conversation
|
||||
// - Without --id: append to ALL source conversations
|
||||
// - If --resume <id> AND --id <newId>: 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 <id> 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<Record<string, ToolAvailabili
|
||||
}
|
||||
|
||||
/**
|
||||
* Build continuation prompt with previous conversation context
|
||||
* Build multi-turn prompt with full conversation history
|
||||
*/
|
||||
function buildMultiTurnPrompt(conversation: ConversationRecord, newPrompt: string): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
parts.push('=== CONVERSATION HISTORY ===');
|
||||
parts.push('');
|
||||
|
||||
// Add all previous turns
|
||||
for (const turn of conversation.turns) {
|
||||
parts.push(`--- Turn ${turn.turn} ---`);
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build continuation prompt with previous conversation context (legacy)
|
||||
*/
|
||||
function buildContinuationPrompt(previous: ExecutionRecord, additionalPrompt?: string): string {
|
||||
const parts: string[] = [];
|
||||
@@ -707,6 +1110,9 @@ export function getLatestExecution(baseDir: string, tool?: string): ExecutionRec
|
||||
return getExecutionDetail(baseDir, history.executions[0].id);
|
||||
}
|
||||
|
||||
// Export types
|
||||
export type { ConversationRecord, ConversationTurn, ExecutionRecord };
|
||||
|
||||
// Export utility functions and tool definition for backward compatibility
|
||||
export { executeCliTool, checkToolAvailability };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user