claude-code-workflow 6.3.9 → 6.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +1 -1
- package/.claude/agents/issue-plan-agent.md +3 -10
- package/.claude/agents/issue-queue-agent.md +103 -83
- package/.claude/commands/issue/execute.md +195 -363
- package/.claude/commands/issue/plan.md +9 -2
- package/.claude/commands/issue/queue.md +130 -66
- package/.claude/commands/workflow/init.md +75 -29
- package/.claude/commands/workflow/lite-fix.md +8 -0
- package/.claude/commands/workflow/lite-plan.md +8 -0
- package/.claude/commands/workflow/review-module-cycle.md +4 -0
- package/.claude/commands/workflow/review-session-cycle.md +4 -0
- package/.claude/commands/workflow/review.md +4 -4
- package/.claude/commands/workflow/session/solidify.md +299 -0
- package/.claude/commands/workflow/session/start.md +10 -7
- package/.claude/commands/workflow/tools/context-gather.md +17 -10
- package/.claude/workflows/cli-templates/schemas/queue-schema.json +225 -108
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +6 -28
- package/.claude/workflows/context-tools.md +17 -25
- package/.codex/AGENTS.md +10 -5
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +348 -115
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/src/commands/issue.ts +392 -149
- package/ccw/src/templates/dashboard-js/components/cli-status.js +1 -78
- package/ccw/src/templates/dashboard-js/i18n.js +0 -4
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +0 -18
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +57 -26
- package/package.json +1 -1
- package/.claude/workflows/context-tools-ace.md +0 -105
|
@@ -26,20 +26,6 @@ interface Issue {
|
|
|
26
26
|
context: string;
|
|
27
27
|
bound_solution_id: string | null;
|
|
28
28
|
solution_count: number;
|
|
29
|
-
source?: string;
|
|
30
|
-
source_url?: string;
|
|
31
|
-
labels?: string[];
|
|
32
|
-
// Agent workflow fields
|
|
33
|
-
affected_components?: string[];
|
|
34
|
-
lifecycle_requirements?: {
|
|
35
|
-
test_strategy?: 'unit' | 'integration' | 'e2e' | 'auto';
|
|
36
|
-
regression_scope?: 'full' | 'related' | 'affected';
|
|
37
|
-
commit_strategy?: 'per-task' | 'atomic' | 'squash';
|
|
38
|
-
};
|
|
39
|
-
problem_statement?: string;
|
|
40
|
-
expected_behavior?: string;
|
|
41
|
-
actual_behavior?: string;
|
|
42
|
-
reproduction_steps?: string[];
|
|
43
29
|
// Timestamps
|
|
44
30
|
created_at: string;
|
|
45
31
|
updated_at: string;
|
|
@@ -85,16 +71,6 @@ interface SolutionTask {
|
|
|
85
71
|
|
|
86
72
|
depends_on: string[];
|
|
87
73
|
estimated_minutes?: number;
|
|
88
|
-
executor: 'codex' | 'gemini' | 'agent' | 'auto';
|
|
89
|
-
|
|
90
|
-
// Lifecycle status tracking
|
|
91
|
-
lifecycle_status?: {
|
|
92
|
-
implemented: boolean;
|
|
93
|
-
tested: boolean;
|
|
94
|
-
regression_passed: boolean;
|
|
95
|
-
accepted: boolean;
|
|
96
|
-
committed: boolean;
|
|
97
|
-
};
|
|
98
74
|
status?: string;
|
|
99
75
|
priority?: number;
|
|
100
76
|
}
|
|
@@ -102,6 +78,7 @@ interface SolutionTask {
|
|
|
102
78
|
interface Solution {
|
|
103
79
|
id: string;
|
|
104
80
|
description?: string;
|
|
81
|
+
approach?: string; // Solution approach description
|
|
105
82
|
tasks: SolutionTask[];
|
|
106
83
|
exploration_context?: Record<string, any>;
|
|
107
84
|
analysis?: { risk?: string; impact?: string; complexity?: string };
|
|
@@ -112,17 +89,19 @@ interface Solution {
|
|
|
112
89
|
}
|
|
113
90
|
|
|
114
91
|
interface QueueItem {
|
|
115
|
-
item_id: string; //
|
|
92
|
+
item_id: string; // Item ID in queue: T-1, T-2, ... (task-level) or S-1, S-2, ... (solution-level)
|
|
116
93
|
issue_id: string;
|
|
117
94
|
solution_id: string;
|
|
118
|
-
task_id
|
|
119
|
-
title?: string;
|
|
95
|
+
task_id?: string; // Only for task-level queues
|
|
120
96
|
status: 'pending' | 'ready' | 'executing' | 'completed' | 'failed' | 'blocked';
|
|
121
97
|
execution_order: number;
|
|
122
98
|
execution_group: string;
|
|
123
99
|
depends_on: string[];
|
|
124
100
|
semantic_priority: number;
|
|
125
101
|
assigned_executor: 'codex' | 'gemini' | 'agent';
|
|
102
|
+
task_count?: number; // For solution-level queues
|
|
103
|
+
files_touched?: string[]; // For solution-level queues
|
|
104
|
+
queued_at?: string;
|
|
126
105
|
started_at?: string;
|
|
127
106
|
completed_at?: string;
|
|
128
107
|
result?: Record<string, any>;
|
|
@@ -131,7 +110,8 @@ interface QueueItem {
|
|
|
131
110
|
|
|
132
111
|
interface QueueConflict {
|
|
133
112
|
type: 'file_conflict' | 'dependency_conflict' | 'resource_conflict';
|
|
134
|
-
tasks
|
|
113
|
+
tasks?: string[]; // Task IDs involved (task-level queues)
|
|
114
|
+
solutions?: string[]; // Solution IDs involved (solution-level queues)
|
|
135
115
|
file?: string; // Conflicting file path
|
|
136
116
|
resolution: 'sequential' | 'merge' | 'manual';
|
|
137
117
|
resolution_order?: string[];
|
|
@@ -142,8 +122,10 @@ interface QueueConflict {
|
|
|
142
122
|
interface ExecutionGroup {
|
|
143
123
|
id: string; // Group ID: P1, S1, etc.
|
|
144
124
|
type: 'parallel' | 'sequential';
|
|
145
|
-
task_count
|
|
146
|
-
|
|
125
|
+
task_count?: number; // For task-level queues
|
|
126
|
+
solution_count?: number; // For solution-level queues
|
|
127
|
+
tasks?: string[]; // Task IDs in this group (task-level)
|
|
128
|
+
solutions?: string[]; // Solution IDs in this group (solution-level)
|
|
147
129
|
}
|
|
148
130
|
|
|
149
131
|
interface Queue {
|
|
@@ -151,7 +133,8 @@ interface Queue {
|
|
|
151
133
|
name?: string; // Optional queue name
|
|
152
134
|
status: 'active' | 'completed' | 'archived' | 'failed';
|
|
153
135
|
issue_ids: string[]; // Issues in this queue
|
|
154
|
-
tasks: QueueItem[]; // Task items (
|
|
136
|
+
tasks: QueueItem[]; // Task items (task-level queue)
|
|
137
|
+
solutions?: QueueItem[]; // Solution items (solution-level queue)
|
|
155
138
|
conflicts: QueueConflict[];
|
|
156
139
|
execution_groups?: ExecutionGroup[];
|
|
157
140
|
_metadata: {
|
|
@@ -167,13 +150,14 @@ interface Queue {
|
|
|
167
150
|
|
|
168
151
|
interface QueueIndex {
|
|
169
152
|
active_queue_id: string | null;
|
|
170
|
-
active_item_id: string | null;
|
|
171
153
|
queues: {
|
|
172
154
|
id: string;
|
|
173
155
|
status: string;
|
|
174
156
|
issue_ids: string[];
|
|
175
|
-
total_tasks
|
|
176
|
-
|
|
157
|
+
total_tasks?: number; // For task-level queues
|
|
158
|
+
total_solutions?: number; // For solution-level queues
|
|
159
|
+
completed_tasks?: number; // For task-level queues
|
|
160
|
+
completed_solutions?: number; // For solution-level queues
|
|
177
161
|
created_at: string;
|
|
178
162
|
completed_at?: string;
|
|
179
163
|
}[];
|
|
@@ -308,7 +292,7 @@ function ensureQueuesDir(): void {
|
|
|
308
292
|
function readQueueIndex(): QueueIndex {
|
|
309
293
|
const path = join(getQueuesDir(), 'index.json');
|
|
310
294
|
if (!existsSync(path)) {
|
|
311
|
-
return { active_queue_id: null,
|
|
295
|
+
return { active_queue_id: null, queues: [] };
|
|
312
296
|
}
|
|
313
297
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
314
298
|
}
|
|
@@ -405,12 +389,16 @@ function writeQueue(queue: Queue): void {
|
|
|
405
389
|
writeQueueIndex(index);
|
|
406
390
|
}
|
|
407
391
|
|
|
408
|
-
function generateQueueItemId(queue: Queue): string {
|
|
409
|
-
const
|
|
410
|
-
|
|
392
|
+
function generateQueueItemId(queue: Queue, level: 'solution' | 'task' = 'solution'): string {
|
|
393
|
+
const prefix = level === 'solution' ? 'S' : 'T';
|
|
394
|
+
const items = level === 'solution' ? (queue.solutions || []) : (queue.tasks || []);
|
|
395
|
+
const pattern = new RegExp(`^${prefix}-(\\d+)$`);
|
|
396
|
+
|
|
397
|
+
const maxNum = items.reduce((max, q) => {
|
|
398
|
+
const match = q.item_id.match(pattern);
|
|
411
399
|
return match ? Math.max(max, parseInt(match[1])) : max;
|
|
412
400
|
}, 0);
|
|
413
|
-
return
|
|
401
|
+
return `${prefix}-${maxNum + 1}`;
|
|
414
402
|
}
|
|
415
403
|
|
|
416
404
|
// ============ Commands ============
|
|
@@ -657,7 +645,6 @@ async function taskAction(issueId: string | undefined, taskId: string | undefine
|
|
|
657
645
|
|
|
658
646
|
if (options.title) solution.tasks[taskIdx].title = options.title;
|
|
659
647
|
if (options.status) solution.tasks[taskIdx].status = options.status;
|
|
660
|
-
if (options.executor) solution.tasks[taskIdx].executor = options.executor as any;
|
|
661
648
|
|
|
662
649
|
writeSolutions(issueId, solutions);
|
|
663
650
|
console.log(chalk.green(`✓ Task ${taskId} updated`));
|
|
@@ -690,8 +677,7 @@ async function taskAction(issueId: string | undefined, taskId: string | undefine
|
|
|
690
677
|
scope: 'core',
|
|
691
678
|
message_template: `feat(core): ${options.title}`
|
|
692
679
|
},
|
|
693
|
-
depends_on: []
|
|
694
|
-
executor: (options.executor as any) || 'auto'
|
|
680
|
+
depends_on: []
|
|
695
681
|
};
|
|
696
682
|
|
|
697
683
|
solution.tasks.push(newTask);
|
|
@@ -845,6 +831,138 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
845
831
|
return;
|
|
846
832
|
}
|
|
847
833
|
|
|
834
|
+
// DAG - Return dependency graph for parallel execution planning (solution-level)
|
|
835
|
+
if (subAction === 'dag') {
|
|
836
|
+
const queue = readActiveQueue();
|
|
837
|
+
|
|
838
|
+
// Support both old (tasks) and new (solutions) queue format
|
|
839
|
+
const items = queue.solutions || queue.tasks || [];
|
|
840
|
+
if (!queue.id || items.length === 0) {
|
|
841
|
+
console.log(JSON.stringify({ error: 'No active queue', nodes: [], edges: [], groups: [] }));
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Build DAG nodes (solution-level)
|
|
846
|
+
const completedIds = new Set(items.filter(t => t.status === 'completed').map(t => t.item_id));
|
|
847
|
+
const failedIds = new Set(items.filter(t => t.status === 'failed').map(t => t.item_id));
|
|
848
|
+
|
|
849
|
+
const nodes = items.map(item => ({
|
|
850
|
+
id: item.item_id,
|
|
851
|
+
issue_id: item.issue_id,
|
|
852
|
+
solution_id: item.solution_id,
|
|
853
|
+
status: item.status,
|
|
854
|
+
executor: item.assigned_executor,
|
|
855
|
+
priority: item.semantic_priority,
|
|
856
|
+
depends_on: item.depends_on || [],
|
|
857
|
+
task_count: item.task_count || 1,
|
|
858
|
+
files_touched: item.files_touched || [],
|
|
859
|
+
// Calculate if ready (dependencies satisfied)
|
|
860
|
+
ready: item.status === 'pending' && (item.depends_on || []).every(d => completedIds.has(d)),
|
|
861
|
+
blocked_by: (item.depends_on || []).filter(d => !completedIds.has(d) && !failedIds.has(d))
|
|
862
|
+
}));
|
|
863
|
+
|
|
864
|
+
// Build edges for visualization
|
|
865
|
+
const edges = items.flatMap(item =>
|
|
866
|
+
(item.depends_on || []).map(dep => ({ from: dep, to: item.item_id }))
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
// Group ready items by execution_group
|
|
870
|
+
const readyItems = nodes.filter(n => n.ready || n.status === 'executing');
|
|
871
|
+
const groups: Record<string, string[]> = {};
|
|
872
|
+
|
|
873
|
+
for (const item of items) {
|
|
874
|
+
if (readyItems.some(r => r.id === item.item_id)) {
|
|
875
|
+
const group = item.execution_group || 'P1';
|
|
876
|
+
if (!groups[group]) groups[group] = [];
|
|
877
|
+
groups[group].push(item.item_id);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Calculate parallel batches - prefer execution_groups from queue if available
|
|
882
|
+
const parallelBatches: string[][] = [];
|
|
883
|
+
const readyItemIds = new Set(readyItems.map(t => t.id));
|
|
884
|
+
|
|
885
|
+
// Check if queue has pre-assigned execution_groups
|
|
886
|
+
if (queue.execution_groups && queue.execution_groups.length > 0) {
|
|
887
|
+
// Use agent-assigned execution groups
|
|
888
|
+
for (const group of queue.execution_groups) {
|
|
889
|
+
const groupItems = (group.solutions || group.tasks || [])
|
|
890
|
+
.filter((id: string) => readyItemIds.has(id));
|
|
891
|
+
if (groupItems.length > 0) {
|
|
892
|
+
if (group.type === 'parallel') {
|
|
893
|
+
// All items in parallel group can run together
|
|
894
|
+
parallelBatches.push(groupItems);
|
|
895
|
+
} else {
|
|
896
|
+
// Sequential group: each item is its own batch
|
|
897
|
+
for (const itemId of groupItems) {
|
|
898
|
+
parallelBatches.push([itemId]);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
} else {
|
|
904
|
+
// Fallback: calculate parallel batches from file conflicts
|
|
905
|
+
const remainingReady = new Set(readyItemIds);
|
|
906
|
+
|
|
907
|
+
while (remainingReady.size > 0) {
|
|
908
|
+
const batch: string[] = [];
|
|
909
|
+
const batchFiles = new Set<string>();
|
|
910
|
+
|
|
911
|
+
for (const itemId of Array.from(remainingReady)) {
|
|
912
|
+
const item = items.find(t => t.item_id === itemId);
|
|
913
|
+
if (!item) continue;
|
|
914
|
+
|
|
915
|
+
// Get all files touched by this solution
|
|
916
|
+
let solutionFiles: string[] = item.files_touched || [];
|
|
917
|
+
|
|
918
|
+
// If not in queue item, fetch from solution definition
|
|
919
|
+
if (solutionFiles.length === 0) {
|
|
920
|
+
const solution = findSolution(item.issue_id, item.solution_id);
|
|
921
|
+
if (solution?.tasks) {
|
|
922
|
+
for (const task of solution.tasks) {
|
|
923
|
+
for (const mp of task.modification_points || []) {
|
|
924
|
+
solutionFiles.push(mp.file);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const hasConflict = solutionFiles.some(f => batchFiles.has(f));
|
|
931
|
+
|
|
932
|
+
if (!hasConflict) {
|
|
933
|
+
batch.push(itemId);
|
|
934
|
+
solutionFiles.forEach(f => batchFiles.add(f));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (batch.length === 0) {
|
|
939
|
+
// Fallback: take one at a time if all conflict
|
|
940
|
+
const first = Array.from(remainingReady)[0];
|
|
941
|
+
batch.push(first);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
parallelBatches.push(batch);
|
|
945
|
+
batch.forEach(id => remainingReady.delete(id));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
console.log(JSON.stringify({
|
|
950
|
+
queue_id: queue.id,
|
|
951
|
+
total: nodes.length,
|
|
952
|
+
ready_count: readyItems.length,
|
|
953
|
+
completed_count: completedIds.size,
|
|
954
|
+
nodes,
|
|
955
|
+
edges,
|
|
956
|
+
groups: Object.entries(groups).map(([id, solutions]) => ({ id, solutions })),
|
|
957
|
+
parallel_batches: parallelBatches,
|
|
958
|
+
_summary: {
|
|
959
|
+
can_parallel: parallelBatches[0]?.length || 0,
|
|
960
|
+
batches_needed: parallelBatches.length
|
|
961
|
+
}
|
|
962
|
+
}, null, 2));
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
848
966
|
// Archive current queue
|
|
849
967
|
if (subAction === 'archive') {
|
|
850
968
|
const queue = readActiveQueue();
|
|
@@ -889,7 +1007,7 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
889
1007
|
return;
|
|
890
1008
|
}
|
|
891
1009
|
|
|
892
|
-
// Add issue
|
|
1010
|
+
// Add issue solution to queue (solution-level granularity)
|
|
893
1011
|
if (subAction === 'add' && issueId) {
|
|
894
1012
|
const issue = findIssue(issueId);
|
|
895
1013
|
if (!issue) {
|
|
@@ -906,48 +1024,61 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
906
1024
|
|
|
907
1025
|
// Get or create active queue (create new if current is completed/archived)
|
|
908
1026
|
let queue = readActiveQueue();
|
|
909
|
-
const
|
|
1027
|
+
const items = queue.solutions || [];
|
|
1028
|
+
const isNewQueue = items.length === 0 || queue.status !== 'active';
|
|
910
1029
|
|
|
911
1030
|
if (queue.status !== 'active') {
|
|
912
1031
|
// Create new queue if current is not active
|
|
913
1032
|
queue = createEmptyQueue();
|
|
914
1033
|
}
|
|
915
1034
|
|
|
1035
|
+
// Ensure solutions array exists
|
|
1036
|
+
if (!queue.solutions) {
|
|
1037
|
+
queue.solutions = [];
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Check if solution already in queue
|
|
1041
|
+
const exists = queue.solutions.some(q => q.issue_id === issueId && q.solution_id === solution.id);
|
|
1042
|
+
if (exists) {
|
|
1043
|
+
console.log(chalk.yellow(`Solution ${solution.id} already in queue`));
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
916
1047
|
// Add issue to queue's issue list
|
|
917
1048
|
if (!queue.issue_ids.includes(issueId)) {
|
|
918
1049
|
queue.issue_ids.push(issueId);
|
|
919
1050
|
}
|
|
920
1051
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
item_id: generateQueueItemId(queue),
|
|
928
|
-
issue_id: issueId,
|
|
929
|
-
solution_id: solution.id,
|
|
930
|
-
task_id: task.id,
|
|
931
|
-
status: 'pending',
|
|
932
|
-
execution_order: queue.tasks.length + 1,
|
|
933
|
-
execution_group: 'P1',
|
|
934
|
-
depends_on: task.depends_on.map(dep => {
|
|
935
|
-
const depItem = queue.tasks.find(q => q.task_id === dep && q.issue_id === issueId);
|
|
936
|
-
return depItem?.item_id || dep;
|
|
937
|
-
}),
|
|
938
|
-
semantic_priority: 0.5,
|
|
939
|
-
assigned_executor: task.executor === 'auto' ? 'codex' : task.executor as any
|
|
940
|
-
});
|
|
941
|
-
added++;
|
|
1052
|
+
// Collect all files touched by this solution
|
|
1053
|
+
const filesTouched = new Set<string>();
|
|
1054
|
+
for (const task of solution.tasks || []) {
|
|
1055
|
+
for (const mp of task.modification_points || []) {
|
|
1056
|
+
filesTouched.add(mp.file);
|
|
1057
|
+
}
|
|
942
1058
|
}
|
|
943
1059
|
|
|
1060
|
+
// Create solution-level queue item (S-N)
|
|
1061
|
+
queue.solutions.push({
|
|
1062
|
+
item_id: generateQueueItemId(queue, 'solution'),
|
|
1063
|
+
issue_id: issueId,
|
|
1064
|
+
solution_id: solution.id,
|
|
1065
|
+
status: 'pending',
|
|
1066
|
+
execution_order: queue.solutions.length + 1,
|
|
1067
|
+
execution_group: 'P1',
|
|
1068
|
+
depends_on: [],
|
|
1069
|
+
semantic_priority: 0.5,
|
|
1070
|
+
assigned_executor: 'codex',
|
|
1071
|
+
task_count: solution.tasks?.length || 0,
|
|
1072
|
+
files_touched: Array.from(filesTouched)
|
|
1073
|
+
});
|
|
1074
|
+
|
|
944
1075
|
writeQueue(queue);
|
|
945
1076
|
updateIssue(issueId, { status: 'queued', queued_at: new Date().toISOString() });
|
|
946
1077
|
|
|
947
1078
|
if (isNewQueue) {
|
|
948
1079
|
console.log(chalk.green(`✓ Created queue ${queue.id}`));
|
|
949
1080
|
}
|
|
950
|
-
console.log(chalk.green(`✓ Added ${
|
|
1081
|
+
console.log(chalk.green(`✓ Added solution ${solution.id} (${solution.tasks?.length || 0} tasks) to queue`));
|
|
951
1082
|
return;
|
|
952
1083
|
}
|
|
953
1084
|
|
|
@@ -961,7 +1092,11 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
961
1092
|
|
|
962
1093
|
console.log(chalk.bold.cyan('\nActive Queue\n'));
|
|
963
1094
|
|
|
964
|
-
|
|
1095
|
+
// Support both solution-level and task-level queues
|
|
1096
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1097
|
+
const isSolutionLevel = !!(queue.solutions && queue.solutions.length > 0);
|
|
1098
|
+
|
|
1099
|
+
if (!queue.id || items.length === 0) {
|
|
965
1100
|
console.log(chalk.yellow('No active queue'));
|
|
966
1101
|
console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
|
|
967
1102
|
console.log(chalk.gray('Or list history: ccw issue queue list'));
|
|
@@ -970,13 +1105,17 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
970
1105
|
|
|
971
1106
|
console.log(chalk.gray(`Queue: ${queue.id}`));
|
|
972
1107
|
console.log(chalk.gray(`Issues: ${queue.issue_ids.join(', ')}`));
|
|
973
|
-
console.log(chalk.gray(`Total: ${
|
|
1108
|
+
console.log(chalk.gray(`Total: ${items.length} | Pending: ${items.filter(i => i.status === 'pending').length} | Executing: ${items.filter(i => i.status === 'executing').length} | Completed: ${items.filter(i => i.status === 'completed').length}`));
|
|
974
1109
|
console.log();
|
|
975
1110
|
|
|
976
|
-
|
|
1111
|
+
if (isSolutionLevel) {
|
|
1112
|
+
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
|
|
1113
|
+
} else {
|
|
1114
|
+
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
|
|
1115
|
+
}
|
|
977
1116
|
console.log(chalk.gray('-'.repeat(60)));
|
|
978
1117
|
|
|
979
|
-
for (const item of
|
|
1118
|
+
for (const item of items) {
|
|
980
1119
|
const statusColor = {
|
|
981
1120
|
'pending': chalk.gray,
|
|
982
1121
|
'ready': chalk.cyan,
|
|
@@ -986,10 +1125,14 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
986
1125
|
'blocked': chalk.magenta
|
|
987
1126
|
}[item.status] || chalk.white;
|
|
988
1127
|
|
|
1128
|
+
const thirdCol = isSolutionLevel
|
|
1129
|
+
? String(item.task_count || 0).padEnd(8)
|
|
1130
|
+
: (item.task_id || '-').padEnd(8);
|
|
1131
|
+
|
|
989
1132
|
console.log(
|
|
990
1133
|
item.item_id.padEnd(10) +
|
|
991
1134
|
item.issue_id.substring(0, 13).padEnd(15) +
|
|
992
|
-
|
|
1135
|
+
thirdCol +
|
|
993
1136
|
statusColor(item.status.padEnd(12)) +
|
|
994
1137
|
item.assigned_executor
|
|
995
1138
|
);
|
|
@@ -998,78 +1141,111 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
998
1141
|
|
|
999
1142
|
/**
|
|
1000
1143
|
* next - Get next ready task for execution (JSON output)
|
|
1144
|
+
* Accepts optional item_id to fetch a specific task directly
|
|
1001
1145
|
*/
|
|
1002
|
-
async function nextAction(options: IssueOptions): Promise<void> {
|
|
1146
|
+
async function nextAction(itemId: string | undefined, options: IssueOptions): Promise<void> {
|
|
1003
1147
|
const queue = readActiveQueue();
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1148
|
+
// Support both old (tasks) and new (solutions) queue format
|
|
1149
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1150
|
+
let nextItem: typeof items[0] | undefined;
|
|
1151
|
+
let isResume = false;
|
|
1152
|
+
|
|
1153
|
+
// If specific item_id provided, fetch that item directly
|
|
1154
|
+
if (itemId) {
|
|
1155
|
+
nextItem = items.find(t => t.item_id === itemId);
|
|
1156
|
+
if (!nextItem) {
|
|
1157
|
+
console.log(JSON.stringify({ status: 'error', message: `Item ${itemId} not found` }));
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
if (nextItem.status === 'completed') {
|
|
1161
|
+
console.log(JSON.stringify({ status: 'completed', message: `Item ${itemId} already completed` }));
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (nextItem.status === 'failed') {
|
|
1165
|
+
console.log(JSON.stringify({ status: 'failed', message: `Item ${itemId} failed, use retry to reset` }));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
isResume = nextItem.status === 'executing';
|
|
1169
|
+
} else {
|
|
1170
|
+
// Auto-select: Priority 1 - executing, Priority 2 - ready pending
|
|
1171
|
+
const executingItems = items.filter(item => item.status === 'executing');
|
|
1172
|
+
const pendingItems = items.filter(item => {
|
|
1173
|
+
if (item.status !== 'pending') return false;
|
|
1174
|
+
return (item.depends_on || []).every(depId => {
|
|
1175
|
+
const dep = items.find(q => q.item_id === depId);
|
|
1176
|
+
return !dep || dep.status === 'completed';
|
|
1177
|
+
});
|
|
1014
1178
|
});
|
|
1015
|
-
});
|
|
1016
1179
|
|
|
1017
|
-
|
|
1018
|
-
const readyTasks = [...executingTasks, ...pendingTasks];
|
|
1180
|
+
const readyItems = [...executingItems, ...pendingItems];
|
|
1019
1181
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1182
|
+
if (readyItems.length === 0) {
|
|
1183
|
+
console.log(JSON.stringify({
|
|
1184
|
+
status: 'empty',
|
|
1185
|
+
message: 'No ready items',
|
|
1186
|
+
queue_status: queue._metadata
|
|
1187
|
+
}, null, 2));
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1028
1190
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1191
|
+
readyItems.sort((a, b) => a.execution_order - b.execution_order);
|
|
1192
|
+
nextItem = readyItems[0];
|
|
1193
|
+
isResume = nextItem.status === 'executing';
|
|
1194
|
+
}
|
|
1033
1195
|
|
|
1034
|
-
// Load
|
|
1196
|
+
// Load FULL solution with all tasks
|
|
1035
1197
|
const solution = findSolution(nextItem.issue_id, nextItem.solution_id);
|
|
1036
|
-
const taskDef = solution?.tasks.find(t => t.id === nextItem.task_id);
|
|
1037
1198
|
|
|
1038
|
-
if (!
|
|
1039
|
-
console.log(JSON.stringify({ status: 'error', message: '
|
|
1199
|
+
if (!solution) {
|
|
1200
|
+
console.log(JSON.stringify({ status: 'error', message: 'Solution not found' }));
|
|
1040
1201
|
process.exit(1);
|
|
1041
1202
|
}
|
|
1042
1203
|
|
|
1043
|
-
// Only update status if not already executing
|
|
1204
|
+
// Only update status if not already executing
|
|
1044
1205
|
if (!isResume) {
|
|
1045
|
-
const idx =
|
|
1046
|
-
|
|
1047
|
-
|
|
1206
|
+
const idx = items.findIndex(q => q.item_id === nextItem.item_id);
|
|
1207
|
+
items[idx].status = 'executing';
|
|
1208
|
+
items[idx].started_at = new Date().toISOString();
|
|
1209
|
+
// Write back to correct array
|
|
1210
|
+
if (queue.solutions) {
|
|
1211
|
+
queue.solutions = items;
|
|
1212
|
+
} else {
|
|
1213
|
+
queue.tasks = items;
|
|
1214
|
+
}
|
|
1048
1215
|
writeQueue(queue);
|
|
1049
1216
|
updateIssue(nextItem.issue_id, { status: 'executing' });
|
|
1050
1217
|
}
|
|
1051
1218
|
|
|
1052
|
-
// Calculate queue stats
|
|
1219
|
+
// Calculate queue stats
|
|
1053
1220
|
const stats = {
|
|
1054
|
-
total:
|
|
1055
|
-
completed:
|
|
1056
|
-
failed:
|
|
1057
|
-
executing:
|
|
1058
|
-
pending:
|
|
1221
|
+
total: items.length,
|
|
1222
|
+
completed: items.filter(q => q.status === 'completed').length,
|
|
1223
|
+
failed: items.filter(q => q.status === 'failed').length,
|
|
1224
|
+
executing: items.filter(q => q.status === 'executing').length,
|
|
1225
|
+
pending: items.filter(q => q.status === 'pending').length
|
|
1059
1226
|
};
|
|
1060
1227
|
const remaining = stats.pending + stats.executing;
|
|
1061
1228
|
|
|
1229
|
+
// Calculate total estimated time for all tasks
|
|
1230
|
+
const totalMinutes = solution.tasks?.reduce((sum, t) => sum + (t.estimated_minutes || 30), 0) || 30;
|
|
1231
|
+
|
|
1062
1232
|
console.log(JSON.stringify({
|
|
1063
1233
|
item_id: nextItem.item_id,
|
|
1064
1234
|
issue_id: nextItem.issue_id,
|
|
1065
1235
|
solution_id: nextItem.solution_id,
|
|
1066
|
-
|
|
1067
|
-
|
|
1236
|
+
// Return full solution object with all tasks
|
|
1237
|
+
solution: {
|
|
1238
|
+
id: solution.id,
|
|
1239
|
+
approach: solution.approach,
|
|
1240
|
+
tasks: solution.tasks || [],
|
|
1241
|
+
exploration_context: solution.exploration_context || {}
|
|
1242
|
+
},
|
|
1068
1243
|
resumed: isResume,
|
|
1069
|
-
resume_note: isResume ? `Resuming interrupted
|
|
1244
|
+
resume_note: isResume ? `Resuming interrupted item (started: ${nextItem.started_at})` : undefined,
|
|
1070
1245
|
execution_hints: {
|
|
1071
1246
|
executor: nextItem.assigned_executor,
|
|
1072
|
-
|
|
1247
|
+
task_count: solution.tasks?.length || 0,
|
|
1248
|
+
estimated_minutes: totalMinutes
|
|
1073
1249
|
},
|
|
1074
1250
|
queue_progress: {
|
|
1075
1251
|
completed: stats.completed,
|
|
@@ -1080,18 +1256,72 @@ async function nextAction(options: IssueOptions): Promise<void> {
|
|
|
1080
1256
|
}, null, 2));
|
|
1081
1257
|
}
|
|
1082
1258
|
|
|
1259
|
+
/**
|
|
1260
|
+
* detail - Get task details by item_id (READ-ONLY, does NOT change status)
|
|
1261
|
+
* Used for parallel execution: orchestrator gets dag, then dispatches with detail <id>
|
|
1262
|
+
*/
|
|
1263
|
+
async function detailAction(itemId: string | undefined, options: IssueOptions): Promise<void> {
|
|
1264
|
+
if (!itemId) {
|
|
1265
|
+
console.log(JSON.stringify({ status: 'error', message: 'item_id is required' }));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const queue = readActiveQueue();
|
|
1270
|
+
// Support both old (tasks) and new (solutions) queue format
|
|
1271
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1272
|
+
const queueItem = items.find(t => t.item_id === itemId);
|
|
1273
|
+
|
|
1274
|
+
if (!queueItem) {
|
|
1275
|
+
console.log(JSON.stringify({ status: 'error', message: `Item ${itemId} not found` }));
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Load FULL solution with all tasks
|
|
1280
|
+
const solution = findSolution(queueItem.issue_id, queueItem.solution_id);
|
|
1281
|
+
|
|
1282
|
+
if (!solution) {
|
|
1283
|
+
console.log(JSON.stringify({ status: 'error', message: 'Solution not found' }));
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Calculate total estimated time for all tasks
|
|
1288
|
+
const totalMinutes = solution.tasks?.reduce((sum, t) => sum + (t.estimated_minutes || 30), 0) || 30;
|
|
1289
|
+
|
|
1290
|
+
// Return FULL SOLUTION with all tasks (READ-ONLY - no status update)
|
|
1291
|
+
console.log(JSON.stringify({
|
|
1292
|
+
item_id: queueItem.item_id,
|
|
1293
|
+
issue_id: queueItem.issue_id,
|
|
1294
|
+
solution_id: queueItem.solution_id,
|
|
1295
|
+
status: queueItem.status,
|
|
1296
|
+
// Return full solution object with all tasks
|
|
1297
|
+
solution: {
|
|
1298
|
+
id: solution.id,
|
|
1299
|
+
approach: solution.approach,
|
|
1300
|
+
tasks: solution.tasks || [],
|
|
1301
|
+
exploration_context: solution.exploration_context || {}
|
|
1302
|
+
},
|
|
1303
|
+
execution_hints: {
|
|
1304
|
+
executor: queueItem.assigned_executor,
|
|
1305
|
+
task_count: solution.tasks?.length || 0,
|
|
1306
|
+
estimated_minutes: totalMinutes
|
|
1307
|
+
}
|
|
1308
|
+
}, null, 2));
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1083
1311
|
/**
|
|
1084
1312
|
* done - Mark task completed or failed
|
|
1085
1313
|
*/
|
|
1086
1314
|
async function doneAction(queueId: string | undefined, options: IssueOptions): Promise<void> {
|
|
1087
1315
|
if (!queueId) {
|
|
1088
|
-
console.error(chalk.red('
|
|
1089
|
-
console.error(chalk.gray('Usage: ccw issue done <
|
|
1316
|
+
console.error(chalk.red('Item ID is required'));
|
|
1317
|
+
console.error(chalk.gray('Usage: ccw issue done <item-id> [--fail] [--reason "..."]'));
|
|
1090
1318
|
process.exit(1);
|
|
1091
1319
|
}
|
|
1092
1320
|
|
|
1093
1321
|
const queue = readActiveQueue();
|
|
1094
|
-
|
|
1322
|
+
// Support both old (tasks) and new (solutions) queue format
|
|
1323
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1324
|
+
const idx = items.findIndex(q => q.item_id === queueId);
|
|
1095
1325
|
|
|
1096
1326
|
if (idx === -1) {
|
|
1097
1327
|
console.error(chalk.red(`Queue item "${queueId}" not found`));
|
|
@@ -1099,66 +1329,69 @@ async function doneAction(queueId: string | undefined, options: IssueOptions): P
|
|
|
1099
1329
|
}
|
|
1100
1330
|
|
|
1101
1331
|
const isFail = options.fail;
|
|
1102
|
-
|
|
1103
|
-
|
|
1332
|
+
items[idx].status = isFail ? 'failed' : 'completed';
|
|
1333
|
+
items[idx].completed_at = new Date().toISOString();
|
|
1104
1334
|
|
|
1105
1335
|
if (isFail) {
|
|
1106
|
-
|
|
1336
|
+
items[idx].failure_reason = options.reason || 'Unknown failure';
|
|
1107
1337
|
} else if (options.result) {
|
|
1108
1338
|
try {
|
|
1109
|
-
|
|
1339
|
+
items[idx].result = JSON.parse(options.result);
|
|
1110
1340
|
} catch {
|
|
1111
1341
|
console.warn(chalk.yellow('Warning: Could not parse result JSON'));
|
|
1112
1342
|
}
|
|
1113
1343
|
}
|
|
1114
1344
|
|
|
1115
|
-
//
|
|
1116
|
-
const issueId =
|
|
1117
|
-
const issueTasks = queue.tasks.filter(q => q.issue_id === issueId);
|
|
1118
|
-
const allIssueComplete = issueTasks.every(q => q.status === 'completed');
|
|
1119
|
-
const anyIssueFailed = issueTasks.some(q => q.status === 'failed');
|
|
1345
|
+
// Update issue status (solution = issue in new model)
|
|
1346
|
+
const issueId = items[idx].issue_id;
|
|
1120
1347
|
|
|
1121
|
-
if (
|
|
1122
|
-
updateIssue(issueId, { status: 'completed', completed_at: new Date().toISOString() });
|
|
1123
|
-
console.log(chalk.green(`✓ ${queueId} completed`));
|
|
1124
|
-
console.log(chalk.green(`✓ Issue ${issueId} completed (all tasks done)`));
|
|
1125
|
-
} else if (anyIssueFailed) {
|
|
1348
|
+
if (isFail) {
|
|
1126
1349
|
updateIssue(issueId, { status: 'failed' });
|
|
1127
1350
|
console.log(chalk.red(`✗ ${queueId} failed`));
|
|
1128
1351
|
} else {
|
|
1129
|
-
|
|
1352
|
+
updateIssue(issueId, { status: 'completed', completed_at: new Date().toISOString() });
|
|
1353
|
+
console.log(chalk.green(`✓ ${queueId} completed`));
|
|
1354
|
+
console.log(chalk.green(`✓ Issue ${issueId} completed`));
|
|
1130
1355
|
}
|
|
1131
1356
|
|
|
1132
1357
|
// Check if entire queue is complete
|
|
1133
|
-
const allQueueComplete =
|
|
1134
|
-
const anyQueueFailed =
|
|
1358
|
+
const allQueueComplete = items.every(q => q.status === 'completed');
|
|
1359
|
+
const anyQueueFailed = items.some(q => q.status === 'failed');
|
|
1135
1360
|
|
|
1136
1361
|
if (allQueueComplete) {
|
|
1137
1362
|
queue.status = 'completed';
|
|
1138
|
-
console.log(chalk.green(`\n✓ Queue ${queue.id} completed (all
|
|
1139
|
-
} else if (anyQueueFailed &&
|
|
1363
|
+
console.log(chalk.green(`\n✓ Queue ${queue.id} completed (all solutions done)`));
|
|
1364
|
+
} else if (anyQueueFailed && items.every(q => q.status === 'completed' || q.status === 'failed')) {
|
|
1140
1365
|
queue.status = 'failed';
|
|
1141
|
-
console.log(chalk.yellow(`\n⚠ Queue ${queue.id} has failed
|
|
1366
|
+
console.log(chalk.yellow(`\n⚠ Queue ${queue.id} has failed solutions`));
|
|
1142
1367
|
}
|
|
1143
1368
|
|
|
1369
|
+
// Write back to queue (update the correct array)
|
|
1370
|
+
if (queue.solutions) {
|
|
1371
|
+
queue.solutions = items;
|
|
1372
|
+
} else {
|
|
1373
|
+
queue.tasks = items;
|
|
1374
|
+
}
|
|
1144
1375
|
writeQueue(queue);
|
|
1145
1376
|
}
|
|
1146
1377
|
|
|
1147
1378
|
/**
|
|
1148
|
-
* retry - Reset failed
|
|
1379
|
+
* retry - Reset failed items to pending for re-execution
|
|
1149
1380
|
*/
|
|
1150
1381
|
async function retryAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
|
|
1151
1382
|
const queue = readActiveQueue();
|
|
1383
|
+
// Support both old (tasks) and new (solutions) queue format
|
|
1384
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1152
1385
|
|
|
1153
|
-
if (!queue.id ||
|
|
1386
|
+
if (!queue.id || items.length === 0) {
|
|
1154
1387
|
console.log(chalk.yellow('No active queue'));
|
|
1155
1388
|
return;
|
|
1156
1389
|
}
|
|
1157
1390
|
|
|
1158
1391
|
let updated = 0;
|
|
1159
1392
|
|
|
1160
|
-
for (const item of
|
|
1161
|
-
// Retry failed
|
|
1393
|
+
for (const item of items) {
|
|
1394
|
+
// Retry failed items only
|
|
1162
1395
|
if (item.status === 'failed') {
|
|
1163
1396
|
if (!issueId || item.issue_id === issueId) {
|
|
1164
1397
|
item.status = 'pending';
|
|
@@ -1171,8 +1404,7 @@ async function retryAction(issueId: string | undefined, options: IssueOptions):
|
|
|
1171
1404
|
}
|
|
1172
1405
|
|
|
1173
1406
|
if (updated === 0) {
|
|
1174
|
-
console.log(chalk.yellow('No failed
|
|
1175
|
-
console.log(chalk.gray('Note: Interrupted (executing) tasks are auto-resumed by "ccw issue next"'));
|
|
1407
|
+
console.log(chalk.yellow('No failed items to retry'));
|
|
1176
1408
|
return;
|
|
1177
1409
|
}
|
|
1178
1410
|
|
|
@@ -1181,13 +1413,19 @@ async function retryAction(issueId: string | undefined, options: IssueOptions):
|
|
|
1181
1413
|
queue.status = 'active';
|
|
1182
1414
|
}
|
|
1183
1415
|
|
|
1416
|
+
// Write back to queue
|
|
1417
|
+
if (queue.solutions) {
|
|
1418
|
+
queue.solutions = items;
|
|
1419
|
+
} else {
|
|
1420
|
+
queue.tasks = items;
|
|
1421
|
+
}
|
|
1184
1422
|
writeQueue(queue);
|
|
1185
1423
|
|
|
1186
1424
|
if (issueId) {
|
|
1187
1425
|
updateIssue(issueId, { status: 'queued' });
|
|
1188
1426
|
}
|
|
1189
1427
|
|
|
1190
|
-
console.log(chalk.green(`✓ Reset ${updated}
|
|
1428
|
+
console.log(chalk.green(`✓ Reset ${updated} item(s) to pending`));
|
|
1191
1429
|
}
|
|
1192
1430
|
|
|
1193
1431
|
// ============ Main Entry ============
|
|
@@ -1219,7 +1457,10 @@ export async function issueCommand(
|
|
|
1219
1457
|
await queueAction(argsArray[0], argsArray[1], options);
|
|
1220
1458
|
break;
|
|
1221
1459
|
case 'next':
|
|
1222
|
-
await nextAction(options);
|
|
1460
|
+
await nextAction(argsArray[0], options);
|
|
1461
|
+
break;
|
|
1462
|
+
case 'detail':
|
|
1463
|
+
await detailAction(argsArray[0], options);
|
|
1223
1464
|
break;
|
|
1224
1465
|
case 'done':
|
|
1225
1466
|
await doneAction(argsArray[0], options);
|
|
@@ -1252,14 +1493,16 @@ export async function issueCommand(
|
|
|
1252
1493
|
console.log(chalk.gray(' queue list List all queues (history)'));
|
|
1253
1494
|
console.log(chalk.gray(' queue add <issue-id> Add issue to active queue (or create new)'));
|
|
1254
1495
|
console.log(chalk.gray(' queue switch <queue-id> Switch active queue'));
|
|
1496
|
+
console.log(chalk.gray(' queue dag Get dependency graph (JSON) for parallel execution'));
|
|
1255
1497
|
console.log(chalk.gray(' queue archive Archive current queue'));
|
|
1256
1498
|
console.log(chalk.gray(' queue delete <queue-id> Delete queue from history'));
|
|
1257
1499
|
console.log(chalk.gray(' retry [issue-id] Retry failed tasks'));
|
|
1258
1500
|
console.log();
|
|
1259
1501
|
console.log(chalk.bold('Execution Endpoints:'));
|
|
1260
|
-
console.log(chalk.gray(' next
|
|
1261
|
-
console.log(chalk.gray('
|
|
1262
|
-
console.log(chalk.gray(' done <
|
|
1502
|
+
console.log(chalk.gray(' next [item-id] Get & mark task executing (JSON)'));
|
|
1503
|
+
console.log(chalk.gray(' detail <item-id> Get task details (READ-ONLY, for parallel)'));
|
|
1504
|
+
console.log(chalk.gray(' done <item-id> Mark task completed'));
|
|
1505
|
+
console.log(chalk.gray(' done <item-id> --fail Mark task failed'));
|
|
1263
1506
|
console.log();
|
|
1264
1507
|
console.log(chalk.bold('Options:'));
|
|
1265
1508
|
console.log(chalk.gray(' --title <title> Issue/task title'));
|