diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index d7b1872a..2278290f 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -25,9 +25,12 @@ Available CLI endpoints are dynamically defined by the config file: - **TaskOutput usage**: Only use `TaskOutput({ task_id: "xxx", block: false })` + sleep loop to poll completion status. NEVER read intermediate output during agent/CLI execution - wait for final result only ### CLI Tool Calls (ccw cli) -- **Default: `run_in_background: true`** - Unless otherwise specified, always use background execution for CLI calls: +- **Default: Use Bash `run_in_background: true`** - Unless otherwise specified, always execute CLI calls in background using Bash tool's background mode: ``` - Bash({ command: "ccw cli -p '...' --tool gemini", run_in_background: true }) + Bash({ + command: "ccw cli -p '...' --tool gemini", + run_in_background: true // Bash tool parameter, not ccw cli parameter + }) ``` - **After CLI call**: Stop output immediately - let CLI execute in background. **DO NOT use TaskOutput polling** - wait for hook callback to receive results diff --git a/.test-loop-comprehensive/.task/E2E-TASK-1769007254162.json b/.test-loop-comprehensive/.task/E2E-TASK-1769007254162.json deleted file mode 100644 index 65c7ab99..00000000 --- a/.test-loop-comprehensive/.task/E2E-TASK-1769007254162.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "E2E-TASK-1769007254162", - "title": "Test Task E2E-TASK-1769007254162", - "description": "Test task with loop control", - "status": "pending", - "loop_control": { - "enabled": true, - "description": "Test loop", - "max_iterations": 3, - "success_condition": "current_iteration >= 3", - "error_policy": { - "on_failure": "pause", - "max_retries": 3 - }, - "cli_sequence": [ - { - "step_id": "step1", - "tool": "bash", - "command": "echo \"iteration\"" - }, - { - "step_id": "step2", - "tool": "gemini", - "mode": "analysis", - "prompt_template": "Process output" - } - ] - } -} \ No newline at end of file diff --git a/ccw/src/commands/issue.ts b/ccw/src/commands/issue.ts index c0c5c928..01d3a938 100644 --- a/ccw/src/commands/issue.ts +++ b/ccw/src/commands/issue.ts @@ -172,7 +172,7 @@ interface ExecutionGroup { interface Queue { id: string; // Queue unique ID: QUE-YYYYMMDD-HHMMSS (derived from filename) name?: string; // Optional queue name - status: 'active' | 'completed' | 'archived' | 'failed'; + status: 'active' | 'completed' | 'archived' | 'failed' | 'merged'; issue_ids: string[]; // Issues in this queue tasks: QueueItem[]; // Task items (task-level queue) solutions?: QueueItem[]; // Solution items (solution-level queue) @@ -186,6 +186,8 @@ interface Queue { completed_count: number; failed_count: number; updated_at: string; + merged_into?: string; // Queue ID this was merged into + merged_at?: string; // Timestamp of merge }; } @@ -231,6 +233,37 @@ interface IssueOptions { const ISSUES_DIR = '.workflow/issues'; +// ============ Status Constants ============ + +const VALID_QUEUE_STATUSES = ['active', 'completed', 'archived', 'failed', 'merged'] as const; +const VALID_ITEM_STATUSES = ['pending', 'ready', 'executing', 'completed', 'failed', 'blocked'] as const; +const VALID_ISSUE_STATUSES = ['registered', 'planning', 'planned', 'queued', 'executing', 'completed', 'failed', 'paused'] as const; + +type QueueStatus = typeof VALID_QUEUE_STATUSES[number]; +type QueueItemStatus = typeof VALID_ITEM_STATUSES[number]; +type IssueStatus = typeof VALID_ISSUE_STATUSES[number]; + +/** + * Validate queue status + */ +function validateQueueStatus(status: string): status is QueueStatus { + return VALID_QUEUE_STATUSES.includes(status as QueueStatus); +} + +/** + * Validate queue item status + */ +function validateItemStatus(status: string): status is QueueItemStatus { + return VALID_ITEM_STATUSES.includes(status as QueueItemStatus); +} + +/** + * Validate issue status + */ +function validateIssueStatus(status: string): status is IssueStatus { + return VALID_ISSUE_STATUSES.includes(status as IssueStatus); +} + // ============ Storage Layer (JSONL) ============ /** @@ -811,7 +844,7 @@ function mergeQueues(target: Queue, source: Queue, options?: { deleteSource?: bo index.queues = index.queues.filter(q => q.id !== source.id); } else { // Mark source queue as merged - source.status = 'merged' as any; + source.status = 'merged'; if (!source._metadata) { source._metadata = { version: '2.1', @@ -823,8 +856,8 @@ function mergeQueues(target: Queue, source: Queue, options?: { deleteSource?: bo updated_at: new Date().toISOString() }; } - (source._metadata as any).merged_into = target.id; - (source._metadata as any).merged_at = new Date().toISOString(); + source._metadata.merged_into = target.id; + source._metadata.merged_at = new Date().toISOString(); writeQueue(source); const sourceEntry = index.queues.find(q => q.id === source.id); @@ -1806,13 +1839,12 @@ async function updateAction(issueId: string | undefined, options: IssueOptions): const updates: Partial = {}; if (options.status) { - const validStatuses = ['registered', 'planning', 'planned', 'queued', 'executing', 'completed', 'failed', 'paused']; - if (!validStatuses.includes(options.status)) { + if (!validateIssueStatus(options.status)) { console.error(chalk.red(`Invalid status: ${options.status}`)); - console.error(chalk.gray(`Valid: ${validStatuses.join(', ')}`)); + console.error(chalk.gray(`Valid: ${VALID_ISSUE_STATUSES.join(', ')}`)); process.exit(1); } - updates.status = options.status as Issue['status']; + updates.status = options.status; // Auto-set timestamps based on status if (options.status === 'planned') updates.planned_at = new Date().toISOString(); @@ -2437,15 +2469,31 @@ async function queueAction(subAction: string | undefined, issueId: string | unde return; } - // Show current queue - const queue = readActiveQueue(); + // Show current queue - use readQueue() to detect if queue actually exists + const queue = readQueue(); + + // Handle no active queue case for all output modes + if (!queue) { + if (options.brief || options.json) { + console.log(JSON.stringify({ + status: 'empty', + message: 'No active queue', + id: null, + issue_ids: [], + total: 0, + items: [] + }, null, 2)); + return; + } + // Human-readable output handled below + } // Brief mode: minimal queue info (id, issue_ids, item summaries) if (options.brief) { - const items = queue.solutions || queue.tasks || []; + const items = queue!.solutions || queue!.tasks || []; const briefQueue = { - id: queue.id, - issue_ids: queue.issue_ids || [], + id: queue!.id, + issue_ids: queue!.issue_ids || [], total: items.length, pending: items.filter(i => i.status === 'pending').length, executing: items.filter(i => i.status === 'executing').length, @@ -2469,14 +2517,21 @@ async function queueAction(subAction: string | undefined, issueId: string | unde console.log(chalk.bold.cyan('\nActive Queue\n')); + // Handle no queue case (human-readable) + if (!queue) { + console.log(chalk.yellow('No active queue')); + console.log(chalk.gray('Create one: ccw issue queue add ')); + console.log(chalk.gray('Or list history: ccw issue queue list')); + return; + } + // Support both solution-level and task-level queues const items = queue.solutions || queue.tasks || []; const isSolutionLevel = !!(queue.solutions && queue.solutions.length > 0); - if (!queue.id || items.length === 0) { - console.log(chalk.yellow('No active queue')); - console.log(chalk.gray('Create one: ccw issue queue add ')); - console.log(chalk.gray('Or list history: ccw issue queue list')); + if (items.length === 0) { + console.log(chalk.yellow(`Queue ${queue.id} is empty`)); + console.log(chalk.gray('Add issues: ccw issue queue add ')); return; } @@ -2908,10 +2963,10 @@ async function retryAction(issueId: string | undefined, options: IssueOptions): created_at: item.failure_details.timestamp }); - // Keep issue status as 'failed' (or optionally 'pending_replan') - // This signals to planning phase that this issue had failures + // Reset issue status to 'queued' for re-execution + // Failure details preserved in feedback for debugging updateIssue(item.issue_id, { - status: 'failed', + status: 'queued', updated_at: new Date().toISOString() }); @@ -2927,7 +2982,7 @@ async function retryAction(issueId: string | undefined, options: IssueOptions): item.failure_history.push(item.failure_details); } - // Reset QueueItem for retry (but Issue status remains 'failed') + // Reset QueueItem for retry (Issue status also reset to 'queued') item.status = 'pending'; item.failure_reason = undefined; item.failure_details = undefined; diff --git a/status-reference.md b/status-reference.md new file mode 100644 index 00000000..63eaa0af --- /dev/null +++ b/status-reference.md @@ -0,0 +1,194 @@ +# Issue System Status Reference + +> CCW Issue Management 状态管理参考手册 + +## 概述 + +CCW Issue 系统包含三个核心实体,每个实体有独立的状态定义: + +| 实体 | 描述 | 存储位置 | +|------|------|----------| +| **Issue** | 问题/任务 | `.workflow/issues/issues.jsonl` | +| **Queue** | 执行队列 | `.workflow/issues/queues/*.json` | +| **QueueItem** | 队列项(解决方案级别) | Queue 内的 `solutions[]` 数组 | + +--- + +## Issue 状态 + +**类型定义**: `'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused'` + +| 状态 | 含义 | 触发命令/场景 | +|------|------|---------------| +| `registered` | 已注册,待规划 | `ccw issue create`, `ccw issue init`, `ccw issue pull` | +| `planning` | 规划中 | 手动设置 `ccw issue update --status planning` | +| `planned` | 已规划,solution 已绑定 | `ccw issue bind ` | +| `queued` | 已加入执行队列 | `ccw issue queue add `, `ccw issue retry` | +| `executing` | 执行中 | `ccw issue next` | +| `completed` | 已完成 | `ccw issue done ` | +| `failed` | 执行失败 | `ccw issue done --fail` | +| `paused` | 暂停 | 手动设置 `ccw issue update --status paused` | + +### Issue 状态流转图 + +``` + ┌─────────────────────────────────────┐ + │ ▼ +registered ──→ planning ──→ planned ──→ queued ──→ executing ──→ completed + ▲ │ + │ ▼ + └─────── failed + │ + ▼ + paused (手动) +``` + +--- + +## Queue 状态 + +**类型定义**: `'active' | 'completed' | 'archived' | 'failed' | 'merged'` + +| 状态 | 含义 | 触发命令/场景 | +|------|------|---------------| +| `active` | 活跃队列,可执行 | 创建队列时默认, `ccw issue retry` 重置 | +| `completed` | 已完成(所有项完成) | 所有 QueueItem 状态为 `completed` | +| `archived` | 已归档 | `ccw issue queue archive` | +| `failed` | 有失败项 | 有 QueueItem 失败且所有项已终止 | +| `merged` | 已合并到其他队列 | `ccw issue queue merge --queue ` | + +### Queue 状态流转图 + +``` + ┌────────────────────────────────────────┐ + │ ▼ +active ───────┼──────────────────────────────────→ completed ──→ archived + │ + │ ┌───────────┐ + └────────→│ failed │ + └─────┬─────┘ + │ retry + ▼ + active + +active ──────────────────────────────────────────→ merged +``` + +--- + +## QueueItem 状态 + +**类型定义**: `'pending' | 'ready' | 'executing' | 'completed' | 'failed' | 'blocked'` + +| 状态 | 含义 | 触发命令/场景 | +|------|------|---------------| +| `pending` | 等待执行 | 创建队列项时默认, `ccw issue retry` 重置 | +| `ready` | 依赖满足,可执行 | 计算得出(非持久化),用于 DAG | +| `executing` | 执行中 | `ccw issue next` | +| `completed` | 已完成 | `ccw issue done ` | +| `failed` | 失败 | `ccw issue done --fail` | +| `blocked` | 被依赖阻塞 | 计算得出(非持久化),用于 DAG | + +### QueueItem 状态流转图 + +``` +pending ──→ executing ──→ completed + ▲ │ + │ ▼ + └───────── failed (retry 重置) +``` + +> **注意**: `ready` 和 `blocked` 是在 `ccw issue queue dag` 命令中动态计算的,不会持久化存储。 + +--- + +## 状态验证 + +系统内置状态验证函数,防止无效状态赋值: + +```typescript +// 常量定义 +const VALID_QUEUE_STATUSES = ['active', 'completed', 'archived', 'failed', 'merged'] as const; +const VALID_ITEM_STATUSES = ['pending', 'ready', 'executing', 'completed', 'failed', 'blocked'] as const; +const VALID_ISSUE_STATUSES = ['registered', 'planning', 'planned', 'queued', 'executing', 'completed', 'failed', 'paused'] as const; + +// 验证函数 +validateQueueStatus(status: string): status is QueueStatus +validateItemStatus(status: string): status is QueueItemStatus +validateIssueStatus(status: string): status is IssueStatus +``` + +--- + +## 常用命令速查 + +### 状态查询 + +```bash +# 查看所有 Issue +ccw issue list + +# 按状态筛选 Issue +ccw issue list --status planned,queued + +# 查看队列状态 +ccw issue queue + +# 查看队列 DAG(含 ready/blocked 计算状态) +ccw issue queue dag +``` + +### 状态更新 + +```bash +# 更新 Issue 状态 +ccw issue update --status + +# 从队列同步 Issue 状态为 queued +ccw issue update --from-queue + +# 重试失败项(QueueItem → pending, Issue → queued, Queue → active) +ccw issue retry [issue-id] +``` + +### 执行流程 + +```bash +# 添加到队列 (Issue → queued, 创建 QueueItem → pending) +ccw issue queue add + +# 获取下一个执行项 (QueueItem → executing, Issue → executing) +ccw issue next + +# 标记完成 (QueueItem → completed, Issue → completed) +ccw issue done + +# 标记失败 (QueueItem → failed, Issue → failed) +ccw issue done --fail --reason "error message" +``` + +--- + +## 状态对照表 + +| 操作 | Issue 状态 | Queue 状态 | QueueItem 状态 | +|------|-----------|------------|----------------| +| 创建 Issue | `registered` | - | - | +| 绑定 Solution | `planned` | - | - | +| 加入队列 | `queued` | `active` | `pending` | +| 开始执行 | `executing` | `active` | `executing` | +| 执行完成 | `completed` | `completed`* | `completed` | +| 执行失败 | `failed` | `failed`* | `failed` | +| 重试 | `queued` | `active` | `pending` | +| 归档队列 | - | `archived` | - | +| 合并队列 | - | `merged` | - | + +> *Queue 状态在所有项完成/失败后才会更新 + +--- + +## 相关文档 + +- [Queue 命令详解](./queue.md) +- [Execute 工作流](./execute.md) +- [Plan 规划流程](./plan.md)