claude-code-workflow 6.3.4 → 6.3.6

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.
Files changed (111) hide show
  1. package/.claude/agents/issue-plan-agent.md +859 -0
  2. package/.claude/agents/issue-queue-agent.md +702 -0
  3. package/.claude/commands/issue/execute.md +453 -0
  4. package/.claude/commands/issue/manage.md +865 -0
  5. package/.claude/commands/issue/new.md +484 -0
  6. package/.claude/commands/issue/plan.md +421 -0
  7. package/.claude/commands/issue/queue.md +354 -0
  8. package/.claude/commands/{clean.md → workflow/clean.md} +5 -5
  9. package/.claude/commands/workflow/docs/analyze.md +1467 -0
  10. package/.claude/commands/workflow/docs/copyright.md +1265 -0
  11. package/.claude/commands/workflow/execute.md +0 -1
  12. package/.claude/commands/workflow/tools/conflict-resolution.md +76 -240
  13. package/.claude/commands/workflow/tools/context-gather.md +0 -2
  14. package/.claude/commands/workflow/tools/task-generate-agent.md +81 -8
  15. package/.claude/commands/workflow/tools/task-generate-tdd.md +0 -9
  16. package/.claude/commands/workflow/tools/test-context-gather.md +2 -3
  17. package/.claude/commands/workflow/tools/test-task-generate.md +0 -2
  18. package/.claude/skills/_shared/mermaid-utils.md +584 -0
  19. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +0 -2
  20. package/.claude/skills/command-guide/reference/commands/workflow/execute.md +1 -1
  21. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +1 -2
  22. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +1 -8
  23. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-context-gather.md +1 -4
  24. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +0 -2
  25. package/.claude/skills/copyright-docs/SKILL.md +132 -0
  26. package/.claude/skills/copyright-docs/phases/01-metadata-collection.md +78 -0
  27. package/.claude/skills/copyright-docs/phases/01.5-project-exploration.md +150 -0
  28. package/.claude/skills/copyright-docs/phases/02-deep-analysis.md +664 -0
  29. package/.claude/skills/copyright-docs/phases/02.5-consolidation.md +192 -0
  30. package/.claude/skills/copyright-docs/phases/04-document-assembly.md +261 -0
  31. package/.claude/skills/copyright-docs/phases/05-compliance-refinement.md +192 -0
  32. package/.claude/skills/copyright-docs/specs/cpcc-requirements.md +121 -0
  33. package/.claude/skills/copyright-docs/templates/agent-base.md +200 -0
  34. package/.claude/skills/project-analyze/SKILL.md +162 -0
  35. package/.claude/skills/project-analyze/phases/01-requirements-discovery.md +79 -0
  36. package/.claude/skills/project-analyze/phases/02-project-exploration.md +176 -0
  37. package/.claude/skills/project-analyze/phases/03-deep-analysis.md +854 -0
  38. package/.claude/skills/project-analyze/phases/03.5-consolidation.md +233 -0
  39. package/.claude/skills/project-analyze/phases/04-report-generation.md +217 -0
  40. package/.claude/skills/project-analyze/phases/05-iterative-refinement.md +124 -0
  41. package/.claude/skills/project-analyze/specs/quality-standards.md +115 -0
  42. package/.claude/skills/project-analyze/specs/writing-style.md +152 -0
  43. package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +79 -65
  44. package/.claude/workflows/cli-templates/schemas/issue-task-jsonl-schema.json +136 -0
  45. package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +74 -0
  46. package/.claude/workflows/cli-templates/schemas/queue-schema.json +136 -0
  47. package/.claude/workflows/cli-templates/schemas/registry-schema.json +94 -0
  48. package/.claude/workflows/cli-templates/schemas/solution-schema.json +120 -0
  49. package/.claude/workflows/cli-templates/schemas/solutions-jsonl-schema.json +125 -0
  50. package/.codex/prompts/issue-execute.md +266 -0
  51. package/README.md +11 -1
  52. package/ccw/dist/cli.d.ts.map +1 -1
  53. package/ccw/dist/cli.js +25 -0
  54. package/ccw/dist/cli.js.map +1 -1
  55. package/ccw/dist/commands/cli.d.ts.map +1 -1
  56. package/ccw/dist/commands/cli.js +46 -8
  57. package/ccw/dist/commands/cli.js.map +1 -1
  58. package/ccw/dist/commands/issue.d.ts +21 -0
  59. package/ccw/dist/commands/issue.d.ts.map +1 -0
  60. package/ccw/dist/commands/issue.js +895 -0
  61. package/ccw/dist/commands/issue.js.map +1 -0
  62. package/ccw/dist/core/dashboard-generator-patch.js +1 -0
  63. package/ccw/dist/core/dashboard-generator-patch.js.map +1 -1
  64. package/ccw/dist/core/routes/cli-routes.js +2 -2
  65. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  66. package/ccw/dist/core/routes/issue-routes.d.ts +34 -0
  67. package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -0
  68. package/ccw/dist/core/routes/issue-routes.js +487 -0
  69. package/ccw/dist/core/routes/issue-routes.js.map +1 -0
  70. package/ccw/dist/core/server.d.ts.map +1 -1
  71. package/ccw/dist/core/server.js +17 -2
  72. package/ccw/dist/core/server.js.map +1 -1
  73. package/ccw/dist/tools/claude-cli-tools.d.ts +7 -3
  74. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  75. package/ccw/dist/tools/claude-cli-tools.js +31 -17
  76. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  77. package/ccw/dist/tools/smart-search.d.ts +25 -0
  78. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  79. package/ccw/dist/tools/smart-search.js +121 -17
  80. package/ccw/dist/tools/smart-search.js.map +1 -1
  81. package/ccw/src/cli.ts +26 -0
  82. package/ccw/src/commands/cli.ts +49 -7
  83. package/ccw/src/commands/issue.ts +1184 -0
  84. package/ccw/src/core/dashboard-generator-patch.ts +1 -0
  85. package/ccw/src/core/routes/cli-routes.ts +3 -3
  86. package/ccw/src/core/routes/issue-routes.ts +559 -0
  87. package/ccw/src/core/server.ts +17 -2
  88. package/ccw/src/templates/dashboard-css/32-issue-manager.css +2544 -0
  89. package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +467 -0
  90. package/ccw/src/templates/dashboard-js/components/cli-history.js +40 -13
  91. package/ccw/src/templates/dashboard-js/components/cli-status.js +26 -2
  92. package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +461 -0
  93. package/ccw/src/templates/dashboard-js/components/navigation.js +8 -0
  94. package/ccw/src/templates/dashboard-js/components/notifications.js +16 -0
  95. package/ccw/src/templates/dashboard-js/i18n.js +290 -2
  96. package/ccw/src/templates/dashboard-js/views/cli-manager.js +5 -0
  97. package/ccw/src/templates/dashboard-js/views/history.js +19 -4
  98. package/ccw/src/templates/dashboard-js/views/hook-manager.js +11 -5
  99. package/ccw/src/templates/dashboard-js/views/issue-manager.js +1546 -0
  100. package/ccw/src/templates/dashboard.html +55 -0
  101. package/ccw/src/tools/claude-cli-tools.ts +37 -20
  102. package/ccw/src/tools/smart-search.ts +157 -16
  103. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  104. package/codex-lens/src/codexlens/config.py +5 -0
  105. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  106. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  107. package/codex-lens/src/codexlens/search/hybrid_search.py +144 -11
  108. package/codex-lens/src/codexlens/search/ranking.py +267 -1
  109. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  110. package/codex-lens/src/codexlens/semantic/chunker.py +55 -10
  111. package/package.json +2 -2
@@ -0,0 +1,1184 @@
1
+ /**
2
+ * Issue Command - Unified JSONL storage with CLI & API compatibility
3
+ * Storage: issues.jsonl + solutions/{issue-id}.jsonl + queue.json
4
+ * Commands: init, list, status, task, bind, queue, next, done, retry
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
9
+ import { join, resolve } from 'path';
10
+
11
+ // Handle EPIPE errors gracefully
12
+ process.stdout.on('error', (err: NodeJS.ErrnoException) => {
13
+ if (err.code === 'EPIPE') {
14
+ process.exit(0);
15
+ }
16
+ throw err;
17
+ });
18
+
19
+ // ============ Interfaces ============
20
+
21
+ interface Issue {
22
+ id: string;
23
+ title: string;
24
+ status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused';
25
+ priority: number;
26
+ context: string;
27
+ bound_solution_id: string | null;
28
+ solution_count: number;
29
+ source?: string;
30
+ source_url?: string;
31
+ labels?: string[];
32
+ created_at: string;
33
+ updated_at: string;
34
+ planned_at?: string;
35
+ queued_at?: string;
36
+ completed_at?: string;
37
+ }
38
+
39
+ interface TaskTest {
40
+ unit?: string[]; // Unit test requirements
41
+ integration?: string[]; // Integration test requirements
42
+ commands?: string[]; // Test commands to run
43
+ coverage_target?: number; // Minimum coverage % (optional)
44
+ }
45
+
46
+ interface TaskAcceptance {
47
+ criteria: string[]; // Acceptance criteria (testable)
48
+ verification: string[]; // How to verify each criterion
49
+ manual_checks?: string[]; // Manual verification steps if needed
50
+ }
51
+
52
+ interface TaskCommit {
53
+ type: 'feat' | 'fix' | 'refactor' | 'test' | 'docs' | 'chore';
54
+ scope: string; // Commit scope (e.g., "auth", "api")
55
+ message_template: string; // Commit message template
56
+ breaking?: boolean; // Breaking change flag
57
+ }
58
+
59
+ interface SolutionTask {
60
+ id: string;
61
+ title: string;
62
+ scope: string;
63
+ action: string;
64
+ description?: string;
65
+ modification_points?: { file: string; target: string; change: string }[];
66
+
67
+ // Lifecycle phases (closed-loop)
68
+ implementation: string[]; // Implementation steps
69
+ test: TaskTest; // Test requirements
70
+ regression: string[]; // Regression check points
71
+ acceptance: TaskAcceptance; // Acceptance criteria & verification
72
+ commit: TaskCommit; // Commit specification
73
+
74
+ depends_on: string[];
75
+ estimated_minutes?: number;
76
+ executor: 'codex' | 'gemini' | 'agent' | 'auto';
77
+
78
+ // Lifecycle status tracking
79
+ lifecycle_status?: {
80
+ implemented: boolean;
81
+ tested: boolean;
82
+ regression_passed: boolean;
83
+ accepted: boolean;
84
+ committed: boolean;
85
+ };
86
+ status?: string;
87
+ priority?: number;
88
+ }
89
+
90
+ interface Solution {
91
+ id: string;
92
+ description?: string;
93
+ tasks: SolutionTask[];
94
+ exploration_context?: Record<string, any>;
95
+ analysis?: { risk?: string; impact?: string; complexity?: string };
96
+ score?: number;
97
+ is_bound: boolean;
98
+ created_at: string;
99
+ bound_at?: string;
100
+ }
101
+
102
+ interface QueueItem {
103
+ queue_id: string;
104
+ issue_id: string;
105
+ solution_id: string;
106
+ task_id: string;
107
+ status: 'pending' | 'ready' | 'executing' | 'completed' | 'failed' | 'blocked';
108
+ execution_order: number;
109
+ execution_group: string;
110
+ depends_on: string[];
111
+ semantic_priority: number;
112
+ assigned_executor: 'codex' | 'gemini' | 'agent';
113
+ queued_at: string;
114
+ started_at?: string;
115
+ completed_at?: string;
116
+ result?: Record<string, any>;
117
+ failure_reason?: string;
118
+ }
119
+
120
+ interface Queue {
121
+ id: string; // Queue unique ID: QUE-YYYYMMDD-HHMMSS
122
+ name?: string; // Optional queue name
123
+ status: 'active' | 'completed' | 'archived' | 'failed';
124
+ issue_ids: string[]; // Issues in this queue
125
+ queue: QueueItem[];
126
+ conflicts: any[];
127
+ execution_groups?: any[];
128
+ _metadata: {
129
+ version: string;
130
+ total_tasks: number;
131
+ pending_count: number;
132
+ executing_count: number;
133
+ completed_count: number;
134
+ failed_count: number;
135
+ created_at: string;
136
+ updated_at: string;
137
+ };
138
+ }
139
+
140
+ interface QueueIndex {
141
+ active_queue_id: string | null;
142
+ queues: {
143
+ id: string;
144
+ status: string;
145
+ issue_ids: string[];
146
+ total_tasks: number;
147
+ completed_tasks: number;
148
+ created_at: string;
149
+ completed_at?: string;
150
+ }[];
151
+ }
152
+
153
+ interface IssueOptions {
154
+ status?: string;
155
+ title?: string;
156
+ description?: string;
157
+ executor?: string;
158
+ priority?: string;
159
+ solution?: string;
160
+ result?: string;
161
+ reason?: string;
162
+ json?: boolean;
163
+ force?: boolean;
164
+ fail?: boolean;
165
+ }
166
+
167
+ const ISSUES_DIR = '.workflow/issues';
168
+
169
+ // ============ Storage Layer (JSONL) ============
170
+
171
+ function getProjectRoot(): string {
172
+ let dir = process.cwd();
173
+ while (dir !== resolve(dir, '..')) {
174
+ if (existsSync(join(dir, '.workflow')) || existsSync(join(dir, '.git'))) {
175
+ return dir;
176
+ }
177
+ dir = resolve(dir, '..');
178
+ }
179
+ return process.cwd();
180
+ }
181
+
182
+ function getIssuesDir(): string {
183
+ return join(getProjectRoot(), ISSUES_DIR);
184
+ }
185
+
186
+ function ensureIssuesDir(): void {
187
+ const dir = getIssuesDir();
188
+ if (!existsSync(dir)) {
189
+ mkdirSync(dir, { recursive: true });
190
+ }
191
+ }
192
+
193
+ // ============ Issues JSONL ============
194
+
195
+ function readIssues(): Issue[] {
196
+ const path = join(getIssuesDir(), 'issues.jsonl');
197
+ if (!existsSync(path)) return [];
198
+ try {
199
+ return readFileSync(path, 'utf-8')
200
+ .split('\n')
201
+ .filter(line => line.trim())
202
+ .map(line => JSON.parse(line));
203
+ } catch {
204
+ return [];
205
+ }
206
+ }
207
+
208
+ function writeIssues(issues: Issue[]): void {
209
+ ensureIssuesDir();
210
+ const path = join(getIssuesDir(), 'issues.jsonl');
211
+ writeFileSync(path, issues.map(i => JSON.stringify(i)).join('\n'), 'utf-8');
212
+ }
213
+
214
+ function findIssue(issueId: string): Issue | undefined {
215
+ return readIssues().find(i => i.id === issueId);
216
+ }
217
+
218
+ function updateIssue(issueId: string, updates: Partial<Issue>): boolean {
219
+ const issues = readIssues();
220
+ const idx = issues.findIndex(i => i.id === issueId);
221
+ if (idx === -1) return false;
222
+ issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
223
+ writeIssues(issues);
224
+ return true;
225
+ }
226
+
227
+ // ============ Solutions JSONL ============
228
+
229
+ function getSolutionsPath(issueId: string): string {
230
+ return join(getIssuesDir(), 'solutions', `${issueId}.jsonl`);
231
+ }
232
+
233
+ function readSolutions(issueId: string): Solution[] {
234
+ const path = getSolutionsPath(issueId);
235
+ if (!existsSync(path)) return [];
236
+ try {
237
+ return readFileSync(path, 'utf-8')
238
+ .split('\n')
239
+ .filter(line => line.trim())
240
+ .map(line => JSON.parse(line));
241
+ } catch {
242
+ return [];
243
+ }
244
+ }
245
+
246
+ function writeSolutions(issueId: string, solutions: Solution[]): void {
247
+ const dir = join(getIssuesDir(), 'solutions');
248
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
249
+ writeFileSync(getSolutionsPath(issueId), solutions.map(s => JSON.stringify(s)).join('\n'), 'utf-8');
250
+ }
251
+
252
+ function findSolution(issueId: string, solutionId: string): Solution | undefined {
253
+ return readSolutions(issueId).find(s => s.id === solutionId);
254
+ }
255
+
256
+ function getBoundSolution(issueId: string): Solution | undefined {
257
+ return readSolutions(issueId).find(s => s.is_bound);
258
+ }
259
+
260
+ function generateSolutionId(): string {
261
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
262
+ return `SOL-${ts}`;
263
+ }
264
+
265
+ // ============ Queue Management (Multi-Queue) ============
266
+
267
+ function getQueuesDir(): string {
268
+ return join(getIssuesDir(), 'queues');
269
+ }
270
+
271
+ function ensureQueuesDir(): void {
272
+ const dir = getQueuesDir();
273
+ if (!existsSync(dir)) {
274
+ mkdirSync(dir, { recursive: true });
275
+ }
276
+ }
277
+
278
+ function readQueueIndex(): QueueIndex {
279
+ const path = join(getQueuesDir(), 'index.json');
280
+ if (!existsSync(path)) {
281
+ return { active_queue_id: null, queues: [] };
282
+ }
283
+ return JSON.parse(readFileSync(path, 'utf-8'));
284
+ }
285
+
286
+ function writeQueueIndex(index: QueueIndex): void {
287
+ ensureQueuesDir();
288
+ writeFileSync(join(getQueuesDir(), 'index.json'), JSON.stringify(index, null, 2), 'utf-8');
289
+ }
290
+
291
+ function generateQueueFileId(): string {
292
+ const now = new Date();
293
+ const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
294
+ return `QUE-${ts}`;
295
+ }
296
+
297
+ function readQueue(queueId?: string): Queue | null {
298
+ const index = readQueueIndex();
299
+ const targetId = queueId || index.active_queue_id;
300
+
301
+ if (!targetId) return null;
302
+
303
+ const path = join(getQueuesDir(), `${targetId}.json`);
304
+ if (!existsSync(path)) return null;
305
+
306
+ return JSON.parse(readFileSync(path, 'utf-8'));
307
+ }
308
+
309
+ function readActiveQueue(): Queue {
310
+ const queue = readQueue();
311
+ if (queue) return queue;
312
+
313
+ // Return empty queue structure if no active queue
314
+ return createEmptyQueue();
315
+ }
316
+
317
+ function createEmptyQueue(): Queue {
318
+ return {
319
+ id: generateQueueFileId(),
320
+ status: 'active',
321
+ issue_ids: [],
322
+ queue: [],
323
+ conflicts: [],
324
+ _metadata: {
325
+ version: '2.0',
326
+ total_tasks: 0,
327
+ pending_count: 0,
328
+ executing_count: 0,
329
+ completed_count: 0,
330
+ failed_count: 0,
331
+ created_at: new Date().toISOString(),
332
+ updated_at: new Date().toISOString()
333
+ }
334
+ };
335
+ }
336
+
337
+ function writeQueue(queue: Queue): void {
338
+ ensureQueuesDir();
339
+
340
+ // Update metadata counts
341
+ queue._metadata.total_tasks = queue.queue.length;
342
+ queue._metadata.pending_count = queue.queue.filter(q => q.status === 'pending').length;
343
+ queue._metadata.executing_count = queue.queue.filter(q => q.status === 'executing').length;
344
+ queue._metadata.completed_count = queue.queue.filter(q => q.status === 'completed').length;
345
+ queue._metadata.failed_count = queue.queue.filter(q => q.status === 'failed').length;
346
+ queue._metadata.updated_at = new Date().toISOString();
347
+
348
+ // Write queue file
349
+ const path = join(getQueuesDir(), `${queue.id}.json`);
350
+ writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
351
+
352
+ // Update index
353
+ const index = readQueueIndex();
354
+ const existingIdx = index.queues.findIndex(q => q.id === queue.id);
355
+
356
+ const indexEntry = {
357
+ id: queue.id,
358
+ status: queue.status,
359
+ issue_ids: queue.issue_ids,
360
+ total_tasks: queue._metadata.total_tasks,
361
+ completed_tasks: queue._metadata.completed_count,
362
+ created_at: queue._metadata.created_at,
363
+ completed_at: queue.status === 'completed' ? new Date().toISOString() : undefined
364
+ };
365
+
366
+ if (existingIdx >= 0) {
367
+ index.queues[existingIdx] = indexEntry;
368
+ } else {
369
+ index.queues.unshift(indexEntry);
370
+ }
371
+
372
+ if (queue.status === 'active') {
373
+ index.active_queue_id = queue.id;
374
+ }
375
+
376
+ writeQueueIndex(index);
377
+ }
378
+
379
+ function generateQueueItemId(queue: Queue): string {
380
+ const maxNum = queue.queue.reduce((max, q) => {
381
+ const match = q.queue_id.match(/^Q-(\d+)$/);
382
+ return match ? Math.max(max, parseInt(match[1])) : max;
383
+ }, 0);
384
+ return `Q-${String(maxNum + 1).padStart(3, '0')}`;
385
+ }
386
+
387
+ // ============ Commands ============
388
+
389
+ /**
390
+ * init - Initialize a new issue
391
+ */
392
+ async function initAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
393
+ if (!issueId) {
394
+ console.error(chalk.red('Issue ID is required'));
395
+ console.error(chalk.gray('Usage: ccw issue init <issue-id> [--title "..."]'));
396
+ process.exit(1);
397
+ }
398
+
399
+ const existing = findIssue(issueId);
400
+ if (existing && !options.force) {
401
+ console.error(chalk.red(`Issue "${issueId}" already exists`));
402
+ console.error(chalk.gray('Use --force to reinitialize'));
403
+ process.exit(1);
404
+ }
405
+
406
+ const issues = readIssues().filter(i => i.id !== issueId);
407
+ const newIssue: Issue = {
408
+ id: issueId,
409
+ title: options.title || issueId,
410
+ status: 'registered',
411
+ priority: options.priority ? parseInt(options.priority) : 3,
412
+ context: options.description || '',
413
+ bound_solution_id: null,
414
+ solution_count: 0,
415
+ created_at: new Date().toISOString(),
416
+ updated_at: new Date().toISOString()
417
+ };
418
+
419
+ issues.push(newIssue);
420
+ writeIssues(issues);
421
+
422
+ console.log(chalk.green(`✓ Issue "${issueId}" initialized`));
423
+ console.log(chalk.gray(` Next: ccw issue task ${issueId} --title "Task title"`));
424
+ }
425
+
426
+ /**
427
+ * list - List issues or tasks
428
+ */
429
+ async function listAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
430
+ if (!issueId) {
431
+ // List all issues
432
+ const issues = readIssues();
433
+
434
+ if (options.json) {
435
+ console.log(JSON.stringify(issues, null, 2));
436
+ return;
437
+ }
438
+
439
+ if (issues.length === 0) {
440
+ console.log(chalk.yellow('No issues found'));
441
+ console.log(chalk.gray('Create one with: ccw issue init <issue-id>'));
442
+ return;
443
+ }
444
+
445
+ console.log(chalk.bold.cyan('\nIssues\n'));
446
+ console.log(chalk.gray('ID'.padEnd(20) + 'Status'.padEnd(15) + 'Solutions'.padEnd(12) + 'Title'));
447
+ console.log(chalk.gray('-'.repeat(70)));
448
+
449
+ for (const issue of issues) {
450
+ const statusColor = {
451
+ 'registered': chalk.gray,
452
+ 'planning': chalk.blue,
453
+ 'planned': chalk.cyan,
454
+ 'queued': chalk.yellow,
455
+ 'executing': chalk.yellow,
456
+ 'completed': chalk.green,
457
+ 'failed': chalk.red,
458
+ 'paused': chalk.magenta
459
+ }[issue.status] || chalk.white;
460
+
461
+ const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${issue.solution_count}`;
462
+ console.log(
463
+ issue.id.padEnd(20) +
464
+ statusColor(issue.status.padEnd(15)) +
465
+ bound.padEnd(12) +
466
+ (issue.title || '').substring(0, 30)
467
+ );
468
+ }
469
+ return;
470
+ }
471
+
472
+ // List tasks in bound solution
473
+ const issue = findIssue(issueId);
474
+ if (!issue) {
475
+ console.error(chalk.red(`Issue "${issueId}" not found`));
476
+ process.exit(1);
477
+ }
478
+
479
+ const solution = getBoundSolution(issueId);
480
+ const tasks = solution?.tasks || [];
481
+
482
+ if (options.json) {
483
+ console.log(JSON.stringify({ issue, solution, tasks }, null, 2));
484
+ return;
485
+ }
486
+
487
+ console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
488
+ console.log(`Title: ${issue.title}`);
489
+ console.log(`Status: ${issue.status}`);
490
+ console.log(`Bound: ${issue.bound_solution_id || 'none'}`);
491
+ console.log();
492
+
493
+ if (tasks.length === 0) {
494
+ console.log(chalk.yellow('No tasks (bind a solution first)'));
495
+ return;
496
+ }
497
+
498
+ console.log(chalk.gray('ID'.padEnd(8) + 'Action'.padEnd(12) + 'Scope'.padEnd(20) + 'Title'));
499
+ console.log(chalk.gray('-'.repeat(70)));
500
+
501
+ for (const task of tasks) {
502
+ console.log(
503
+ task.id.padEnd(8) +
504
+ task.action.padEnd(12) +
505
+ task.scope.substring(0, 18).padEnd(20) +
506
+ task.title.substring(0, 30)
507
+ );
508
+ }
509
+ }
510
+
511
+ /**
512
+ * status - Show detailed status
513
+ */
514
+ async function statusAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
515
+ if (!issueId) {
516
+ // Show queue status
517
+ const queue = readActiveQueue();
518
+ const issues = readIssues();
519
+ const index = readQueueIndex();
520
+
521
+ if (options.json) {
522
+ console.log(JSON.stringify({ queue: queue._metadata, issues: issues.length, queues: index.queues.length }, null, 2));
523
+ return;
524
+ }
525
+
526
+ console.log(chalk.bold.cyan('\nSystem Status\n'));
527
+ console.log(`Issues: ${issues.length}`);
528
+ console.log(`Queues: ${index.queues.length} (Active: ${index.active_queue_id || 'none'})`);
529
+ console.log(`Active Queue: ${queue._metadata.total_tasks} tasks`);
530
+ console.log(` Pending: ${queue._metadata.pending_count}`);
531
+ console.log(` Executing: ${queue._metadata.executing_count}`);
532
+ console.log(` Completed: ${queue._metadata.completed_count}`);
533
+ console.log(` Failed: ${queue._metadata.failed_count}`);
534
+ return;
535
+ }
536
+
537
+ const issue = findIssue(issueId);
538
+ if (!issue) {
539
+ console.error(chalk.red(`Issue "${issueId}" not found`));
540
+ process.exit(1);
541
+ }
542
+
543
+ const solutions = readSolutions(issueId);
544
+ const boundSol = solutions.find(s => s.is_bound);
545
+
546
+ if (options.json) {
547
+ console.log(JSON.stringify({ issue, solutions, bound: boundSol }, null, 2));
548
+ return;
549
+ }
550
+
551
+ console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
552
+ console.log(`Title: ${issue.title}`);
553
+ console.log(`Status: ${issue.status}`);
554
+ console.log(`Priority: ${issue.priority}`);
555
+ console.log(`Created: ${issue.created_at}`);
556
+ console.log(`Updated: ${issue.updated_at}`);
557
+
558
+ if (issue.context) {
559
+ console.log();
560
+ console.log(chalk.bold('Context:'));
561
+ console.log(issue.context.substring(0, 200));
562
+ }
563
+
564
+ console.log();
565
+ console.log(chalk.bold(`Solutions (${solutions.length}):`));
566
+ for (const sol of solutions) {
567
+ const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
568
+ console.log(` ${marker} ${sol.id}: ${sol.tasks.length} tasks`);
569
+ }
570
+ }
571
+
572
+ /**
573
+ * task - Add or update task (simplified - mainly for manual task management)
574
+ */
575
+ async function taskAction(issueId: string | undefined, taskId: string | undefined, options: IssueOptions): Promise<void> {
576
+ if (!issueId) {
577
+ console.error(chalk.red('Issue ID is required'));
578
+ console.error(chalk.gray('Usage: ccw issue task <issue-id> [task-id] --title "..."'));
579
+ process.exit(1);
580
+ }
581
+
582
+ const issue = findIssue(issueId);
583
+ if (!issue) {
584
+ console.error(chalk.red(`Issue "${issueId}" not found`));
585
+ process.exit(1);
586
+ }
587
+
588
+ const solutions = readSolutions(issueId);
589
+ let boundIdx = solutions.findIndex(s => s.is_bound);
590
+
591
+ // Create default solution if none bound
592
+ if (boundIdx === -1) {
593
+ const newSol: Solution = {
594
+ id: generateSolutionId(),
595
+ description: 'Manual tasks',
596
+ tasks: [],
597
+ is_bound: true,
598
+ created_at: new Date().toISOString(),
599
+ bound_at: new Date().toISOString()
600
+ };
601
+ solutions.push(newSol);
602
+ boundIdx = solutions.length - 1;
603
+ updateIssue(issueId, { bound_solution_id: newSol.id, status: 'planned' });
604
+ }
605
+
606
+ const solution = solutions[boundIdx];
607
+
608
+ if (taskId) {
609
+ // Update existing task
610
+ const taskIdx = solution.tasks.findIndex(t => t.id === taskId);
611
+ if (taskIdx === -1) {
612
+ console.error(chalk.red(`Task "${taskId}" not found`));
613
+ process.exit(1);
614
+ }
615
+
616
+ if (options.title) solution.tasks[taskIdx].title = options.title;
617
+ if (options.status) solution.tasks[taskIdx].status = options.status;
618
+ if (options.executor) solution.tasks[taskIdx].executor = options.executor as any;
619
+
620
+ writeSolutions(issueId, solutions);
621
+ console.log(chalk.green(`✓ Task ${taskId} updated`));
622
+ } else {
623
+ // Add new task
624
+ if (!options.title) {
625
+ console.error(chalk.red('Task title is required (--title)'));
626
+ process.exit(1);
627
+ }
628
+
629
+ const newTaskId = `T${solution.tasks.length + 1}`;
630
+ const newTask: SolutionTask = {
631
+ id: newTaskId,
632
+ title: options.title,
633
+ scope: '',
634
+ action: 'Implement',
635
+ description: options.description || options.title,
636
+ implementation: [],
637
+ test: {
638
+ unit: [],
639
+ commands: ['npm test']
640
+ },
641
+ regression: ['npm test'],
642
+ acceptance: {
643
+ criteria: ['Task completed successfully'],
644
+ verification: ['Manual verification']
645
+ },
646
+ commit: {
647
+ type: 'feat',
648
+ scope: 'core',
649
+ message_template: `feat(core): ${options.title}`
650
+ },
651
+ depends_on: [],
652
+ executor: (options.executor as any) || 'auto'
653
+ };
654
+
655
+ solution.tasks.push(newTask);
656
+ writeSolutions(issueId, solutions);
657
+ console.log(chalk.green(`✓ Task ${newTaskId} added to ${issueId}`));
658
+ }
659
+ }
660
+
661
+ /**
662
+ * bind - Register and/or bind a solution
663
+ */
664
+ async function bindAction(issueId: string | undefined, solutionId: string | undefined, options: IssueOptions): Promise<void> {
665
+ if (!issueId) {
666
+ console.error(chalk.red('Issue ID is required'));
667
+ console.error(chalk.gray('Usage: ccw issue bind <issue-id> [solution-id] [--solution <path>]'));
668
+ process.exit(1);
669
+ }
670
+
671
+ const issue = findIssue(issueId);
672
+ if (!issue) {
673
+ console.error(chalk.red(`Issue "${issueId}" not found`));
674
+ process.exit(1);
675
+ }
676
+
677
+ let solutions = readSolutions(issueId);
678
+
679
+ // Register new solution from file if provided
680
+ if (options.solution) {
681
+ try {
682
+ const content = readFileSync(options.solution, 'utf-8');
683
+ const data = JSON.parse(content);
684
+ const newSol: Solution = {
685
+ id: solutionId || generateSolutionId(),
686
+ description: data.description || data.approach_name || 'Imported solution',
687
+ tasks: data.tasks || [],
688
+ exploration_context: data.exploration_context,
689
+ analysis: data.analysis,
690
+ score: data.score,
691
+ is_bound: false,
692
+ created_at: new Date().toISOString()
693
+ };
694
+ solutions.push(newSol);
695
+ solutionId = newSol.id;
696
+ console.log(chalk.green(`✓ Solution ${solutionId} registered (${newSol.tasks.length} tasks)`));
697
+ } catch (e) {
698
+ console.error(chalk.red(`Failed to read solution file: ${options.solution}`));
699
+ process.exit(1);
700
+ }
701
+ }
702
+
703
+ if (!solutionId) {
704
+ // List available solutions
705
+ if (solutions.length === 0) {
706
+ console.log(chalk.yellow('No solutions available'));
707
+ console.log(chalk.gray('Register one: ccw issue bind <issue-id> --solution <path>'));
708
+ return;
709
+ }
710
+
711
+ console.log(chalk.bold.cyan(`\nSolutions for ${issueId}:\n`));
712
+ for (const sol of solutions) {
713
+ const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
714
+ console.log(` ${marker} ${sol.id}: ${sol.tasks.length} tasks - ${sol.description || ''}`);
715
+ }
716
+ return;
717
+ }
718
+
719
+ // Bind the specified solution
720
+ const solIdx = solutions.findIndex(s => s.id === solutionId);
721
+ if (solIdx === -1) {
722
+ console.error(chalk.red(`Solution "${solutionId}" not found`));
723
+ process.exit(1);
724
+ }
725
+
726
+ // Unbind all, bind selected
727
+ solutions = solutions.map(s => ({ ...s, is_bound: false }));
728
+ solutions[solIdx].is_bound = true;
729
+ solutions[solIdx].bound_at = new Date().toISOString();
730
+
731
+ writeSolutions(issueId, solutions);
732
+ updateIssue(issueId, {
733
+ bound_solution_id: solutionId,
734
+ solution_count: solutions.length,
735
+ status: 'planned',
736
+ planned_at: new Date().toISOString()
737
+ });
738
+
739
+ console.log(chalk.green(`✓ Solution ${solutionId} bound to ${issueId}`));
740
+ }
741
+
742
+ /**
743
+ * queue - Queue management (list / add / history)
744
+ */
745
+ async function queueAction(subAction: string | undefined, issueId: string | undefined, options: IssueOptions): Promise<void> {
746
+ // List all queues (history)
747
+ if (subAction === 'list' || subAction === 'history') {
748
+ const index = readQueueIndex();
749
+
750
+ if (options.json) {
751
+ console.log(JSON.stringify(index, null, 2));
752
+ return;
753
+ }
754
+
755
+ console.log(chalk.bold.cyan('\nQueue History\n'));
756
+ console.log(chalk.gray(`Active: ${index.active_queue_id || 'none'}`));
757
+ console.log();
758
+
759
+ if (index.queues.length === 0) {
760
+ console.log(chalk.yellow('No queues found'));
761
+ console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
762
+ return;
763
+ }
764
+
765
+ console.log(chalk.gray('ID'.padEnd(22) + 'Status'.padEnd(12) + 'Tasks'.padEnd(10) + 'Issues'));
766
+ console.log(chalk.gray('-'.repeat(70)));
767
+
768
+ for (const q of index.queues) {
769
+ const statusColor = {
770
+ 'active': chalk.green,
771
+ 'completed': chalk.cyan,
772
+ 'archived': chalk.gray,
773
+ 'failed': chalk.red
774
+ }[q.status] || chalk.white;
775
+
776
+ const marker = q.id === index.active_queue_id ? '→ ' : ' ';
777
+ console.log(
778
+ marker +
779
+ q.id.padEnd(20) +
780
+ statusColor(q.status.padEnd(12)) +
781
+ `${q.completed_tasks}/${q.total_tasks}`.padEnd(10) +
782
+ q.issue_ids.join(', ')
783
+ );
784
+ }
785
+ return;
786
+ }
787
+
788
+ // Switch active queue
789
+ if (subAction === 'switch' && issueId) {
790
+ const queueId = issueId; // issueId is actually queue ID here
791
+ const targetQueue = readQueue(queueId);
792
+
793
+ if (!targetQueue) {
794
+ console.error(chalk.red(`Queue "${queueId}" not found`));
795
+ process.exit(1);
796
+ }
797
+
798
+ const index = readQueueIndex();
799
+ index.active_queue_id = queueId;
800
+ writeQueueIndex(index);
801
+
802
+ console.log(chalk.green(`✓ Switched to queue ${queueId}`));
803
+ return;
804
+ }
805
+
806
+ // Archive current queue
807
+ if (subAction === 'archive') {
808
+ const queue = readActiveQueue();
809
+ if (!queue.id || queue.queue.length === 0) {
810
+ console.log(chalk.yellow('No active queue to archive'));
811
+ return;
812
+ }
813
+
814
+ queue.status = 'archived';
815
+ writeQueue(queue);
816
+
817
+ const index = readQueueIndex();
818
+ index.active_queue_id = null;
819
+ writeQueueIndex(index);
820
+
821
+ console.log(chalk.green(`✓ Archived queue ${queue.id}`));
822
+ return;
823
+ }
824
+
825
+ // Add issue tasks to queue
826
+ if (subAction === 'add' && issueId) {
827
+ const issue = findIssue(issueId);
828
+ if (!issue) {
829
+ console.error(chalk.red(`Issue "${issueId}" not found`));
830
+ process.exit(1);
831
+ }
832
+
833
+ const solution = getBoundSolution(issueId);
834
+ if (!solution) {
835
+ console.error(chalk.red(`No bound solution for "${issueId}"`));
836
+ console.error(chalk.gray('First bind a solution: ccw issue bind <issue-id> <solution-id>'));
837
+ process.exit(1);
838
+ }
839
+
840
+ // Get or create active queue (create new if current is completed/archived)
841
+ let queue = readActiveQueue();
842
+ const isNewQueue = queue.queue.length === 0 || queue.status !== 'active';
843
+
844
+ if (queue.status !== 'active') {
845
+ // Create new queue if current is not active
846
+ queue = createEmptyQueue();
847
+ }
848
+
849
+ // Add issue to queue's issue list
850
+ if (!queue.issue_ids.includes(issueId)) {
851
+ queue.issue_ids.push(issueId);
852
+ }
853
+
854
+ let added = 0;
855
+ for (const task of solution.tasks) {
856
+ const exists = queue.queue.some(q => q.issue_id === issueId && q.task_id === task.id);
857
+ if (exists) continue;
858
+
859
+ queue.queue.push({
860
+ queue_id: generateQueueItemId(queue),
861
+ issue_id: issueId,
862
+ solution_id: solution.id,
863
+ task_id: task.id,
864
+ status: 'pending',
865
+ execution_order: queue.queue.length + 1,
866
+ execution_group: 'P1',
867
+ depends_on: task.depends_on.map(dep => {
868
+ const depItem = queue.queue.find(q => q.task_id === dep && q.issue_id === issueId);
869
+ return depItem?.queue_id || dep;
870
+ }),
871
+ semantic_priority: 0.5,
872
+ assigned_executor: task.executor === 'auto' ? 'codex' : task.executor as any,
873
+ queued_at: new Date().toISOString()
874
+ });
875
+ added++;
876
+ }
877
+
878
+ writeQueue(queue);
879
+ updateIssue(issueId, { status: 'queued', queued_at: new Date().toISOString() });
880
+
881
+ if (isNewQueue) {
882
+ console.log(chalk.green(`✓ Created queue ${queue.id}`));
883
+ }
884
+ console.log(chalk.green(`✓ Added ${added} tasks from ${solution.id}`));
885
+ return;
886
+ }
887
+
888
+ // Show current queue
889
+ const queue = readActiveQueue();
890
+
891
+ if (options.json) {
892
+ console.log(JSON.stringify(queue, null, 2));
893
+ return;
894
+ }
895
+
896
+ console.log(chalk.bold.cyan('\nActive Queue\n'));
897
+
898
+ if (!queue.id || queue.queue.length === 0) {
899
+ console.log(chalk.yellow('No active queue'));
900
+ console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
901
+ console.log(chalk.gray('Or list history: ccw issue queue list'));
902
+ return;
903
+ }
904
+
905
+ console.log(chalk.gray(`Queue: ${queue.id}`));
906
+ console.log(chalk.gray(`Issues: ${queue.issue_ids.join(', ')}`));
907
+ console.log(chalk.gray(`Total: ${queue._metadata.total_tasks} | Pending: ${queue._metadata.pending_count} | Executing: ${queue._metadata.executing_count} | Completed: ${queue._metadata.completed_count}`));
908
+ console.log();
909
+
910
+ console.log(chalk.gray('QueueID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
911
+ console.log(chalk.gray('-'.repeat(60)));
912
+
913
+ for (const item of queue.queue) {
914
+ const statusColor = {
915
+ 'pending': chalk.gray,
916
+ 'ready': chalk.cyan,
917
+ 'executing': chalk.yellow,
918
+ 'completed': chalk.green,
919
+ 'failed': chalk.red,
920
+ 'blocked': chalk.magenta
921
+ }[item.status] || chalk.white;
922
+
923
+ console.log(
924
+ item.queue_id.padEnd(10) +
925
+ item.issue_id.substring(0, 13).padEnd(15) +
926
+ item.task_id.padEnd(8) +
927
+ statusColor(item.status.padEnd(12)) +
928
+ item.assigned_executor
929
+ );
930
+ }
931
+ }
932
+
933
+ /**
934
+ * next - Get next ready task for execution (JSON output)
935
+ */
936
+ async function nextAction(options: IssueOptions): Promise<void> {
937
+ const queue = readActiveQueue();
938
+
939
+ // Find ready tasks
940
+ const readyTasks = queue.queue.filter(item => {
941
+ if (item.status !== 'pending') return false;
942
+ return item.depends_on.every(depId => {
943
+ const dep = queue.queue.find(q => q.queue_id === depId);
944
+ return !dep || dep.status === 'completed';
945
+ });
946
+ });
947
+
948
+ if (readyTasks.length === 0) {
949
+ console.log(JSON.stringify({
950
+ status: 'empty',
951
+ message: 'No ready tasks',
952
+ queue_status: queue._metadata
953
+ }, null, 2));
954
+ return;
955
+ }
956
+
957
+ // Sort by execution order
958
+ readyTasks.sort((a, b) => a.execution_order - b.execution_order);
959
+ const nextItem = readyTasks[0];
960
+
961
+ // Load task definition
962
+ const solution = findSolution(nextItem.issue_id, nextItem.solution_id);
963
+ const taskDef = solution?.tasks.find(t => t.id === nextItem.task_id);
964
+
965
+ if (!taskDef) {
966
+ console.log(JSON.stringify({ status: 'error', message: 'Task definition not found' }));
967
+ process.exit(1);
968
+ }
969
+
970
+ // Mark as executing
971
+ const idx = queue.queue.findIndex(q => q.queue_id === nextItem.queue_id);
972
+ queue.queue[idx].status = 'executing';
973
+ queue.queue[idx].started_at = new Date().toISOString();
974
+ writeQueue(queue);
975
+
976
+ // Update issue status
977
+ updateIssue(nextItem.issue_id, { status: 'executing' });
978
+
979
+ console.log(JSON.stringify({
980
+ queue_id: nextItem.queue_id,
981
+ issue_id: nextItem.issue_id,
982
+ solution_id: nextItem.solution_id,
983
+ task: taskDef,
984
+ context: solution?.exploration_context || {},
985
+ execution_hints: {
986
+ executor: nextItem.assigned_executor,
987
+ estimated_minutes: taskDef.estimated_minutes || 30
988
+ }
989
+ }, null, 2));
990
+ }
991
+
992
+ /**
993
+ * done - Mark task completed or failed
994
+ */
995
+ async function doneAction(queueId: string | undefined, options: IssueOptions): Promise<void> {
996
+ if (!queueId) {
997
+ console.error(chalk.red('Queue ID is required'));
998
+ console.error(chalk.gray('Usage: ccw issue done <queue-id> [--fail] [--reason "..."]'));
999
+ process.exit(1);
1000
+ }
1001
+
1002
+ const queue = readActiveQueue();
1003
+ const idx = queue.queue.findIndex(q => q.queue_id === queueId);
1004
+
1005
+ if (idx === -1) {
1006
+ console.error(chalk.red(`Queue item "${queueId}" not found`));
1007
+ process.exit(1);
1008
+ }
1009
+
1010
+ const isFail = options.fail;
1011
+ queue.queue[idx].status = isFail ? 'failed' : 'completed';
1012
+ queue.queue[idx].completed_at = new Date().toISOString();
1013
+
1014
+ if (isFail) {
1015
+ queue.queue[idx].failure_reason = options.reason || 'Unknown failure';
1016
+ } else if (options.result) {
1017
+ try {
1018
+ queue.queue[idx].result = JSON.parse(options.result);
1019
+ } catch {
1020
+ console.warn(chalk.yellow('Warning: Could not parse result JSON'));
1021
+ }
1022
+ }
1023
+
1024
+ // Check if all issue tasks are complete
1025
+ const issueId = queue.queue[idx].issue_id;
1026
+ const issueTasks = queue.queue.filter(q => q.issue_id === issueId);
1027
+ const allIssueComplete = issueTasks.every(q => q.status === 'completed');
1028
+ const anyIssueFailed = issueTasks.some(q => q.status === 'failed');
1029
+
1030
+ if (allIssueComplete) {
1031
+ updateIssue(issueId, { status: 'completed', completed_at: new Date().toISOString() });
1032
+ console.log(chalk.green(`✓ ${queueId} completed`));
1033
+ console.log(chalk.green(`✓ Issue ${issueId} completed (all tasks done)`));
1034
+ } else if (anyIssueFailed) {
1035
+ updateIssue(issueId, { status: 'failed' });
1036
+ console.log(chalk.red(`✗ ${queueId} failed`));
1037
+ } else {
1038
+ console.log(isFail ? chalk.red(`✗ ${queueId} failed`) : chalk.green(`✓ ${queueId} completed`));
1039
+ }
1040
+
1041
+ // Check if entire queue is complete
1042
+ const allQueueComplete = queue.queue.every(q => q.status === 'completed');
1043
+ const anyQueueFailed = queue.queue.some(q => q.status === 'failed');
1044
+
1045
+ if (allQueueComplete) {
1046
+ queue.status = 'completed';
1047
+ console.log(chalk.green(`\n✓ Queue ${queue.id} completed (all tasks done)`));
1048
+ } else if (anyQueueFailed && queue.queue.every(q => q.status === 'completed' || q.status === 'failed')) {
1049
+ queue.status = 'failed';
1050
+ console.log(chalk.yellow(`\n⚠ Queue ${queue.id} has failed tasks`));
1051
+ }
1052
+
1053
+ writeQueue(queue);
1054
+ }
1055
+
1056
+ /**
1057
+ * retry - Retry failed tasks
1058
+ */
1059
+ async function retryAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
1060
+ const queue = readActiveQueue();
1061
+
1062
+ if (!queue.id || queue.queue.length === 0) {
1063
+ console.log(chalk.yellow('No active queue'));
1064
+ return;
1065
+ }
1066
+
1067
+ let updated = 0;
1068
+
1069
+ for (const item of queue.queue) {
1070
+ if (item.status === 'failed') {
1071
+ if (!issueId || item.issue_id === issueId) {
1072
+ item.status = 'pending';
1073
+ item.failure_reason = undefined;
1074
+ item.started_at = undefined;
1075
+ item.completed_at = undefined;
1076
+ updated++;
1077
+ }
1078
+ }
1079
+ }
1080
+
1081
+ if (updated === 0) {
1082
+ console.log(chalk.yellow('No failed tasks to retry'));
1083
+ return;
1084
+ }
1085
+
1086
+ // Reset queue status if it was failed
1087
+ if (queue.status === 'failed') {
1088
+ queue.status = 'active';
1089
+ }
1090
+
1091
+ writeQueue(queue);
1092
+
1093
+ if (issueId) {
1094
+ updateIssue(issueId, { status: 'queued' });
1095
+ }
1096
+
1097
+ console.log(chalk.green(`✓ Reset ${updated} task(s) to pending`));
1098
+ }
1099
+
1100
+ // ============ Main Entry ============
1101
+
1102
+ export async function issueCommand(
1103
+ subcommand: string,
1104
+ args: string | string[],
1105
+ options: IssueOptions
1106
+ ): Promise<void> {
1107
+ const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
1108
+
1109
+ switch (subcommand) {
1110
+ case 'init':
1111
+ await initAction(argsArray[0], options);
1112
+ break;
1113
+ case 'list':
1114
+ await listAction(argsArray[0], options);
1115
+ break;
1116
+ case 'status':
1117
+ await statusAction(argsArray[0], options);
1118
+ break;
1119
+ case 'task':
1120
+ await taskAction(argsArray[0], argsArray[1], options);
1121
+ break;
1122
+ case 'bind':
1123
+ await bindAction(argsArray[0], argsArray[1], options);
1124
+ break;
1125
+ case 'queue':
1126
+ await queueAction(argsArray[0], argsArray[1], options);
1127
+ break;
1128
+ case 'next':
1129
+ await nextAction(options);
1130
+ break;
1131
+ case 'done':
1132
+ await doneAction(argsArray[0], options);
1133
+ break;
1134
+ case 'retry':
1135
+ await retryAction(argsArray[0], options);
1136
+ break;
1137
+ // Legacy aliases
1138
+ case 'register':
1139
+ console.log(chalk.yellow('Deprecated: use "ccw issue bind <issue-id> --solution <path>"'));
1140
+ await bindAction(argsArray[0], undefined, options);
1141
+ break;
1142
+ case 'complete':
1143
+ await doneAction(argsArray[0], options);
1144
+ break;
1145
+ case 'fail':
1146
+ await doneAction(argsArray[0], { ...options, fail: true });
1147
+ break;
1148
+ default:
1149
+ console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
1150
+ console.log(chalk.bold('Core Commands:'));
1151
+ console.log(chalk.gray(' init <issue-id> Initialize new issue'));
1152
+ console.log(chalk.gray(' list [issue-id] List issues or tasks'));
1153
+ console.log(chalk.gray(' status [issue-id] Show detailed status'));
1154
+ console.log(chalk.gray(' task <issue-id> [task-id] Add or update task'));
1155
+ console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution (--solution <path> to register)'));
1156
+ console.log();
1157
+ console.log(chalk.bold('Queue Commands:'));
1158
+ console.log(chalk.gray(' queue Show active queue'));
1159
+ console.log(chalk.gray(' queue list List all queues (history)'));
1160
+ console.log(chalk.gray(' queue add <issue-id> Add issue to active queue (or create new)'));
1161
+ console.log(chalk.gray(' queue switch <queue-id> Switch active queue'));
1162
+ console.log(chalk.gray(' queue archive Archive current queue'));
1163
+ console.log(chalk.gray(' retry [issue-id] Retry failed tasks'));
1164
+ console.log();
1165
+ console.log(chalk.bold('Execution Endpoints:'));
1166
+ console.log(chalk.gray(' next Get next ready task (JSON)'));
1167
+ console.log(chalk.gray(' done <queue-id> Mark task completed'));
1168
+ console.log(chalk.gray(' done <queue-id> --fail Mark task failed'));
1169
+ console.log();
1170
+ console.log(chalk.bold('Options:'));
1171
+ console.log(chalk.gray(' --title <title> Issue/task title'));
1172
+ console.log(chalk.gray(' --solution <path> Solution JSON file'));
1173
+ console.log(chalk.gray(' --result <json> Execution result'));
1174
+ console.log(chalk.gray(' --reason <text> Failure reason'));
1175
+ console.log(chalk.gray(' --json JSON output'));
1176
+ console.log(chalk.gray(' --force Force operation'));
1177
+ console.log();
1178
+ console.log(chalk.bold('Storage:'));
1179
+ console.log(chalk.gray(' .workflow/issues/issues.jsonl All issues'));
1180
+ console.log(chalk.gray(' .workflow/issues/solutions/*.jsonl Solutions per issue'));
1181
+ console.log(chalk.gray(' .workflow/issues/queues/ Queue files (multi-queue)'));
1182
+ console.log(chalk.gray(' .workflow/issues/queues/index.json Queue index'));
1183
+ }
1184
+ }