mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: 增加队列状态“merged”及相关验证功能,添加状态参考文档
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user