claude-code-workflow 6.3.13 → 6.3.15

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 (69) hide show
  1. package/.claude/agents/issue-plan-agent.md +57 -103
  2. package/.claude/agents/issue-queue-agent.md +69 -120
  3. package/.claude/commands/issue/new.md +217 -473
  4. package/.claude/commands/issue/plan.md +76 -154
  5. package/.claude/commands/issue/queue.md +208 -259
  6. package/.claude/skills/issue-manage/SKILL.md +63 -22
  7. package/.claude/workflows/cli-templates/schemas/discovery-finding-schema.json +3 -3
  8. package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +3 -3
  9. package/.claude/workflows/cli-templates/schemas/queue-schema.json +0 -5
  10. package/.codex/prompts/issue-plan.md +16 -19
  11. package/.codex/prompts/issue-queue.md +0 -1
  12. package/README.md +1 -0
  13. package/ccw/dist/cli.d.ts.map +1 -1
  14. package/ccw/dist/cli.js +3 -1
  15. package/ccw/dist/cli.js.map +1 -1
  16. package/ccw/dist/commands/cli.d.ts.map +1 -1
  17. package/ccw/dist/commands/cli.js +45 -3
  18. package/ccw/dist/commands/cli.js.map +1 -1
  19. package/ccw/dist/commands/issue.d.ts +3 -1
  20. package/ccw/dist/commands/issue.d.ts.map +1 -1
  21. package/ccw/dist/commands/issue.js +383 -30
  22. package/ccw/dist/commands/issue.js.map +1 -1
  23. package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
  24. package/ccw/dist/core/routes/issue-routes.js +77 -16
  25. package/ccw/dist/core/routes/issue-routes.js.map +1 -1
  26. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  27. package/ccw/dist/tools/cli-executor.js +117 -4
  28. package/ccw/dist/tools/cli-executor.js.map +1 -1
  29. package/ccw/dist/tools/litellm-executor.d.ts +4 -0
  30. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
  31. package/ccw/dist/tools/litellm-executor.js +54 -1
  32. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  33. package/ccw/dist/tools/ui-generate-preview.d.ts +18 -0
  34. package/ccw/dist/tools/ui-generate-preview.d.ts.map +1 -1
  35. package/ccw/dist/tools/ui-generate-preview.js +26 -10
  36. package/ccw/dist/tools/ui-generate-preview.js.map +1 -1
  37. package/ccw/src/cli.ts +3 -1
  38. package/ccw/src/commands/cli.ts +47 -3
  39. package/ccw/src/commands/issue.ts +442 -34
  40. package/ccw/src/core/routes/issue-routes.ts +82 -16
  41. package/ccw/src/tools/cli-executor.ts +125 -4
  42. package/ccw/src/tools/litellm-executor.ts +107 -24
  43. package/ccw/src/tools/ui-generate-preview.js +60 -37
  44. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  45. package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
  46. package/codex-lens/src/codexlens/config.py +25 -2
  47. package/codex-lens/src/codexlens/entities.py +5 -1
  48. package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
  49. package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -243
  50. package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
  51. package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
  52. package/codex-lens/src/codexlens/parsers/factory.py +256 -256
  53. package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -335
  54. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  55. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  56. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  57. package/codex-lens/src/codexlens/search/chain_search.py +30 -1
  58. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  59. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  60. package/codex-lens/src/codexlens/semantic/__pycache__/reranker.cpython-313.pyc +0 -0
  61. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  62. package/codex-lens/src/codexlens/semantic/embedder.py +6 -9
  63. package/codex-lens/src/codexlens/semantic/vector_store.py +271 -200
  64. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  65. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  66. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
  67. package/codex-lens/src/codexlens/storage/sqlite_store.py +184 -108
  68. package/package.json +6 -1
  69. package/.claude/commands/issue/manage.md +0 -113
@@ -1 +1 @@
1
- {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/commands/issue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiKH,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAo0CD,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CA0Ff"}
1
+ {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/commands/issue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoLH,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC9B;AA0rDD,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAuGf"}
@@ -52,11 +52,61 @@ function readIssues() {
52
52
  function writeIssues(issues) {
53
53
  ensureIssuesDir();
54
54
  const path = join(getIssuesDir(), 'issues.jsonl');
55
- writeFileSync(path, issues.map(i => JSON.stringify(i)).join('\n'), 'utf-8');
55
+ // Always add trailing newline for proper JSONL format
56
+ const content = issues.map(i => JSON.stringify(i)).join('\n');
57
+ writeFileSync(path, content ? content + '\n' : '', 'utf-8');
56
58
  }
57
59
  function findIssue(issueId) {
58
60
  return readIssues().find(i => i.id === issueId);
59
61
  }
62
+ // ============ Issue History JSONL ============
63
+ function readIssueHistory() {
64
+ const path = join(getIssuesDir(), 'issue-history.jsonl');
65
+ if (!existsSync(path))
66
+ return [];
67
+ try {
68
+ return readFileSync(path, 'utf-8')
69
+ .split('\n')
70
+ .filter(line => line.trim())
71
+ .map(line => JSON.parse(line));
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ function appendIssueHistory(issue) {
78
+ ensureIssuesDir();
79
+ const path = join(getIssuesDir(), 'issue-history.jsonl');
80
+ const line = JSON.stringify(issue) + '\n';
81
+ // Append to history file
82
+ if (existsSync(path)) {
83
+ const content = readFileSync(path, 'utf-8');
84
+ // Ensure proper newline before appending
85
+ const needsNewline = content.length > 0 && !content.endsWith('\n');
86
+ writeFileSync(path, (needsNewline ? '\n' : '') + line, { flag: 'a' });
87
+ }
88
+ else {
89
+ writeFileSync(path, line, 'utf-8');
90
+ }
91
+ }
92
+ /**
93
+ * Move completed issue from issues.jsonl to issue-history.jsonl
94
+ */
95
+ function moveIssueToHistory(issueId) {
96
+ const issues = readIssues();
97
+ const idx = issues.findIndex(i => i.id === issueId);
98
+ if (idx === -1)
99
+ return false;
100
+ const issue = issues[idx];
101
+ if (issue.status !== 'completed')
102
+ return false;
103
+ // Append to history
104
+ appendIssueHistory(issue);
105
+ // Remove from active issues
106
+ issues.splice(idx, 1);
107
+ writeIssues(issues);
108
+ return true;
109
+ }
60
110
  function updateIssue(issueId, updates) {
61
111
  const issues = readIssues();
62
112
  const idx = issues.findIndex(i => i.id === issueId);
@@ -64,8 +114,59 @@ function updateIssue(issueId, updates) {
64
114
  return false;
65
115
  issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
66
116
  writeIssues(issues);
117
+ // Auto-move to history when completed
118
+ if (updates.status === 'completed') {
119
+ moveIssueToHistory(issueId);
120
+ }
67
121
  return true;
68
122
  }
123
+ /**
124
+ * Generate auto-increment issue ID: ISS-YYYYMMDD-NNN
125
+ */
126
+ function generateIssueId(existingIssues = []) {
127
+ const today = new Date();
128
+ const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
129
+ const prefix = `ISS-${dateStr}-`;
130
+ const todayPattern = new RegExp(`^ISS-${dateStr}-(\\d{3})$`);
131
+ let maxSeq = 0;
132
+ for (const issue of existingIssues) {
133
+ const match = issue.id.match(todayPattern);
134
+ if (match)
135
+ maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
136
+ }
137
+ return `${prefix}${String(maxSeq + 1).padStart(3, '0')}`;
138
+ }
139
+ /**
140
+ * Create a new issue with proper JSONL handling
141
+ * Auto-generates ID if not provided
142
+ */
143
+ function createIssue(data) {
144
+ const issues = readIssues();
145
+ const issueId = data.id || generateIssueId(issues);
146
+ if (issues.some(i => i.id === issueId)) {
147
+ throw new Error(`Issue "${issueId}" already exists`);
148
+ }
149
+ const newIssue = {
150
+ id: issueId,
151
+ title: data.title || issueId,
152
+ status: data.status || 'registered',
153
+ priority: data.priority || 3,
154
+ context: data.context || '',
155
+ source: data.source,
156
+ source_url: data.source_url,
157
+ tags: data.tags,
158
+ expected_behavior: data.expected_behavior,
159
+ actual_behavior: data.actual_behavior,
160
+ affected_components: data.affected_components,
161
+ feedback: data.feedback,
162
+ bound_solution_id: data.bound_solution_id || null,
163
+ created_at: new Date().toISOString(),
164
+ updated_at: new Date().toISOString()
165
+ };
166
+ issues.push(newIssue);
167
+ writeIssues(issues);
168
+ return newIssue;
169
+ }
69
170
  // ============ Solutions JSONL ============
70
171
  function getSolutionsPath(issueId) {
71
172
  return join(getIssuesDir(), 'solutions', `${issueId}.jsonl`);
@@ -88,7 +189,9 @@ function writeSolutions(issueId, solutions) {
88
189
  const dir = join(getIssuesDir(), 'solutions');
89
190
  if (!existsSync(dir))
90
191
  mkdirSync(dir, { recursive: true });
91
- writeFileSync(getSolutionsPath(issueId), solutions.map(s => JSON.stringify(s)).join('\n'), 'utf-8');
192
+ // Always add trailing newline for proper JSONL format
193
+ const content = solutions.map(s => JSON.stringify(s)).join('\n');
194
+ writeFileSync(getSolutionsPath(issueId), content ? content + '\n' : '', 'utf-8');
92
195
  }
93
196
  function findSolution(issueId, solutionId) {
94
197
  return readSolutions(issueId).find(s => s.id === solutionId);
@@ -96,9 +199,52 @@ function findSolution(issueId, solutionId) {
96
199
  function getBoundSolution(issueId) {
97
200
  return readSolutions(issueId).find(s => s.is_bound);
98
201
  }
99
- function generateSolutionId() {
100
- const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
101
- return `SOL-${ts}`;
202
+ /**
203
+ * Generate solution ID in format: SOL-{issue-id}-{seq}
204
+ * @param issueId - The issue ID to include in the solution ID
205
+ * @param existingSolutions - Existing solutions to calculate next sequence number
206
+ * @returns Solution ID like "SOL-GH-123-1" or "SOL-ISS-20251229-001-2"
207
+ */
208
+ function generateSolutionId(issueId, existingSolutions = []) {
209
+ // Find the highest existing sequence number for this issue
210
+ const pattern = new RegExp(`^SOL-${issueId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)$`);
211
+ let maxSeq = 0;
212
+ for (const sol of existingSolutions) {
213
+ const match = sol.id.match(pattern);
214
+ if (match) {
215
+ maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
216
+ }
217
+ }
218
+ return `SOL-${issueId}-${maxSeq + 1}`;
219
+ }
220
+ /**
221
+ * Create a new solution with proper JSONL handling
222
+ * Auto-generates ID if not provided
223
+ */
224
+ function createSolution(issueId, data) {
225
+ const issue = findIssue(issueId);
226
+ if (!issue) {
227
+ throw new Error(`Issue "${issueId}" not found`);
228
+ }
229
+ const solutions = readSolutions(issueId);
230
+ const solutionId = data.id || generateSolutionId(issueId, solutions);
231
+ if (solutions.some(s => s.id === solutionId)) {
232
+ throw new Error(`Solution "${solutionId}" already exists`);
233
+ }
234
+ const newSolution = {
235
+ id: solutionId,
236
+ description: data.description || '',
237
+ approach: data.approach || '',
238
+ tasks: data.tasks || [],
239
+ exploration_context: data.exploration_context,
240
+ analysis: data.analysis,
241
+ score: data.score,
242
+ is_bound: false,
243
+ created_at: new Date().toISOString()
244
+ };
245
+ solutions.push(newSolution);
246
+ writeSolutions(issueId, solutions);
247
+ return newSolution;
102
248
  }
103
249
  // ============ Queue Management (Multi-Queue) ============
104
250
  function getQueuesDir() {
@@ -235,7 +381,54 @@ function generateQueueItemId(queue, level = 'solution') {
235
381
  }
236
382
  // ============ Commands ============
237
383
  /**
238
- * init - Initialize a new issue
384
+ * create - Create issue from JSON data
385
+ * Usage: ccw issue create --data '{"title":"...", "context":"..."}'
386
+ * Output: JSON with created issue (includes auto-generated ID)
387
+ */
388
+ async function createAction(options) {
389
+ if (!options.data) {
390
+ console.error(chalk.red('JSON data required'));
391
+ console.error(chalk.gray('Usage: ccw issue create --data \'{"title":"...", "context":"..."}\''));
392
+ process.exit(1);
393
+ }
394
+ try {
395
+ const data = JSON.parse(options.data);
396
+ const issue = createIssue(data);
397
+ console.log(JSON.stringify(issue, null, 2));
398
+ }
399
+ catch (err) {
400
+ console.error(chalk.red(err.message));
401
+ process.exit(1);
402
+ }
403
+ }
404
+ /**
405
+ * solution - Create solution from JSON data
406
+ * Usage: ccw issue solution <issue-id> --data '{"tasks":[...]}'
407
+ * Output: JSON with created solution (includes auto-generated ID)
408
+ */
409
+ async function solutionAction(issueId, options) {
410
+ if (!issueId) {
411
+ console.error(chalk.red('Issue ID required'));
412
+ console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
413
+ process.exit(1);
414
+ }
415
+ if (!options.data) {
416
+ console.error(chalk.red('JSON data required'));
417
+ console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
418
+ process.exit(1);
419
+ }
420
+ try {
421
+ const data = JSON.parse(options.data);
422
+ const solution = createSolution(issueId, data);
423
+ console.log(JSON.stringify(solution, null, 2));
424
+ }
425
+ catch (err) {
426
+ console.error(chalk.red(err.message));
427
+ process.exit(1);
428
+ }
429
+ }
430
+ /**
431
+ * init - Initialize a new issue (manual ID)
239
432
  */
240
433
  async function initAction(issueId, options) {
241
434
  if (!issueId) {
@@ -257,7 +450,6 @@ async function initAction(issueId, options) {
257
450
  priority: options.priority ? parseInt(options.priority) : 3,
258
451
  context: options.description || '',
259
452
  bound_solution_id: null,
260
- solution_count: 0,
261
453
  created_at: new Date().toISOString(),
262
454
  updated_at: new Date().toISOString()
263
455
  };
@@ -278,9 +470,17 @@ async function listAction(issueId, options) {
278
470
  const statuses = options.status.split(',').map(s => s.trim());
279
471
  issues = issues.filter(i => statuses.includes(i.status));
280
472
  }
281
- // IDs only mode (one per line, for scripting)
282
- if (options.ids) {
283
- issues.forEach(i => console.log(i.id));
473
+ // Brief mode: minimal fields only (id, title, status, priority, tags, bound_solution_id)
474
+ if (options.brief) {
475
+ const briefIssues = issues.map(i => ({
476
+ id: i.id,
477
+ title: i.title,
478
+ status: i.status,
479
+ priority: i.priority,
480
+ tags: i.tags || [],
481
+ bound_solution_id: i.bound_solution_id
482
+ }));
483
+ console.log(JSON.stringify(briefIssues, null, 2));
284
484
  return;
285
485
  }
286
486
  if (options.json) {
@@ -306,7 +506,8 @@ async function listAction(issueId, options) {
306
506
  'failed': chalk.red,
307
507
  'paused': chalk.magenta
308
508
  }[issue.status] || chalk.white;
309
- const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${issue.solution_count}`;
509
+ const solutionCount = readSolutions(issue.id).length;
510
+ const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${solutionCount}`;
310
511
  console.log(issue.id.padEnd(20) +
311
512
  statusColor(issue.status.padEnd(15)) +
312
513
  bound.padEnd(12) +
@@ -344,6 +545,43 @@ async function listAction(issueId, options) {
344
545
  task.title.substring(0, 30));
345
546
  }
346
547
  }
548
+ /**
549
+ * history - List completed issues from history
550
+ */
551
+ async function historyAction(options) {
552
+ const history = readIssueHistory();
553
+ // Brief mode: minimal fields only
554
+ if (options.brief) {
555
+ const briefHistory = history.map(i => ({
556
+ id: i.id,
557
+ title: i.title,
558
+ status: i.status,
559
+ completed_at: i.completed_at
560
+ }));
561
+ console.log(JSON.stringify(briefHistory, null, 2));
562
+ return;
563
+ }
564
+ if (options.json) {
565
+ console.log(JSON.stringify(history, null, 2));
566
+ return;
567
+ }
568
+ if (history.length === 0) {
569
+ console.log(chalk.yellow('No completed issues in history'));
570
+ return;
571
+ }
572
+ console.log(chalk.bold.cyan('\nIssue History (Completed)\n'));
573
+ console.log(chalk.gray('ID'.padEnd(25) + 'Completed At'.padEnd(22) + 'Title'));
574
+ console.log(chalk.gray('-'.repeat(80)));
575
+ for (const issue of history) {
576
+ const completedAt = issue.completed_at
577
+ ? new Date(issue.completed_at).toLocaleString()
578
+ : 'N/A';
579
+ console.log(chalk.green(issue.id.padEnd(25)) +
580
+ completedAt.padEnd(22) +
581
+ (issue.title || '').substring(0, 35));
582
+ }
583
+ console.log(chalk.gray(`\nTotal: ${history.length} completed issues`));
584
+ }
347
585
  /**
348
586
  * status - Show detailed status
349
587
  */
@@ -416,7 +654,7 @@ async function taskAction(issueId, taskId, options) {
416
654
  // Create default solution if none bound
417
655
  if (boundIdx === -1) {
418
656
  const newSol = {
419
- id: generateSolutionId(),
657
+ id: generateSolutionId(issueId, solutions),
420
658
  description: 'Manual tasks',
421
659
  tasks: [],
422
660
  is_bound: true,
@@ -479,11 +717,82 @@ async function taskAction(issueId, taskId, options) {
479
717
  }
480
718
  /**
481
719
  * update - Update issue fields (status, priority, title, etc.)
720
+ * --from-queue: Sync statuses from active queue (auto-update queued issues)
482
721
  */
483
722
  async function updateAction(issueId, options) {
723
+ // Handle --from-queue: Sync statuses from queue
724
+ if (options.fromQueue) {
725
+ // Determine queue ID: string value = specific queue, true = active queue
726
+ const queueId = typeof options.fromQueue === 'string' ? options.fromQueue : undefined;
727
+ const queue = queueId ? readQueue(queueId) : readActiveQueue();
728
+ if (!queue) {
729
+ if (options.json) {
730
+ console.log(JSON.stringify({ success: false, message: `Queue not found: ${queueId}`, queued: [], unplanned: [] }));
731
+ }
732
+ else {
733
+ console.log(chalk.red(`Queue not found: ${queueId}`));
734
+ }
735
+ return;
736
+ }
737
+ const items = queue.solutions || queue.tasks || [];
738
+ const allIssues = readIssues();
739
+ if (!queue.id || items.length === 0) {
740
+ if (options.json) {
741
+ console.log(JSON.stringify({ success: false, message: 'No active queue', queued: [], unplanned: [] }));
742
+ }
743
+ else {
744
+ console.log(chalk.yellow('No active queue to sync from'));
745
+ }
746
+ return;
747
+ }
748
+ // Get issue IDs from queue
749
+ const queuedIssueIds = new Set(items.map(item => item.issue_id));
750
+ const now = new Date().toISOString();
751
+ // Track updates
752
+ const updated = [];
753
+ const unplanned = [];
754
+ // Update queued issues
755
+ for (const issueId of queuedIssueIds) {
756
+ const issue = allIssues.find(i => i.id === issueId);
757
+ if (issue && issue.status !== 'queued' && issue.status !== 'executing' && issue.status !== 'completed') {
758
+ updateIssue(issueId, { status: 'queued', queued_at: now });
759
+ updated.push(issueId);
760
+ }
761
+ }
762
+ // Find planned issues NOT in queue
763
+ for (const issue of allIssues) {
764
+ if (issue.status === 'planned' && issue.bound_solution_id && !queuedIssueIds.has(issue.id)) {
765
+ unplanned.push(issue.id);
766
+ }
767
+ }
768
+ if (options.json) {
769
+ console.log(JSON.stringify({
770
+ success: true,
771
+ queue_id: queue.id,
772
+ queued: updated,
773
+ queued_count: updated.length,
774
+ unplanned: unplanned,
775
+ unplanned_count: unplanned.length
776
+ }, null, 2));
777
+ }
778
+ else {
779
+ console.log(chalk.green(`✓ Synced from queue ${queue.id}`));
780
+ console.log(chalk.gray(` Updated to 'queued': ${updated.length} issues`));
781
+ if (updated.length > 0) {
782
+ updated.forEach(id => console.log(chalk.gray(` - ${id}`)));
783
+ }
784
+ if (unplanned.length > 0) {
785
+ console.log(chalk.yellow(` Planned but NOT in queue: ${unplanned.length} issues`));
786
+ unplanned.forEach(id => console.log(chalk.yellow(` - ${id}`)));
787
+ }
788
+ }
789
+ return;
790
+ }
791
+ // Standard single-issue update
484
792
  if (!issueId) {
485
793
  console.error(chalk.red('Issue ID is required'));
486
- console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status> [--priority <n>] [--title "..."]'));
794
+ console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>'));
795
+ console.error(chalk.gray(' ccw issue update --from-queue [queue-id] (sync from queue)'));
487
796
  process.exit(1);
488
797
  }
489
798
  const issue = findIssue(issueId);
@@ -553,8 +862,9 @@ async function bindAction(issueId, solutionId, options) {
553
862
  try {
554
863
  const content = readFileSync(options.solution, 'utf-8');
555
864
  const data = JSON.parse(content);
865
+ // Priority: CLI arg > file content ID > generate new (SOL-{issue-id}-{seq})
556
866
  const newSol = {
557
- id: solutionId || generateSolutionId(),
867
+ id: solutionId || data.id || generateSolutionId(issueId, solutions),
558
868
  description: data.description || data.approach_name || 'Imported solution',
559
869
  tasks: data.tasks || [],
560
870
  exploration_context: data.exploration_context,
@@ -599,7 +909,6 @@ async function bindAction(issueId, solutionId, options) {
599
909
  writeSolutions(issueId, solutions);
600
910
  updateIssue(issueId, {
601
911
  bound_solution_id: solutionId,
602
- solution_count: solutions.length,
603
912
  status: 'planned',
604
913
  planned_at: new Date().toISOString()
605
914
  });
@@ -612,6 +921,21 @@ async function queueAction(subAction, issueId, options) {
612
921
  // List all queues (history)
613
922
  if (subAction === 'list' || subAction === 'history') {
614
923
  const index = readQueueIndex();
924
+ // Brief mode: minimal queue index info
925
+ if (options.brief) {
926
+ const briefIndex = {
927
+ active_queue_id: index.active_queue_id,
928
+ queues: index.queues.map(q => ({
929
+ id: q.id,
930
+ status: q.status,
931
+ issue_ids: q.issue_ids,
932
+ total_solutions: q.total_solutions,
933
+ completed_solutions: q.completed_solutions
934
+ }))
935
+ };
936
+ console.log(JSON.stringify(briefIndex, null, 2));
937
+ return;
938
+ }
615
939
  if (options.json) {
616
940
  console.log(JSON.stringify(index, null, 2));
617
941
  return;
@@ -673,7 +997,6 @@ async function queueAction(subAction, issueId, options) {
673
997
  issue_id: item.issue_id,
674
998
  solution_id: item.solution_id,
675
999
  status: item.status,
676
- executor: item.assigned_executor,
677
1000
  priority: item.semantic_priority,
678
1001
  depends_on: item.depends_on || [],
679
1002
  task_count: item.task_count || 1,
@@ -860,7 +1183,6 @@ async function queueAction(subAction, issueId, options) {
860
1183
  execution_group: 'P1',
861
1184
  depends_on: [],
862
1185
  semantic_priority: 0.5,
863
- assigned_executor: 'codex',
864
1186
  task_count: solution.tasks?.length || 0,
865
1187
  files_touched: Array.from(filesTouched)
866
1188
  });
@@ -874,6 +1196,27 @@ async function queueAction(subAction, issueId, options) {
874
1196
  }
875
1197
  // Show current queue
876
1198
  const queue = readActiveQueue();
1199
+ // Brief mode: minimal queue info (id, issue_ids, item summaries)
1200
+ if (options.brief) {
1201
+ const items = queue.solutions || queue.tasks || [];
1202
+ const briefQueue = {
1203
+ id: queue.id,
1204
+ issue_ids: queue.issue_ids || [],
1205
+ total: items.length,
1206
+ pending: items.filter(i => i.status === 'pending').length,
1207
+ executing: items.filter(i => i.status === 'executing').length,
1208
+ completed: items.filter(i => i.status === 'completed').length,
1209
+ items: items.map(i => ({
1210
+ item_id: i.item_id,
1211
+ issue_id: i.issue_id,
1212
+ solution_id: i.solution_id,
1213
+ status: i.status,
1214
+ task_count: i.task_count
1215
+ }))
1216
+ };
1217
+ console.log(JSON.stringify(briefQueue, null, 2));
1218
+ return;
1219
+ }
877
1220
  if (options.json) {
878
1221
  console.log(JSON.stringify(queue, null, 2));
879
1222
  return;
@@ -893,12 +1236,12 @@ async function queueAction(subAction, issueId, options) {
893
1236
  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}`));
894
1237
  console.log();
895
1238
  if (isSolutionLevel) {
896
- console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
1239
+ console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'));
897
1240
  }
898
1241
  else {
899
- console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
1242
+ console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'));
900
1243
  }
901
- console.log(chalk.gray('-'.repeat(60)));
1244
+ console.log(chalk.gray('-'.repeat(48)));
902
1245
  for (const item of items) {
903
1246
  const statusColor = {
904
1247
  'pending': chalk.gray,
@@ -914,8 +1257,7 @@ async function queueAction(subAction, issueId, options) {
914
1257
  console.log(item.item_id.padEnd(10) +
915
1258
  item.issue_id.substring(0, 13).padEnd(15) +
916
1259
  thirdCol +
917
- statusColor(item.status.padEnd(12)) +
918
- item.assigned_executor);
1260
+ statusColor(item.status));
919
1261
  }
920
1262
  }
921
1263
  /**
@@ -1015,7 +1357,6 @@ async function nextAction(itemId, options) {
1015
1357
  resumed: isResume,
1016
1358
  resume_note: isResume ? `Resuming interrupted item (started: ${nextItem.started_at})` : undefined,
1017
1359
  execution_hints: {
1018
- executor: nextItem.assigned_executor,
1019
1360
  task_count: solution.tasks?.length || 0,
1020
1361
  estimated_minutes: totalMinutes
1021
1362
  },
@@ -1066,7 +1407,6 @@ async function detailAction(itemId, options) {
1066
1407
  exploration_context: solution.exploration_context || {}
1067
1408
  },
1068
1409
  execution_hints: {
1069
- executor: queueItem.assigned_executor,
1070
1410
  task_count: solution.tasks?.length || 0,
1071
1411
  estimated_minutes: totalMinutes
1072
1412
  }
@@ -1183,12 +1523,21 @@ async function retryAction(issueId, options) {
1183
1523
  export async function issueCommand(subcommand, args, options) {
1184
1524
  const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
1185
1525
  switch (subcommand) {
1526
+ case 'create':
1527
+ await createAction(options);
1528
+ break;
1529
+ case 'solution':
1530
+ await solutionAction(argsArray[0], options);
1531
+ break;
1186
1532
  case 'init':
1187
1533
  await initAction(argsArray[0], options);
1188
1534
  break;
1189
1535
  case 'list':
1190
1536
  await listAction(argsArray[0], options);
1191
1537
  break;
1538
+ case 'history':
1539
+ await historyAction(options);
1540
+ break;
1192
1541
  case 'status':
1193
1542
  await statusAction(argsArray[0], options);
1194
1543
  break;
@@ -1230,12 +1579,15 @@ export async function issueCommand(subcommand, args, options) {
1230
1579
  default:
1231
1580
  console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
1232
1581
  console.log(chalk.bold('Core Commands:'));
1233
- console.log(chalk.gray(' init <issue-id> Initialize new issue'));
1582
+ console.log(chalk.gray(' create --data \'{"title":"..."}\' Create issue (auto-generates ID)'));
1583
+ console.log(chalk.gray(' init <issue-id> Initialize new issue (manual ID)'));
1234
1584
  console.log(chalk.gray(' list [issue-id] List issues or tasks'));
1585
+ console.log(chalk.gray(' history List completed issues (from history)'));
1235
1586
  console.log(chalk.gray(' status [issue-id] Show detailed status'));
1236
- console.log(chalk.gray(' task <issue-id> [task-id] Add or update task'));
1237
- console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution (--solution <path> to register)'));
1238
- console.log(chalk.gray(' update <issue-id> Update issue (--status, --priority, --title)'));
1587
+ console.log(chalk.gray(' solution <id> --data \'{...}\' Create solution (auto-generates ID)'));
1588
+ console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution'));
1589
+ console.log(chalk.gray(' update <issue-id> --status <s> Update issue status'));
1590
+ console.log(chalk.gray(' update --from-queue [queue-id] Sync statuses from queue (default: active)'));
1239
1591
  console.log();
1240
1592
  console.log(chalk.bold('Queue Commands:'));
1241
1593
  console.log(chalk.gray(' queue Show active queue'));
@@ -1256,7 +1608,7 @@ export async function issueCommand(subcommand, args, options) {
1256
1608
  console.log(chalk.bold('Options:'));
1257
1609
  console.log(chalk.gray(' --title <title> Issue/task title'));
1258
1610
  console.log(chalk.gray(' --status <status> Filter by status (comma-separated)'));
1259
- console.log(chalk.gray(' --ids List only IDs (one per line)'));
1611
+ console.log(chalk.gray(' --brief Brief JSON output (minimal fields)'));
1260
1612
  console.log(chalk.gray(' --solution <path> Solution JSON file'));
1261
1613
  console.log(chalk.gray(' --result <json> Execution result'));
1262
1614
  console.log(chalk.gray(' --reason <text> Failure reason'));
@@ -1264,7 +1616,8 @@ export async function issueCommand(subcommand, args, options) {
1264
1616
  console.log(chalk.gray(' --force Force operation'));
1265
1617
  console.log();
1266
1618
  console.log(chalk.bold('Storage:'));
1267
- console.log(chalk.gray(' .workflow/issues/issues.jsonl All issues'));
1619
+ console.log(chalk.gray(' .workflow/issues/issues.jsonl Active issues'));
1620
+ console.log(chalk.gray(' .workflow/issues/issue-history.jsonl Completed issues'));
1268
1621
  console.log(chalk.gray(' .workflow/issues/solutions/*.jsonl Solutions per issue'));
1269
1622
  console.log(chalk.gray(' .workflow/issues/queues/ Queue files (multi-queue)'));
1270
1623
  console.log(chalk.gray(' .workflow/issues/queues/index.json Queue index'));