feat: 增加队列状态“merged”及相关验证功能,添加状态参考文档

This commit is contained in:
catlog22
2026-01-25 10:11:52 +08:00
parent 16f27c080a
commit fe2536d4cd
4 changed files with 275 additions and 52 deletions

View File

@@ -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 - **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) ### 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 - **After CLI call**: Stop output immediately - let CLI execute in background. **DO NOT use TaskOutput polling** - wait for hook callback to receive results

View File

@@ -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"
}
]
}
}

View File

@@ -172,7 +172,7 @@ interface ExecutionGroup {
interface Queue { interface Queue {
id: string; // Queue unique ID: QUE-YYYYMMDD-HHMMSS (derived from filename) id: string; // Queue unique ID: QUE-YYYYMMDD-HHMMSS (derived from filename)
name?: string; // Optional queue name name?: string; // Optional queue name
status: 'active' | 'completed' | 'archived' | 'failed'; status: 'active' | 'completed' | 'archived' | 'failed' | 'merged';
issue_ids: string[]; // Issues in this queue issue_ids: string[]; // Issues in this queue
tasks: QueueItem[]; // Task items (task-level queue) tasks: QueueItem[]; // Task items (task-level queue)
solutions?: QueueItem[]; // Solution items (solution-level queue) solutions?: QueueItem[]; // Solution items (solution-level queue)
@@ -186,6 +186,8 @@ interface Queue {
completed_count: number; completed_count: number;
failed_count: number; failed_count: number;
updated_at: string; 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'; 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) ============ // ============ 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); index.queues = index.queues.filter(q => q.id !== source.id);
} else { } else {
// Mark source queue as merged // Mark source queue as merged
source.status = 'merged' as any; source.status = 'merged';
if (!source._metadata) { if (!source._metadata) {
source._metadata = { source._metadata = {
version: '2.1', version: '2.1',
@@ -823,8 +856,8 @@ function mergeQueues(target: Queue, source: Queue, options?: { deleteSource?: bo
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}; };
} }
(source._metadata as any).merged_into = target.id; source._metadata.merged_into = target.id;
(source._metadata as any).merged_at = new Date().toISOString(); source._metadata.merged_at = new Date().toISOString();
writeQueue(source); writeQueue(source);
const sourceEntry = index.queues.find(q => q.id === source.id); 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<Issue> = {}; const updates: Partial<Issue> = {};
if (options.status) { if (options.status) {
const validStatuses = ['registered', 'planning', 'planned', 'queued', 'executing', 'completed', 'failed', 'paused']; if (!validateIssueStatus(options.status)) {
if (!validStatuses.includes(options.status)) {
console.error(chalk.red(`Invalid status: ${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); process.exit(1);
} }
updates.status = options.status as Issue['status']; updates.status = options.status;
// Auto-set timestamps based on status // Auto-set timestamps based on status
if (options.status === 'planned') updates.planned_at = new Date().toISOString(); if (options.status === 'planned') updates.planned_at = new Date().toISOString();
@@ -2437,15 +2469,31 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
return; return;
} }
// Show current queue // Show current queue - use readQueue() to detect if queue actually exists
const queue = readActiveQueue(); 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) // Brief mode: minimal queue info (id, issue_ids, item summaries)
if (options.brief) { if (options.brief) {
const items = queue.solutions || queue.tasks || []; const items = queue!.solutions || queue!.tasks || [];
const briefQueue = { const briefQueue = {
id: queue.id, id: queue!.id,
issue_ids: queue.issue_ids || [], issue_ids: queue!.issue_ids || [],
total: items.length, total: items.length,
pending: items.filter(i => i.status === 'pending').length, pending: items.filter(i => i.status === 'pending').length,
executing: items.filter(i => i.status === 'executing').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')); 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 <issue-id>'));
console.log(chalk.gray('Or list history: ccw issue queue list'));
return;
}
// Support both solution-level and task-level queues // Support both solution-level and task-level queues
const items = queue.solutions || queue.tasks || []; const items = queue.solutions || queue.tasks || [];
const isSolutionLevel = !!(queue.solutions && queue.solutions.length > 0); const isSolutionLevel = !!(queue.solutions && queue.solutions.length > 0);
if (!queue.id || items.length === 0) { if (items.length === 0) {
console.log(chalk.yellow('No active queue')); console.log(chalk.yellow(`Queue ${queue.id} is empty`));
console.log(chalk.gray('Create one: ccw issue queue add <issue-id>')); console.log(chalk.gray('Add issues: ccw issue queue add <issue-id>'));
console.log(chalk.gray('Or list history: ccw issue queue list'));
return; return;
} }
@@ -2908,10 +2963,10 @@ async function retryAction(issueId: string | undefined, options: IssueOptions):
created_at: item.failure_details.timestamp created_at: item.failure_details.timestamp
}); });
// Keep issue status as 'failed' (or optionally 'pending_replan') // Reset issue status to 'queued' for re-execution
// This signals to planning phase that this issue had failures // Failure details preserved in feedback for debugging
updateIssue(item.issue_id, { updateIssue(item.issue_id, {
status: 'failed', status: 'queued',
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}); });
@@ -2927,7 +2982,7 @@ async function retryAction(issueId: string | undefined, options: IssueOptions):
item.failure_history.push(item.failure_details); 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.status = 'pending';
item.failure_reason = undefined; item.failure_reason = undefined;
item.failure_details = undefined; item.failure_details = undefined;

194
status-reference.md Normal file
View File

@@ -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 <id> --status planning` |
| `planned` | 已规划solution 已绑定 | `ccw issue bind <issue-id> <solution-id>` |
| `queued` | 已加入执行队列 | `ccw issue queue add <issue-id>`, `ccw issue retry` |
| `executing` | 执行中 | `ccw issue next` |
| `completed` | 已完成 | `ccw issue done <item-id>` |
| `failed` | 执行失败 | `ccw issue done <item-id> --fail` |
| `paused` | 暂停 | 手动设置 `ccw issue update <id> --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 <source> --queue <target>` |
### 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 <item-id>` |
| `failed` | 失败 | `ccw issue done <item-id> --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 <issue-id> --status <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 <issue-id>
# 获取下一个执行项 (QueueItem → executing, Issue → executing)
ccw issue next
# 标记完成 (QueueItem → completed, Issue → completed)
ccw issue done <item-id>
# 标记失败 (QueueItem → failed, Issue → failed)
ccw issue done <item-id> --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)