mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-04 01:40:45 +08:00
feat: 增加队列状态“merged”及相关验证功能,添加状态参考文档
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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<Issue> = {};
|
||||
|
||||
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 <issue-id>'));
|
||||
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 <issue-id>'));
|
||||
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 <issue-id>'));
|
||||
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;
|
||||
|
||||
194
status-reference.md
Normal file
194
status-reference.md
Normal 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)
|
||||
Reference in New Issue
Block a user