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,895 @@
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
+ import chalk from 'chalk';
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
8
+ import { join, resolve } from 'path';
9
+ // Handle EPIPE errors gracefully
10
+ process.stdout.on('error', (err) => {
11
+ if (err.code === 'EPIPE') {
12
+ process.exit(0);
13
+ }
14
+ throw err;
15
+ });
16
+ const ISSUES_DIR = '.workflow/issues';
17
+ // ============ Storage Layer (JSONL) ============
18
+ function getProjectRoot() {
19
+ let dir = process.cwd();
20
+ while (dir !== resolve(dir, '..')) {
21
+ if (existsSync(join(dir, '.workflow')) || existsSync(join(dir, '.git'))) {
22
+ return dir;
23
+ }
24
+ dir = resolve(dir, '..');
25
+ }
26
+ return process.cwd();
27
+ }
28
+ function getIssuesDir() {
29
+ return join(getProjectRoot(), ISSUES_DIR);
30
+ }
31
+ function ensureIssuesDir() {
32
+ const dir = getIssuesDir();
33
+ if (!existsSync(dir)) {
34
+ mkdirSync(dir, { recursive: true });
35
+ }
36
+ }
37
+ // ============ Issues JSONL ============
38
+ function readIssues() {
39
+ const path = join(getIssuesDir(), 'issues.jsonl');
40
+ if (!existsSync(path))
41
+ return [];
42
+ try {
43
+ return readFileSync(path, 'utf-8')
44
+ .split('\n')
45
+ .filter(line => line.trim())
46
+ .map(line => JSON.parse(line));
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ }
52
+ function writeIssues(issues) {
53
+ ensureIssuesDir();
54
+ const path = join(getIssuesDir(), 'issues.jsonl');
55
+ writeFileSync(path, issues.map(i => JSON.stringify(i)).join('\n'), 'utf-8');
56
+ }
57
+ function findIssue(issueId) {
58
+ return readIssues().find(i => i.id === issueId);
59
+ }
60
+ function updateIssue(issueId, updates) {
61
+ const issues = readIssues();
62
+ const idx = issues.findIndex(i => i.id === issueId);
63
+ if (idx === -1)
64
+ return false;
65
+ issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
66
+ writeIssues(issues);
67
+ return true;
68
+ }
69
+ // ============ Solutions JSONL ============
70
+ function getSolutionsPath(issueId) {
71
+ return join(getIssuesDir(), 'solutions', `${issueId}.jsonl`);
72
+ }
73
+ function readSolutions(issueId) {
74
+ const path = getSolutionsPath(issueId);
75
+ if (!existsSync(path))
76
+ return [];
77
+ try {
78
+ return readFileSync(path, 'utf-8')
79
+ .split('\n')
80
+ .filter(line => line.trim())
81
+ .map(line => JSON.parse(line));
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ }
87
+ function writeSolutions(issueId, solutions) {
88
+ const dir = join(getIssuesDir(), 'solutions');
89
+ if (!existsSync(dir))
90
+ mkdirSync(dir, { recursive: true });
91
+ writeFileSync(getSolutionsPath(issueId), solutions.map(s => JSON.stringify(s)).join('\n'), 'utf-8');
92
+ }
93
+ function findSolution(issueId, solutionId) {
94
+ return readSolutions(issueId).find(s => s.id === solutionId);
95
+ }
96
+ function getBoundSolution(issueId) {
97
+ return readSolutions(issueId).find(s => s.is_bound);
98
+ }
99
+ function generateSolutionId() {
100
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
101
+ return `SOL-${ts}`;
102
+ }
103
+ // ============ Queue Management (Multi-Queue) ============
104
+ function getQueuesDir() {
105
+ return join(getIssuesDir(), 'queues');
106
+ }
107
+ function ensureQueuesDir() {
108
+ const dir = getQueuesDir();
109
+ if (!existsSync(dir)) {
110
+ mkdirSync(dir, { recursive: true });
111
+ }
112
+ }
113
+ function readQueueIndex() {
114
+ const path = join(getQueuesDir(), 'index.json');
115
+ if (!existsSync(path)) {
116
+ return { active_queue_id: null, queues: [] };
117
+ }
118
+ return JSON.parse(readFileSync(path, 'utf-8'));
119
+ }
120
+ function writeQueueIndex(index) {
121
+ ensureQueuesDir();
122
+ writeFileSync(join(getQueuesDir(), 'index.json'), JSON.stringify(index, null, 2), 'utf-8');
123
+ }
124
+ function generateQueueFileId() {
125
+ const now = new Date();
126
+ const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
127
+ return `QUE-${ts}`;
128
+ }
129
+ function readQueue(queueId) {
130
+ const index = readQueueIndex();
131
+ const targetId = queueId || index.active_queue_id;
132
+ if (!targetId)
133
+ return null;
134
+ const path = join(getQueuesDir(), `${targetId}.json`);
135
+ if (!existsSync(path))
136
+ return null;
137
+ return JSON.parse(readFileSync(path, 'utf-8'));
138
+ }
139
+ function readActiveQueue() {
140
+ const queue = readQueue();
141
+ if (queue)
142
+ return queue;
143
+ // Return empty queue structure if no active queue
144
+ return createEmptyQueue();
145
+ }
146
+ function createEmptyQueue() {
147
+ return {
148
+ id: generateQueueFileId(),
149
+ status: 'active',
150
+ issue_ids: [],
151
+ queue: [],
152
+ conflicts: [],
153
+ _metadata: {
154
+ version: '2.0',
155
+ total_tasks: 0,
156
+ pending_count: 0,
157
+ executing_count: 0,
158
+ completed_count: 0,
159
+ failed_count: 0,
160
+ created_at: new Date().toISOString(),
161
+ updated_at: new Date().toISOString()
162
+ }
163
+ };
164
+ }
165
+ function writeQueue(queue) {
166
+ ensureQueuesDir();
167
+ // Update metadata counts
168
+ queue._metadata.total_tasks = queue.queue.length;
169
+ queue._metadata.pending_count = queue.queue.filter(q => q.status === 'pending').length;
170
+ queue._metadata.executing_count = queue.queue.filter(q => q.status === 'executing').length;
171
+ queue._metadata.completed_count = queue.queue.filter(q => q.status === 'completed').length;
172
+ queue._metadata.failed_count = queue.queue.filter(q => q.status === 'failed').length;
173
+ queue._metadata.updated_at = new Date().toISOString();
174
+ // Write queue file
175
+ const path = join(getQueuesDir(), `${queue.id}.json`);
176
+ writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
177
+ // Update index
178
+ const index = readQueueIndex();
179
+ const existingIdx = index.queues.findIndex(q => q.id === queue.id);
180
+ const indexEntry = {
181
+ id: queue.id,
182
+ status: queue.status,
183
+ issue_ids: queue.issue_ids,
184
+ total_tasks: queue._metadata.total_tasks,
185
+ completed_tasks: queue._metadata.completed_count,
186
+ created_at: queue._metadata.created_at,
187
+ completed_at: queue.status === 'completed' ? new Date().toISOString() : undefined
188
+ };
189
+ if (existingIdx >= 0) {
190
+ index.queues[existingIdx] = indexEntry;
191
+ }
192
+ else {
193
+ index.queues.unshift(indexEntry);
194
+ }
195
+ if (queue.status === 'active') {
196
+ index.active_queue_id = queue.id;
197
+ }
198
+ writeQueueIndex(index);
199
+ }
200
+ function generateQueueItemId(queue) {
201
+ const maxNum = queue.queue.reduce((max, q) => {
202
+ const match = q.queue_id.match(/^Q-(\d+)$/);
203
+ return match ? Math.max(max, parseInt(match[1])) : max;
204
+ }, 0);
205
+ return `Q-${String(maxNum + 1).padStart(3, '0')}`;
206
+ }
207
+ // ============ Commands ============
208
+ /**
209
+ * init - Initialize a new issue
210
+ */
211
+ async function initAction(issueId, options) {
212
+ if (!issueId) {
213
+ console.error(chalk.red('Issue ID is required'));
214
+ console.error(chalk.gray('Usage: ccw issue init <issue-id> [--title "..."]'));
215
+ process.exit(1);
216
+ }
217
+ const existing = findIssue(issueId);
218
+ if (existing && !options.force) {
219
+ console.error(chalk.red(`Issue "${issueId}" already exists`));
220
+ console.error(chalk.gray('Use --force to reinitialize'));
221
+ process.exit(1);
222
+ }
223
+ const issues = readIssues().filter(i => i.id !== issueId);
224
+ const newIssue = {
225
+ id: issueId,
226
+ title: options.title || issueId,
227
+ status: 'registered',
228
+ priority: options.priority ? parseInt(options.priority) : 3,
229
+ context: options.description || '',
230
+ bound_solution_id: null,
231
+ solution_count: 0,
232
+ created_at: new Date().toISOString(),
233
+ updated_at: new Date().toISOString()
234
+ };
235
+ issues.push(newIssue);
236
+ writeIssues(issues);
237
+ console.log(chalk.green(`✓ Issue "${issueId}" initialized`));
238
+ console.log(chalk.gray(` Next: ccw issue task ${issueId} --title "Task title"`));
239
+ }
240
+ /**
241
+ * list - List issues or tasks
242
+ */
243
+ async function listAction(issueId, options) {
244
+ if (!issueId) {
245
+ // List all issues
246
+ const issues = readIssues();
247
+ if (options.json) {
248
+ console.log(JSON.stringify(issues, null, 2));
249
+ return;
250
+ }
251
+ if (issues.length === 0) {
252
+ console.log(chalk.yellow('No issues found'));
253
+ console.log(chalk.gray('Create one with: ccw issue init <issue-id>'));
254
+ return;
255
+ }
256
+ console.log(chalk.bold.cyan('\nIssues\n'));
257
+ console.log(chalk.gray('ID'.padEnd(20) + 'Status'.padEnd(15) + 'Solutions'.padEnd(12) + 'Title'));
258
+ console.log(chalk.gray('-'.repeat(70)));
259
+ for (const issue of issues) {
260
+ const statusColor = {
261
+ 'registered': chalk.gray,
262
+ 'planning': chalk.blue,
263
+ 'planned': chalk.cyan,
264
+ 'queued': chalk.yellow,
265
+ 'executing': chalk.yellow,
266
+ 'completed': chalk.green,
267
+ 'failed': chalk.red,
268
+ 'paused': chalk.magenta
269
+ }[issue.status] || chalk.white;
270
+ const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${issue.solution_count}`;
271
+ console.log(issue.id.padEnd(20) +
272
+ statusColor(issue.status.padEnd(15)) +
273
+ bound.padEnd(12) +
274
+ (issue.title || '').substring(0, 30));
275
+ }
276
+ return;
277
+ }
278
+ // List tasks in bound solution
279
+ const issue = findIssue(issueId);
280
+ if (!issue) {
281
+ console.error(chalk.red(`Issue "${issueId}" not found`));
282
+ process.exit(1);
283
+ }
284
+ const solution = getBoundSolution(issueId);
285
+ const tasks = solution?.tasks || [];
286
+ if (options.json) {
287
+ console.log(JSON.stringify({ issue, solution, tasks }, null, 2));
288
+ return;
289
+ }
290
+ console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
291
+ console.log(`Title: ${issue.title}`);
292
+ console.log(`Status: ${issue.status}`);
293
+ console.log(`Bound: ${issue.bound_solution_id || 'none'}`);
294
+ console.log();
295
+ if (tasks.length === 0) {
296
+ console.log(chalk.yellow('No tasks (bind a solution first)'));
297
+ return;
298
+ }
299
+ console.log(chalk.gray('ID'.padEnd(8) + 'Action'.padEnd(12) + 'Scope'.padEnd(20) + 'Title'));
300
+ console.log(chalk.gray('-'.repeat(70)));
301
+ for (const task of tasks) {
302
+ console.log(task.id.padEnd(8) +
303
+ task.action.padEnd(12) +
304
+ task.scope.substring(0, 18).padEnd(20) +
305
+ task.title.substring(0, 30));
306
+ }
307
+ }
308
+ /**
309
+ * status - Show detailed status
310
+ */
311
+ async function statusAction(issueId, options) {
312
+ if (!issueId) {
313
+ // Show queue status
314
+ const queue = readActiveQueue();
315
+ const issues = readIssues();
316
+ const index = readQueueIndex();
317
+ if (options.json) {
318
+ console.log(JSON.stringify({ queue: queue._metadata, issues: issues.length, queues: index.queues.length }, null, 2));
319
+ return;
320
+ }
321
+ console.log(chalk.bold.cyan('\nSystem Status\n'));
322
+ console.log(`Issues: ${issues.length}`);
323
+ console.log(`Queues: ${index.queues.length} (Active: ${index.active_queue_id || 'none'})`);
324
+ console.log(`Active Queue: ${queue._metadata.total_tasks} tasks`);
325
+ console.log(` Pending: ${queue._metadata.pending_count}`);
326
+ console.log(` Executing: ${queue._metadata.executing_count}`);
327
+ console.log(` Completed: ${queue._metadata.completed_count}`);
328
+ console.log(` Failed: ${queue._metadata.failed_count}`);
329
+ return;
330
+ }
331
+ const issue = findIssue(issueId);
332
+ if (!issue) {
333
+ console.error(chalk.red(`Issue "${issueId}" not found`));
334
+ process.exit(1);
335
+ }
336
+ const solutions = readSolutions(issueId);
337
+ const boundSol = solutions.find(s => s.is_bound);
338
+ if (options.json) {
339
+ console.log(JSON.stringify({ issue, solutions, bound: boundSol }, null, 2));
340
+ return;
341
+ }
342
+ console.log(chalk.bold.cyan(`\nIssue: ${issueId}\n`));
343
+ console.log(`Title: ${issue.title}`);
344
+ console.log(`Status: ${issue.status}`);
345
+ console.log(`Priority: ${issue.priority}`);
346
+ console.log(`Created: ${issue.created_at}`);
347
+ console.log(`Updated: ${issue.updated_at}`);
348
+ if (issue.context) {
349
+ console.log();
350
+ console.log(chalk.bold('Context:'));
351
+ console.log(issue.context.substring(0, 200));
352
+ }
353
+ console.log();
354
+ console.log(chalk.bold(`Solutions (${solutions.length}):`));
355
+ for (const sol of solutions) {
356
+ const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
357
+ console.log(` ${marker} ${sol.id}: ${sol.tasks.length} tasks`);
358
+ }
359
+ }
360
+ /**
361
+ * task - Add or update task (simplified - mainly for manual task management)
362
+ */
363
+ async function taskAction(issueId, taskId, options) {
364
+ if (!issueId) {
365
+ console.error(chalk.red('Issue ID is required'));
366
+ console.error(chalk.gray('Usage: ccw issue task <issue-id> [task-id] --title "..."'));
367
+ process.exit(1);
368
+ }
369
+ const issue = findIssue(issueId);
370
+ if (!issue) {
371
+ console.error(chalk.red(`Issue "${issueId}" not found`));
372
+ process.exit(1);
373
+ }
374
+ const solutions = readSolutions(issueId);
375
+ let boundIdx = solutions.findIndex(s => s.is_bound);
376
+ // Create default solution if none bound
377
+ if (boundIdx === -1) {
378
+ const newSol = {
379
+ id: generateSolutionId(),
380
+ description: 'Manual tasks',
381
+ tasks: [],
382
+ is_bound: true,
383
+ created_at: new Date().toISOString(),
384
+ bound_at: new Date().toISOString()
385
+ };
386
+ solutions.push(newSol);
387
+ boundIdx = solutions.length - 1;
388
+ updateIssue(issueId, { bound_solution_id: newSol.id, status: 'planned' });
389
+ }
390
+ const solution = solutions[boundIdx];
391
+ if (taskId) {
392
+ // Update existing task
393
+ const taskIdx = solution.tasks.findIndex(t => t.id === taskId);
394
+ if (taskIdx === -1) {
395
+ console.error(chalk.red(`Task "${taskId}" not found`));
396
+ process.exit(1);
397
+ }
398
+ if (options.title)
399
+ solution.tasks[taskIdx].title = options.title;
400
+ if (options.status)
401
+ solution.tasks[taskIdx].status = options.status;
402
+ if (options.executor)
403
+ solution.tasks[taskIdx].executor = options.executor;
404
+ writeSolutions(issueId, solutions);
405
+ console.log(chalk.green(`✓ Task ${taskId} updated`));
406
+ }
407
+ else {
408
+ // Add new task
409
+ if (!options.title) {
410
+ console.error(chalk.red('Task title is required (--title)'));
411
+ process.exit(1);
412
+ }
413
+ const newTaskId = `T${solution.tasks.length + 1}`;
414
+ const newTask = {
415
+ id: newTaskId,
416
+ title: options.title,
417
+ scope: '',
418
+ action: 'Implement',
419
+ description: options.description || options.title,
420
+ implementation: [],
421
+ test: {
422
+ unit: [],
423
+ commands: ['npm test']
424
+ },
425
+ regression: ['npm test'],
426
+ acceptance: {
427
+ criteria: ['Task completed successfully'],
428
+ verification: ['Manual verification']
429
+ },
430
+ commit: {
431
+ type: 'feat',
432
+ scope: 'core',
433
+ message_template: `feat(core): ${options.title}`
434
+ },
435
+ depends_on: [],
436
+ executor: options.executor || 'auto'
437
+ };
438
+ solution.tasks.push(newTask);
439
+ writeSolutions(issueId, solutions);
440
+ console.log(chalk.green(`✓ Task ${newTaskId} added to ${issueId}`));
441
+ }
442
+ }
443
+ /**
444
+ * bind - Register and/or bind a solution
445
+ */
446
+ async function bindAction(issueId, solutionId, options) {
447
+ if (!issueId) {
448
+ console.error(chalk.red('Issue ID is required'));
449
+ console.error(chalk.gray('Usage: ccw issue bind <issue-id> [solution-id] [--solution <path>]'));
450
+ process.exit(1);
451
+ }
452
+ const issue = findIssue(issueId);
453
+ if (!issue) {
454
+ console.error(chalk.red(`Issue "${issueId}" not found`));
455
+ process.exit(1);
456
+ }
457
+ let solutions = readSolutions(issueId);
458
+ // Register new solution from file if provided
459
+ if (options.solution) {
460
+ try {
461
+ const content = readFileSync(options.solution, 'utf-8');
462
+ const data = JSON.parse(content);
463
+ const newSol = {
464
+ id: solutionId || generateSolutionId(),
465
+ description: data.description || data.approach_name || 'Imported solution',
466
+ tasks: data.tasks || [],
467
+ exploration_context: data.exploration_context,
468
+ analysis: data.analysis,
469
+ score: data.score,
470
+ is_bound: false,
471
+ created_at: new Date().toISOString()
472
+ };
473
+ solutions.push(newSol);
474
+ solutionId = newSol.id;
475
+ console.log(chalk.green(`✓ Solution ${solutionId} registered (${newSol.tasks.length} tasks)`));
476
+ }
477
+ catch (e) {
478
+ console.error(chalk.red(`Failed to read solution file: ${options.solution}`));
479
+ process.exit(1);
480
+ }
481
+ }
482
+ if (!solutionId) {
483
+ // List available solutions
484
+ if (solutions.length === 0) {
485
+ console.log(chalk.yellow('No solutions available'));
486
+ console.log(chalk.gray('Register one: ccw issue bind <issue-id> --solution <path>'));
487
+ return;
488
+ }
489
+ console.log(chalk.bold.cyan(`\nSolutions for ${issueId}:\n`));
490
+ for (const sol of solutions) {
491
+ const marker = sol.is_bound ? chalk.green('◉') : chalk.gray('○');
492
+ console.log(` ${marker} ${sol.id}: ${sol.tasks.length} tasks - ${sol.description || ''}`);
493
+ }
494
+ return;
495
+ }
496
+ // Bind the specified solution
497
+ const solIdx = solutions.findIndex(s => s.id === solutionId);
498
+ if (solIdx === -1) {
499
+ console.error(chalk.red(`Solution "${solutionId}" not found`));
500
+ process.exit(1);
501
+ }
502
+ // Unbind all, bind selected
503
+ solutions = solutions.map(s => ({ ...s, is_bound: false }));
504
+ solutions[solIdx].is_bound = true;
505
+ solutions[solIdx].bound_at = new Date().toISOString();
506
+ writeSolutions(issueId, solutions);
507
+ updateIssue(issueId, {
508
+ bound_solution_id: solutionId,
509
+ solution_count: solutions.length,
510
+ status: 'planned',
511
+ planned_at: new Date().toISOString()
512
+ });
513
+ console.log(chalk.green(`✓ Solution ${solutionId} bound to ${issueId}`));
514
+ }
515
+ /**
516
+ * queue - Queue management (list / add / history)
517
+ */
518
+ async function queueAction(subAction, issueId, options) {
519
+ // List all queues (history)
520
+ if (subAction === 'list' || subAction === 'history') {
521
+ const index = readQueueIndex();
522
+ if (options.json) {
523
+ console.log(JSON.stringify(index, null, 2));
524
+ return;
525
+ }
526
+ console.log(chalk.bold.cyan('\nQueue History\n'));
527
+ console.log(chalk.gray(`Active: ${index.active_queue_id || 'none'}`));
528
+ console.log();
529
+ if (index.queues.length === 0) {
530
+ console.log(chalk.yellow('No queues found'));
531
+ console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
532
+ return;
533
+ }
534
+ console.log(chalk.gray('ID'.padEnd(22) + 'Status'.padEnd(12) + 'Tasks'.padEnd(10) + 'Issues'));
535
+ console.log(chalk.gray('-'.repeat(70)));
536
+ for (const q of index.queues) {
537
+ const statusColor = {
538
+ 'active': chalk.green,
539
+ 'completed': chalk.cyan,
540
+ 'archived': chalk.gray,
541
+ 'failed': chalk.red
542
+ }[q.status] || chalk.white;
543
+ const marker = q.id === index.active_queue_id ? '→ ' : ' ';
544
+ console.log(marker +
545
+ q.id.padEnd(20) +
546
+ statusColor(q.status.padEnd(12)) +
547
+ `${q.completed_tasks}/${q.total_tasks}`.padEnd(10) +
548
+ q.issue_ids.join(', '));
549
+ }
550
+ return;
551
+ }
552
+ // Switch active queue
553
+ if (subAction === 'switch' && issueId) {
554
+ const queueId = issueId; // issueId is actually queue ID here
555
+ const targetQueue = readQueue(queueId);
556
+ if (!targetQueue) {
557
+ console.error(chalk.red(`Queue "${queueId}" not found`));
558
+ process.exit(1);
559
+ }
560
+ const index = readQueueIndex();
561
+ index.active_queue_id = queueId;
562
+ writeQueueIndex(index);
563
+ console.log(chalk.green(`✓ Switched to queue ${queueId}`));
564
+ return;
565
+ }
566
+ // Archive current queue
567
+ if (subAction === 'archive') {
568
+ const queue = readActiveQueue();
569
+ if (!queue.id || queue.queue.length === 0) {
570
+ console.log(chalk.yellow('No active queue to archive'));
571
+ return;
572
+ }
573
+ queue.status = 'archived';
574
+ writeQueue(queue);
575
+ const index = readQueueIndex();
576
+ index.active_queue_id = null;
577
+ writeQueueIndex(index);
578
+ console.log(chalk.green(`✓ Archived queue ${queue.id}`));
579
+ return;
580
+ }
581
+ // Add issue tasks to queue
582
+ if (subAction === 'add' && issueId) {
583
+ const issue = findIssue(issueId);
584
+ if (!issue) {
585
+ console.error(chalk.red(`Issue "${issueId}" not found`));
586
+ process.exit(1);
587
+ }
588
+ const solution = getBoundSolution(issueId);
589
+ if (!solution) {
590
+ console.error(chalk.red(`No bound solution for "${issueId}"`));
591
+ console.error(chalk.gray('First bind a solution: ccw issue bind <issue-id> <solution-id>'));
592
+ process.exit(1);
593
+ }
594
+ // Get or create active queue (create new if current is completed/archived)
595
+ let queue = readActiveQueue();
596
+ const isNewQueue = queue.queue.length === 0 || queue.status !== 'active';
597
+ if (queue.status !== 'active') {
598
+ // Create new queue if current is not active
599
+ queue = createEmptyQueue();
600
+ }
601
+ // Add issue to queue's issue list
602
+ if (!queue.issue_ids.includes(issueId)) {
603
+ queue.issue_ids.push(issueId);
604
+ }
605
+ let added = 0;
606
+ for (const task of solution.tasks) {
607
+ const exists = queue.queue.some(q => q.issue_id === issueId && q.task_id === task.id);
608
+ if (exists)
609
+ continue;
610
+ queue.queue.push({
611
+ queue_id: generateQueueItemId(queue),
612
+ issue_id: issueId,
613
+ solution_id: solution.id,
614
+ task_id: task.id,
615
+ status: 'pending',
616
+ execution_order: queue.queue.length + 1,
617
+ execution_group: 'P1',
618
+ depends_on: task.depends_on.map(dep => {
619
+ const depItem = queue.queue.find(q => q.task_id === dep && q.issue_id === issueId);
620
+ return depItem?.queue_id || dep;
621
+ }),
622
+ semantic_priority: 0.5,
623
+ assigned_executor: task.executor === 'auto' ? 'codex' : task.executor,
624
+ queued_at: new Date().toISOString()
625
+ });
626
+ added++;
627
+ }
628
+ writeQueue(queue);
629
+ updateIssue(issueId, { status: 'queued', queued_at: new Date().toISOString() });
630
+ if (isNewQueue) {
631
+ console.log(chalk.green(`✓ Created queue ${queue.id}`));
632
+ }
633
+ console.log(chalk.green(`✓ Added ${added} tasks from ${solution.id}`));
634
+ return;
635
+ }
636
+ // Show current queue
637
+ const queue = readActiveQueue();
638
+ if (options.json) {
639
+ console.log(JSON.stringify(queue, null, 2));
640
+ return;
641
+ }
642
+ console.log(chalk.bold.cyan('\nActive Queue\n'));
643
+ if (!queue.id || queue.queue.length === 0) {
644
+ console.log(chalk.yellow('No active queue'));
645
+ console.log(chalk.gray('Create one: ccw issue queue add <issue-id>'));
646
+ console.log(chalk.gray('Or list history: ccw issue queue list'));
647
+ return;
648
+ }
649
+ console.log(chalk.gray(`Queue: ${queue.id}`));
650
+ console.log(chalk.gray(`Issues: ${queue.issue_ids.join(', ')}`));
651
+ console.log(chalk.gray(`Total: ${queue._metadata.total_tasks} | Pending: ${queue._metadata.pending_count} | Executing: ${queue._metadata.executing_count} | Completed: ${queue._metadata.completed_count}`));
652
+ console.log();
653
+ console.log(chalk.gray('QueueID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
654
+ console.log(chalk.gray('-'.repeat(60)));
655
+ for (const item of queue.queue) {
656
+ const statusColor = {
657
+ 'pending': chalk.gray,
658
+ 'ready': chalk.cyan,
659
+ 'executing': chalk.yellow,
660
+ 'completed': chalk.green,
661
+ 'failed': chalk.red,
662
+ 'blocked': chalk.magenta
663
+ }[item.status] || chalk.white;
664
+ console.log(item.queue_id.padEnd(10) +
665
+ item.issue_id.substring(0, 13).padEnd(15) +
666
+ item.task_id.padEnd(8) +
667
+ statusColor(item.status.padEnd(12)) +
668
+ item.assigned_executor);
669
+ }
670
+ }
671
+ /**
672
+ * next - Get next ready task for execution (JSON output)
673
+ */
674
+ async function nextAction(options) {
675
+ const queue = readActiveQueue();
676
+ // Find ready tasks
677
+ const readyTasks = queue.queue.filter(item => {
678
+ if (item.status !== 'pending')
679
+ return false;
680
+ return item.depends_on.every(depId => {
681
+ const dep = queue.queue.find(q => q.queue_id === depId);
682
+ return !dep || dep.status === 'completed';
683
+ });
684
+ });
685
+ if (readyTasks.length === 0) {
686
+ console.log(JSON.stringify({
687
+ status: 'empty',
688
+ message: 'No ready tasks',
689
+ queue_status: queue._metadata
690
+ }, null, 2));
691
+ return;
692
+ }
693
+ // Sort by execution order
694
+ readyTasks.sort((a, b) => a.execution_order - b.execution_order);
695
+ const nextItem = readyTasks[0];
696
+ // Load task definition
697
+ const solution = findSolution(nextItem.issue_id, nextItem.solution_id);
698
+ const taskDef = solution?.tasks.find(t => t.id === nextItem.task_id);
699
+ if (!taskDef) {
700
+ console.log(JSON.stringify({ status: 'error', message: 'Task definition not found' }));
701
+ process.exit(1);
702
+ }
703
+ // Mark as executing
704
+ const idx = queue.queue.findIndex(q => q.queue_id === nextItem.queue_id);
705
+ queue.queue[idx].status = 'executing';
706
+ queue.queue[idx].started_at = new Date().toISOString();
707
+ writeQueue(queue);
708
+ // Update issue status
709
+ updateIssue(nextItem.issue_id, { status: 'executing' });
710
+ console.log(JSON.stringify({
711
+ queue_id: nextItem.queue_id,
712
+ issue_id: nextItem.issue_id,
713
+ solution_id: nextItem.solution_id,
714
+ task: taskDef,
715
+ context: solution?.exploration_context || {},
716
+ execution_hints: {
717
+ executor: nextItem.assigned_executor,
718
+ estimated_minutes: taskDef.estimated_minutes || 30
719
+ }
720
+ }, null, 2));
721
+ }
722
+ /**
723
+ * done - Mark task completed or failed
724
+ */
725
+ async function doneAction(queueId, options) {
726
+ if (!queueId) {
727
+ console.error(chalk.red('Queue ID is required'));
728
+ console.error(chalk.gray('Usage: ccw issue done <queue-id> [--fail] [--reason "..."]'));
729
+ process.exit(1);
730
+ }
731
+ const queue = readActiveQueue();
732
+ const idx = queue.queue.findIndex(q => q.queue_id === queueId);
733
+ if (idx === -1) {
734
+ console.error(chalk.red(`Queue item "${queueId}" not found`));
735
+ process.exit(1);
736
+ }
737
+ const isFail = options.fail;
738
+ queue.queue[idx].status = isFail ? 'failed' : 'completed';
739
+ queue.queue[idx].completed_at = new Date().toISOString();
740
+ if (isFail) {
741
+ queue.queue[idx].failure_reason = options.reason || 'Unknown failure';
742
+ }
743
+ else if (options.result) {
744
+ try {
745
+ queue.queue[idx].result = JSON.parse(options.result);
746
+ }
747
+ catch {
748
+ console.warn(chalk.yellow('Warning: Could not parse result JSON'));
749
+ }
750
+ }
751
+ // Check if all issue tasks are complete
752
+ const issueId = queue.queue[idx].issue_id;
753
+ const issueTasks = queue.queue.filter(q => q.issue_id === issueId);
754
+ const allIssueComplete = issueTasks.every(q => q.status === 'completed');
755
+ const anyIssueFailed = issueTasks.some(q => q.status === 'failed');
756
+ if (allIssueComplete) {
757
+ updateIssue(issueId, { status: 'completed', completed_at: new Date().toISOString() });
758
+ console.log(chalk.green(`✓ ${queueId} completed`));
759
+ console.log(chalk.green(`✓ Issue ${issueId} completed (all tasks done)`));
760
+ }
761
+ else if (anyIssueFailed) {
762
+ updateIssue(issueId, { status: 'failed' });
763
+ console.log(chalk.red(`✗ ${queueId} failed`));
764
+ }
765
+ else {
766
+ console.log(isFail ? chalk.red(`✗ ${queueId} failed`) : chalk.green(`✓ ${queueId} completed`));
767
+ }
768
+ // Check if entire queue is complete
769
+ const allQueueComplete = queue.queue.every(q => q.status === 'completed');
770
+ const anyQueueFailed = queue.queue.some(q => q.status === 'failed');
771
+ if (allQueueComplete) {
772
+ queue.status = 'completed';
773
+ console.log(chalk.green(`\n✓ Queue ${queue.id} completed (all tasks done)`));
774
+ }
775
+ else if (anyQueueFailed && queue.queue.every(q => q.status === 'completed' || q.status === 'failed')) {
776
+ queue.status = 'failed';
777
+ console.log(chalk.yellow(`\n⚠ Queue ${queue.id} has failed tasks`));
778
+ }
779
+ writeQueue(queue);
780
+ }
781
+ /**
782
+ * retry - Retry failed tasks
783
+ */
784
+ async function retryAction(issueId, options) {
785
+ const queue = readActiveQueue();
786
+ if (!queue.id || queue.queue.length === 0) {
787
+ console.log(chalk.yellow('No active queue'));
788
+ return;
789
+ }
790
+ let updated = 0;
791
+ for (const item of queue.queue) {
792
+ if (item.status === 'failed') {
793
+ if (!issueId || item.issue_id === issueId) {
794
+ item.status = 'pending';
795
+ item.failure_reason = undefined;
796
+ item.started_at = undefined;
797
+ item.completed_at = undefined;
798
+ updated++;
799
+ }
800
+ }
801
+ }
802
+ if (updated === 0) {
803
+ console.log(chalk.yellow('No failed tasks to retry'));
804
+ return;
805
+ }
806
+ // Reset queue status if it was failed
807
+ if (queue.status === 'failed') {
808
+ queue.status = 'active';
809
+ }
810
+ writeQueue(queue);
811
+ if (issueId) {
812
+ updateIssue(issueId, { status: 'queued' });
813
+ }
814
+ console.log(chalk.green(`✓ Reset ${updated} task(s) to pending`));
815
+ }
816
+ // ============ Main Entry ============
817
+ export async function issueCommand(subcommand, args, options) {
818
+ const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
819
+ switch (subcommand) {
820
+ case 'init':
821
+ await initAction(argsArray[0], options);
822
+ break;
823
+ case 'list':
824
+ await listAction(argsArray[0], options);
825
+ break;
826
+ case 'status':
827
+ await statusAction(argsArray[0], options);
828
+ break;
829
+ case 'task':
830
+ await taskAction(argsArray[0], argsArray[1], options);
831
+ break;
832
+ case 'bind':
833
+ await bindAction(argsArray[0], argsArray[1], options);
834
+ break;
835
+ case 'queue':
836
+ await queueAction(argsArray[0], argsArray[1], options);
837
+ break;
838
+ case 'next':
839
+ await nextAction(options);
840
+ break;
841
+ case 'done':
842
+ await doneAction(argsArray[0], options);
843
+ break;
844
+ case 'retry':
845
+ await retryAction(argsArray[0], options);
846
+ break;
847
+ // Legacy aliases
848
+ case 'register':
849
+ console.log(chalk.yellow('Deprecated: use "ccw issue bind <issue-id> --solution <path>"'));
850
+ await bindAction(argsArray[0], undefined, options);
851
+ break;
852
+ case 'complete':
853
+ await doneAction(argsArray[0], options);
854
+ break;
855
+ case 'fail':
856
+ await doneAction(argsArray[0], { ...options, fail: true });
857
+ break;
858
+ default:
859
+ console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
860
+ console.log(chalk.bold('Core Commands:'));
861
+ console.log(chalk.gray(' init <issue-id> Initialize new issue'));
862
+ console.log(chalk.gray(' list [issue-id] List issues or tasks'));
863
+ console.log(chalk.gray(' status [issue-id] Show detailed status'));
864
+ console.log(chalk.gray(' task <issue-id> [task-id] Add or update task'));
865
+ console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution (--solution <path> to register)'));
866
+ console.log();
867
+ console.log(chalk.bold('Queue Commands:'));
868
+ console.log(chalk.gray(' queue Show active queue'));
869
+ console.log(chalk.gray(' queue list List all queues (history)'));
870
+ console.log(chalk.gray(' queue add <issue-id> Add issue to active queue (or create new)'));
871
+ console.log(chalk.gray(' queue switch <queue-id> Switch active queue'));
872
+ console.log(chalk.gray(' queue archive Archive current queue'));
873
+ console.log(chalk.gray(' retry [issue-id] Retry failed tasks'));
874
+ console.log();
875
+ console.log(chalk.bold('Execution Endpoints:'));
876
+ console.log(chalk.gray(' next Get next ready task (JSON)'));
877
+ console.log(chalk.gray(' done <queue-id> Mark task completed'));
878
+ console.log(chalk.gray(' done <queue-id> --fail Mark task failed'));
879
+ console.log();
880
+ console.log(chalk.bold('Options:'));
881
+ console.log(chalk.gray(' --title <title> Issue/task title'));
882
+ console.log(chalk.gray(' --solution <path> Solution JSON file'));
883
+ console.log(chalk.gray(' --result <json> Execution result'));
884
+ console.log(chalk.gray(' --reason <text> Failure reason'));
885
+ console.log(chalk.gray(' --json JSON output'));
886
+ console.log(chalk.gray(' --force Force operation'));
887
+ console.log();
888
+ console.log(chalk.bold('Storage:'));
889
+ console.log(chalk.gray(' .workflow/issues/issues.jsonl All issues'));
890
+ console.log(chalk.gray(' .workflow/issues/solutions/*.jsonl Solutions per issue'));
891
+ console.log(chalk.gray(' .workflow/issues/queues/ Queue files (multi-queue)'));
892
+ console.log(chalk.gray(' .workflow/issues/queues/index.json Queue index'));
893
+ }
894
+ }
895
+ //# sourceMappingURL=issue.js.map