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
@@ -18,14 +18,34 @@ process.stdout.on('error', (err: NodeJS.ErrnoException) => {
18
18
 
19
19
  // ============ Interfaces ============
20
20
 
21
+ interface IssueFeedback {
22
+ type: 'failure' | 'clarification' | 'rejection';
23
+ stage: string; // new/plan/execute
24
+ content: string;
25
+ created_at: string;
26
+ }
27
+
21
28
  interface Issue {
22
29
  id: string;
23
30
  title: string;
24
31
  status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused';
25
32
  priority: number;
26
- context: string;
33
+ context: string; // Problem description (single source of truth)
34
+ source?: 'github' | 'text' | 'discovery';
35
+ source_url?: string;
36
+ tags?: string[];
37
+
38
+ // Optional structured fields
39
+ expected_behavior?: string;
40
+ actual_behavior?: string;
41
+ affected_components?: string[];
42
+
43
+ // Feedback history (failures + human clarifications)
44
+ feedback?: IssueFeedback[];
45
+
46
+ // Solution binding
27
47
  bound_solution_id: string | null;
28
- solution_count: number;
48
+
29
49
  // Timestamps
30
50
  created_at: string;
31
51
  updated_at: string;
@@ -98,7 +118,6 @@ interface QueueItem {
98
118
  execution_group: string;
99
119
  depends_on: string[];
100
120
  semantic_priority: number;
101
- assigned_executor: 'codex' | 'gemini' | 'agent';
102
121
  task_count?: number; // For solution-level queues
103
122
  files_touched?: string[]; // For solution-level queues
104
123
  queued_at?: string;
@@ -175,7 +194,9 @@ interface IssueOptions {
175
194
  json?: boolean;
176
195
  force?: boolean;
177
196
  fail?: boolean;
178
- ids?: boolean; // List only IDs (one per line)
197
+ brief?: boolean; // List brief info only (id, title, status, priority, tags) - JSON format
198
+ data?: string; // JSON data for create
199
+ fromQueue?: boolean | string; // Sync statuses from queue (true=active, string=specific queue ID)
179
200
  }
180
201
 
181
202
  const ISSUES_DIR = '.workflow/issues';
@@ -222,22 +243,132 @@ function readIssues(): Issue[] {
222
243
  function writeIssues(issues: Issue[]): void {
223
244
  ensureIssuesDir();
224
245
  const path = join(getIssuesDir(), 'issues.jsonl');
225
- writeFileSync(path, issues.map(i => JSON.stringify(i)).join('\n'), 'utf-8');
246
+ // Always add trailing newline for proper JSONL format
247
+ const content = issues.map(i => JSON.stringify(i)).join('\n');
248
+ writeFileSync(path, content ? content + '\n' : '', 'utf-8');
226
249
  }
227
250
 
228
251
  function findIssue(issueId: string): Issue | undefined {
229
252
  return readIssues().find(i => i.id === issueId);
230
253
  }
231
254
 
255
+ // ============ Issue History JSONL ============
256
+
257
+ function readIssueHistory(): Issue[] {
258
+ const path = join(getIssuesDir(), 'issue-history.jsonl');
259
+ if (!existsSync(path)) return [];
260
+ try {
261
+ return readFileSync(path, 'utf-8')
262
+ .split('\n')
263
+ .filter(line => line.trim())
264
+ .map(line => JSON.parse(line));
265
+ } catch {
266
+ return [];
267
+ }
268
+ }
269
+
270
+ function appendIssueHistory(issue: Issue): void {
271
+ ensureIssuesDir();
272
+ const path = join(getIssuesDir(), 'issue-history.jsonl');
273
+ const line = JSON.stringify(issue) + '\n';
274
+ // Append to history file
275
+ if (existsSync(path)) {
276
+ const content = readFileSync(path, 'utf-8');
277
+ // Ensure proper newline before appending
278
+ const needsNewline = content.length > 0 && !content.endsWith('\n');
279
+ writeFileSync(path, (needsNewline ? '\n' : '') + line, { flag: 'a' });
280
+ } else {
281
+ writeFileSync(path, line, 'utf-8');
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Move completed issue from issues.jsonl to issue-history.jsonl
287
+ */
288
+ function moveIssueToHistory(issueId: string): boolean {
289
+ const issues = readIssues();
290
+ const idx = issues.findIndex(i => i.id === issueId);
291
+ if (idx === -1) return false;
292
+
293
+ const issue = issues[idx];
294
+ if (issue.status !== 'completed') return false;
295
+
296
+ // Append to history
297
+ appendIssueHistory(issue);
298
+
299
+ // Remove from active issues
300
+ issues.splice(idx, 1);
301
+ writeIssues(issues);
302
+
303
+ return true;
304
+ }
305
+
232
306
  function updateIssue(issueId: string, updates: Partial<Issue>): boolean {
233
307
  const issues = readIssues();
234
308
  const idx = issues.findIndex(i => i.id === issueId);
235
309
  if (idx === -1) return false;
236
310
  issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
237
311
  writeIssues(issues);
312
+
313
+ // Auto-move to history when completed
314
+ if (updates.status === 'completed') {
315
+ moveIssueToHistory(issueId);
316
+ }
317
+
238
318
  return true;
239
319
  }
240
320
 
321
+ /**
322
+ * Generate auto-increment issue ID: ISS-YYYYMMDD-NNN
323
+ */
324
+ function generateIssueId(existingIssues: Issue[] = []): string {
325
+ const today = new Date();
326
+ const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
327
+ const prefix = `ISS-${dateStr}-`;
328
+ const todayPattern = new RegExp(`^ISS-${dateStr}-(\\d{3})$`);
329
+ let maxSeq = 0;
330
+ for (const issue of existingIssues) {
331
+ const match = issue.id.match(todayPattern);
332
+ if (match) maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
333
+ }
334
+ return `${prefix}${String(maxSeq + 1).padStart(3, '0')}`;
335
+ }
336
+
337
+ /**
338
+ * Create a new issue with proper JSONL handling
339
+ * Auto-generates ID if not provided
340
+ */
341
+ function createIssue(data: Partial<Issue>): Issue {
342
+ const issues = readIssues();
343
+ const issueId = data.id || generateIssueId(issues);
344
+
345
+ if (issues.some(i => i.id === issueId)) {
346
+ throw new Error(`Issue "${issueId}" already exists`);
347
+ }
348
+
349
+ const newIssue: Issue = {
350
+ id: issueId,
351
+ title: data.title || issueId,
352
+ status: data.status || 'registered',
353
+ priority: data.priority || 3,
354
+ context: data.context || '',
355
+ source: data.source,
356
+ source_url: data.source_url,
357
+ tags: data.tags,
358
+ expected_behavior: data.expected_behavior,
359
+ actual_behavior: data.actual_behavior,
360
+ affected_components: data.affected_components,
361
+ feedback: data.feedback,
362
+ bound_solution_id: data.bound_solution_id || null,
363
+ created_at: new Date().toISOString(),
364
+ updated_at: new Date().toISOString()
365
+ };
366
+
367
+ issues.push(newIssue);
368
+ writeIssues(issues);
369
+ return newIssue;
370
+ }
371
+
241
372
  // ============ Solutions JSONL ============
242
373
 
243
374
  function getSolutionsPath(issueId: string): string {
@@ -260,7 +391,9 @@ function readSolutions(issueId: string): Solution[] {
260
391
  function writeSolutions(issueId: string, solutions: Solution[]): void {
261
392
  const dir = join(getIssuesDir(), 'solutions');
262
393
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
263
- writeFileSync(getSolutionsPath(issueId), solutions.map(s => JSON.stringify(s)).join('\n'), 'utf-8');
394
+ // Always add trailing newline for proper JSONL format
395
+ const content = solutions.map(s => JSON.stringify(s)).join('\n');
396
+ writeFileSync(getSolutionsPath(issueId), content ? content + '\n' : '', 'utf-8');
264
397
  }
265
398
 
266
399
  function findSolution(issueId: string, solutionId: string): Solution | undefined {
@@ -271,9 +404,57 @@ function getBoundSolution(issueId: string): Solution | undefined {
271
404
  return readSolutions(issueId).find(s => s.is_bound);
272
405
  }
273
406
 
274
- function generateSolutionId(): string {
275
- const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
276
- return `SOL-${ts}`;
407
+ /**
408
+ * Generate solution ID in format: SOL-{issue-id}-{seq}
409
+ * @param issueId - The issue ID to include in the solution ID
410
+ * @param existingSolutions - Existing solutions to calculate next sequence number
411
+ * @returns Solution ID like "SOL-GH-123-1" or "SOL-ISS-20251229-001-2"
412
+ */
413
+ function generateSolutionId(issueId: string, existingSolutions: Solution[] = []): string {
414
+ // Find the highest existing sequence number for this issue
415
+ const pattern = new RegExp(`^SOL-${issueId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)$`);
416
+ let maxSeq = 0;
417
+ for (const sol of existingSolutions) {
418
+ const match = sol.id.match(pattern);
419
+ if (match) {
420
+ maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
421
+ }
422
+ }
423
+ return `SOL-${issueId}-${maxSeq + 1}`;
424
+ }
425
+
426
+ /**
427
+ * Create a new solution with proper JSONL handling
428
+ * Auto-generates ID if not provided
429
+ */
430
+ function createSolution(issueId: string, data: Partial<Solution>): Solution {
431
+ const issue = findIssue(issueId);
432
+ if (!issue) {
433
+ throw new Error(`Issue "${issueId}" not found`);
434
+ }
435
+
436
+ const solutions = readSolutions(issueId);
437
+ const solutionId = data.id || generateSolutionId(issueId, solutions);
438
+
439
+ if (solutions.some(s => s.id === solutionId)) {
440
+ throw new Error(`Solution "${solutionId}" already exists`);
441
+ }
442
+
443
+ const newSolution: Solution = {
444
+ id: solutionId,
445
+ description: data.description || '',
446
+ approach: data.approach || '',
447
+ tasks: data.tasks || [],
448
+ exploration_context: data.exploration_context,
449
+ analysis: data.analysis,
450
+ score: data.score,
451
+ is_bound: false,
452
+ created_at: new Date().toISOString()
453
+ };
454
+
455
+ solutions.push(newSolution);
456
+ writeSolutions(issueId, solutions);
457
+ return newSolution;
277
458
  }
278
459
 
279
460
  // ============ Queue Management (Multi-Queue) ============
@@ -434,7 +615,57 @@ function generateQueueItemId(queue: Queue, level: 'solution' | 'task' = 'solutio
434
615
  // ============ Commands ============
435
616
 
436
617
  /**
437
- * init - Initialize a new issue
618
+ * create - Create issue from JSON data
619
+ * Usage: ccw issue create --data '{"title":"...", "context":"..."}'
620
+ * Output: JSON with created issue (includes auto-generated ID)
621
+ */
622
+ async function createAction(options: IssueOptions): Promise<void> {
623
+ if (!options.data) {
624
+ console.error(chalk.red('JSON data required'));
625
+ console.error(chalk.gray('Usage: ccw issue create --data \'{"title":"...", "context":"..."}\''));
626
+ process.exit(1);
627
+ }
628
+
629
+ try {
630
+ const data = JSON.parse(options.data);
631
+ const issue = createIssue(data);
632
+ console.log(JSON.stringify(issue, null, 2));
633
+ } catch (err) {
634
+ console.error(chalk.red((err as Error).message));
635
+ process.exit(1);
636
+ }
637
+ }
638
+
639
+ /**
640
+ * solution - Create solution from JSON data
641
+ * Usage: ccw issue solution <issue-id> --data '{"tasks":[...]}'
642
+ * Output: JSON with created solution (includes auto-generated ID)
643
+ */
644
+ async function solutionAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
645
+ if (!issueId) {
646
+ console.error(chalk.red('Issue ID required'));
647
+ console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
648
+ process.exit(1);
649
+ }
650
+
651
+ if (!options.data) {
652
+ console.error(chalk.red('JSON data required'));
653
+ console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
654
+ process.exit(1);
655
+ }
656
+
657
+ try {
658
+ const data = JSON.parse(options.data);
659
+ const solution = createSolution(issueId, data);
660
+ console.log(JSON.stringify(solution, null, 2));
661
+ } catch (err) {
662
+ console.error(chalk.red((err as Error).message));
663
+ process.exit(1);
664
+ }
665
+ }
666
+
667
+ /**
668
+ * init - Initialize a new issue (manual ID)
438
669
  */
439
670
  async function initAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
440
671
  if (!issueId) {
@@ -458,7 +689,6 @@ async function initAction(issueId: string | undefined, options: IssueOptions): P
458
689
  priority: options.priority ? parseInt(options.priority) : 3,
459
690
  context: options.description || '',
460
691
  bound_solution_id: null,
461
- solution_count: 0,
462
692
  created_at: new Date().toISOString(),
463
693
  updated_at: new Date().toISOString()
464
694
  };
@@ -484,9 +714,17 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
484
714
  issues = issues.filter(i => statuses.includes(i.status));
485
715
  }
486
716
 
487
- // IDs only mode (one per line, for scripting)
488
- if (options.ids) {
489
- issues.forEach(i => console.log(i.id));
717
+ // Brief mode: minimal fields only (id, title, status, priority, tags, bound_solution_id)
718
+ if (options.brief) {
719
+ const briefIssues = issues.map(i => ({
720
+ id: i.id,
721
+ title: i.title,
722
+ status: i.status,
723
+ priority: i.priority,
724
+ tags: i.tags || [],
725
+ bound_solution_id: i.bound_solution_id
726
+ }));
727
+ console.log(JSON.stringify(briefIssues, null, 2));
490
728
  return;
491
729
  }
492
730
 
@@ -517,7 +755,8 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
517
755
  'paused': chalk.magenta
518
756
  }[issue.status] || chalk.white;
519
757
 
520
- const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${issue.solution_count}`;
758
+ const solutionCount = readSolutions(issue.id).length;
759
+ const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${solutionCount}`;
521
760
  console.log(
522
761
  issue.id.padEnd(20) +
523
762
  statusColor(issue.status.padEnd(15)) +
@@ -567,6 +806,52 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
567
806
  }
568
807
  }
569
808
 
809
+ /**
810
+ * history - List completed issues from history
811
+ */
812
+ async function historyAction(options: IssueOptions): Promise<void> {
813
+ const history = readIssueHistory();
814
+
815
+ // Brief mode: minimal fields only
816
+ if (options.brief) {
817
+ const briefHistory = history.map(i => ({
818
+ id: i.id,
819
+ title: i.title,
820
+ status: i.status,
821
+ completed_at: i.completed_at
822
+ }));
823
+ console.log(JSON.stringify(briefHistory, null, 2));
824
+ return;
825
+ }
826
+
827
+ if (options.json) {
828
+ console.log(JSON.stringify(history, null, 2));
829
+ return;
830
+ }
831
+
832
+ if (history.length === 0) {
833
+ console.log(chalk.yellow('No completed issues in history'));
834
+ return;
835
+ }
836
+
837
+ console.log(chalk.bold.cyan('\nIssue History (Completed)\n'));
838
+ console.log(chalk.gray('ID'.padEnd(25) + 'Completed At'.padEnd(22) + 'Title'));
839
+ console.log(chalk.gray('-'.repeat(80)));
840
+
841
+ for (const issue of history) {
842
+ const completedAt = issue.completed_at
843
+ ? new Date(issue.completed_at).toLocaleString()
844
+ : 'N/A';
845
+ console.log(
846
+ chalk.green(issue.id.padEnd(25)) +
847
+ completedAt.padEnd(22) +
848
+ (issue.title || '').substring(0, 35)
849
+ );
850
+ }
851
+
852
+ console.log(chalk.gray(`\nTotal: ${history.length} completed issues`));
853
+ }
854
+
570
855
  /**
571
856
  * status - Show detailed status
572
857
  */
@@ -651,7 +936,7 @@ async function taskAction(issueId: string | undefined, taskId: string | undefine
651
936
  // Create default solution if none bound
652
937
  if (boundIdx === -1) {
653
938
  const newSol: Solution = {
654
- id: generateSolutionId(),
939
+ id: generateSolutionId(issueId, solutions),
655
940
  description: 'Manual tasks',
656
941
  tasks: [],
657
942
  is_bound: true,
@@ -718,11 +1003,88 @@ async function taskAction(issueId: string | undefined, taskId: string | undefine
718
1003
 
719
1004
  /**
720
1005
  * update - Update issue fields (status, priority, title, etc.)
1006
+ * --from-queue: Sync statuses from active queue (auto-update queued issues)
721
1007
  */
722
1008
  async function updateAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
1009
+ // Handle --from-queue: Sync statuses from queue
1010
+ if (options.fromQueue) {
1011
+ // Determine queue ID: string value = specific queue, true = active queue
1012
+ const queueId = typeof options.fromQueue === 'string' ? options.fromQueue : undefined;
1013
+ const queue = queueId ? readQueue(queueId) : readActiveQueue();
1014
+
1015
+ if (!queue) {
1016
+ if (options.json) {
1017
+ console.log(JSON.stringify({ success: false, message: `Queue not found: ${queueId}`, queued: [], unplanned: [] }));
1018
+ } else {
1019
+ console.log(chalk.red(`Queue not found: ${queueId}`));
1020
+ }
1021
+ return;
1022
+ }
1023
+
1024
+ const items = queue.solutions || queue.tasks || [];
1025
+ const allIssues = readIssues();
1026
+
1027
+ if (!queue.id || items.length === 0) {
1028
+ if (options.json) {
1029
+ console.log(JSON.stringify({ success: false, message: 'No active queue', queued: [], unplanned: [] }));
1030
+ } else {
1031
+ console.log(chalk.yellow('No active queue to sync from'));
1032
+ }
1033
+ return;
1034
+ }
1035
+
1036
+ // Get issue IDs from queue
1037
+ const queuedIssueIds = new Set(items.map(item => item.issue_id));
1038
+ const now = new Date().toISOString();
1039
+
1040
+ // Track updates
1041
+ const updated: string[] = [];
1042
+ const unplanned: string[] = [];
1043
+
1044
+ // Update queued issues
1045
+ for (const issueId of queuedIssueIds) {
1046
+ const issue = allIssues.find(i => i.id === issueId);
1047
+ if (issue && issue.status !== 'queued' && issue.status !== 'executing' && issue.status !== 'completed') {
1048
+ updateIssue(issueId, { status: 'queued', queued_at: now });
1049
+ updated.push(issueId);
1050
+ }
1051
+ }
1052
+
1053
+ // Find planned issues NOT in queue
1054
+ for (const issue of allIssues) {
1055
+ if (issue.status === 'planned' && issue.bound_solution_id && !queuedIssueIds.has(issue.id)) {
1056
+ unplanned.push(issue.id);
1057
+ }
1058
+ }
1059
+
1060
+ if (options.json) {
1061
+ console.log(JSON.stringify({
1062
+ success: true,
1063
+ queue_id: queue.id,
1064
+ queued: updated,
1065
+ queued_count: updated.length,
1066
+ unplanned: unplanned,
1067
+ unplanned_count: unplanned.length
1068
+ }, null, 2));
1069
+ } else {
1070
+ console.log(chalk.green(`✓ Synced from queue ${queue.id}`));
1071
+ console.log(chalk.gray(` Updated to 'queued': ${updated.length} issues`));
1072
+ if (updated.length > 0) {
1073
+ updated.forEach(id => console.log(chalk.gray(` - ${id}`)));
1074
+ }
1075
+ if (unplanned.length > 0) {
1076
+ console.log(chalk.yellow(` Planned but NOT in queue: ${unplanned.length} issues`));
1077
+ unplanned.forEach(id => console.log(chalk.yellow(` - ${id}`)));
1078
+ }
1079
+ }
1080
+ return;
1081
+ }
1082
+
1083
+ // Standard single-issue update
723
1084
  if (!issueId) {
724
1085
  console.error(chalk.red('Issue ID is required'));
725
- console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status> [--priority <n>] [--title "..."]'));
1086
+ console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>'));
1087
+ console.error(chalk.gray(' ccw issue update --from-queue [queue-id] (sync from queue)'));
726
1088
  process.exit(1);
727
1089
  }
728
1090
 
@@ -802,8 +1164,9 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
802
1164
  try {
803
1165
  const content = readFileSync(options.solution, 'utf-8');
804
1166
  const data = JSON.parse(content);
1167
+ // Priority: CLI arg > file content ID > generate new (SOL-{issue-id}-{seq})
805
1168
  const newSol: Solution = {
806
- id: solutionId || generateSolutionId(),
1169
+ id: solutionId || data.id || generateSolutionId(issueId, solutions),
807
1170
  description: data.description || data.approach_name || 'Imported solution',
808
1171
  tasks: data.tasks || [],
809
1172
  exploration_context: data.exploration_context,
@@ -852,7 +1215,6 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
852
1215
  writeSolutions(issueId, solutions);
853
1216
  updateIssue(issueId, {
854
1217
  bound_solution_id: solutionId,
855
- solution_count: solutions.length,
856
1218
  status: 'planned',
857
1219
  planned_at: new Date().toISOString()
858
1220
  });
@@ -868,6 +1230,22 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
868
1230
  if (subAction === 'list' || subAction === 'history') {
869
1231
  const index = readQueueIndex();
870
1232
 
1233
+ // Brief mode: minimal queue index info
1234
+ if (options.brief) {
1235
+ const briefIndex = {
1236
+ active_queue_id: index.active_queue_id,
1237
+ queues: index.queues.map(q => ({
1238
+ id: q.id,
1239
+ status: q.status,
1240
+ issue_ids: q.issue_ids,
1241
+ total_solutions: q.total_solutions,
1242
+ completed_solutions: q.completed_solutions
1243
+ }))
1244
+ };
1245
+ console.log(JSON.stringify(briefIndex, null, 2));
1246
+ return;
1247
+ }
1248
+
871
1249
  if (options.json) {
872
1250
  console.log(JSON.stringify(index, null, 2));
873
1251
  return;
@@ -944,7 +1322,6 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
944
1322
  issue_id: item.issue_id,
945
1323
  solution_id: item.solution_id,
946
1324
  status: item.status,
947
- executor: item.assigned_executor,
948
1325
  priority: item.semantic_priority,
949
1326
  depends_on: item.depends_on || [],
950
1327
  task_count: item.task_count || 1,
@@ -1161,7 +1538,6 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
1161
1538
  execution_group: 'P1',
1162
1539
  depends_on: [],
1163
1540
  semantic_priority: 0.5,
1164
- assigned_executor: 'codex',
1165
1541
  task_count: solution.tasks?.length || 0,
1166
1542
  files_touched: Array.from(filesTouched)
1167
1543
  });
@@ -1179,6 +1555,28 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
1179
1555
  // Show current queue
1180
1556
  const queue = readActiveQueue();
1181
1557
 
1558
+ // Brief mode: minimal queue info (id, issue_ids, item summaries)
1559
+ if (options.brief) {
1560
+ const items = queue.solutions || queue.tasks || [];
1561
+ const briefQueue = {
1562
+ id: queue.id,
1563
+ issue_ids: queue.issue_ids || [],
1564
+ total: items.length,
1565
+ pending: items.filter(i => i.status === 'pending').length,
1566
+ executing: items.filter(i => i.status === 'executing').length,
1567
+ completed: items.filter(i => i.status === 'completed').length,
1568
+ items: items.map(i => ({
1569
+ item_id: i.item_id,
1570
+ issue_id: i.issue_id,
1571
+ solution_id: i.solution_id,
1572
+ status: i.status,
1573
+ task_count: i.task_count
1574
+ }))
1575
+ };
1576
+ console.log(JSON.stringify(briefQueue, null, 2));
1577
+ return;
1578
+ }
1579
+
1182
1580
  if (options.json) {
1183
1581
  console.log(JSON.stringify(queue, null, 2));
1184
1582
  return;
@@ -1203,11 +1601,11 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
1203
1601
  console.log();
1204
1602
 
1205
1603
  if (isSolutionLevel) {
1206
- console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
1604
+ console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'));
1207
1605
  } else {
1208
- console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'.padEnd(12) + 'Executor'));
1606
+ console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'));
1209
1607
  }
1210
- console.log(chalk.gray('-'.repeat(60)));
1608
+ console.log(chalk.gray('-'.repeat(48)));
1211
1609
 
1212
1610
  for (const item of items) {
1213
1611
  const statusColor = {
@@ -1227,8 +1625,7 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
1227
1625
  item.item_id.padEnd(10) +
1228
1626
  item.issue_id.substring(0, 13).padEnd(15) +
1229
1627
  thirdCol +
1230
- statusColor(item.status.padEnd(12)) +
1231
- item.assigned_executor
1628
+ statusColor(item.status)
1232
1629
  );
1233
1630
  }
1234
1631
  }
@@ -1337,7 +1734,6 @@ async function nextAction(itemId: string | undefined, options: IssueOptions): Pr
1337
1734
  resumed: isResume,
1338
1735
  resume_note: isResume ? `Resuming interrupted item (started: ${nextItem.started_at})` : undefined,
1339
1736
  execution_hints: {
1340
- executor: nextItem.assigned_executor,
1341
1737
  task_count: solution.tasks?.length || 0,
1342
1738
  estimated_minutes: totalMinutes
1343
1739
  },
@@ -1395,7 +1791,6 @@ async function detailAction(itemId: string | undefined, options: IssueOptions):
1395
1791
  exploration_context: solution.exploration_context || {}
1396
1792
  },
1397
1793
  execution_hints: {
1398
- executor: queueItem.assigned_executor,
1399
1794
  task_count: solution.tasks?.length || 0,
1400
1795
  estimated_minutes: totalMinutes
1401
1796
  }
@@ -1532,12 +1927,21 @@ export async function issueCommand(
1532
1927
  const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
1533
1928
 
1534
1929
  switch (subcommand) {
1930
+ case 'create':
1931
+ await createAction(options);
1932
+ break;
1933
+ case 'solution':
1934
+ await solutionAction(argsArray[0], options);
1935
+ break;
1535
1936
  case 'init':
1536
1937
  await initAction(argsArray[0], options);
1537
1938
  break;
1538
1939
  case 'list':
1539
1940
  await listAction(argsArray[0], options);
1540
1941
  break;
1942
+ case 'history':
1943
+ await historyAction(options);
1944
+ break;
1541
1945
  case 'status':
1542
1946
  await statusAction(argsArray[0], options);
1543
1947
  break;
@@ -1579,12 +1983,15 @@ export async function issueCommand(
1579
1983
  default:
1580
1984
  console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
1581
1985
  console.log(chalk.bold('Core Commands:'));
1582
- console.log(chalk.gray(' init <issue-id> Initialize new issue'));
1986
+ console.log(chalk.gray(' create --data \'{"title":"..."}\' Create issue (auto-generates ID)'));
1987
+ console.log(chalk.gray(' init <issue-id> Initialize new issue (manual ID)'));
1583
1988
  console.log(chalk.gray(' list [issue-id] List issues or tasks'));
1989
+ console.log(chalk.gray(' history List completed issues (from history)'));
1584
1990
  console.log(chalk.gray(' status [issue-id] Show detailed status'));
1585
- console.log(chalk.gray(' task <issue-id> [task-id] Add or update task'));
1586
- console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution (--solution <path> to register)'));
1587
- console.log(chalk.gray(' update <issue-id> Update issue (--status, --priority, --title)'));
1991
+ console.log(chalk.gray(' solution <id> --data \'{...}\' Create solution (auto-generates ID)'));
1992
+ console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution'));
1993
+ console.log(chalk.gray(' update <issue-id> --status <s> Update issue status'));
1994
+ console.log(chalk.gray(' update --from-queue [queue-id] Sync statuses from queue (default: active)'));
1588
1995
  console.log();
1589
1996
  console.log(chalk.bold('Queue Commands:'));
1590
1997
  console.log(chalk.gray(' queue Show active queue'));
@@ -1605,7 +2012,7 @@ export async function issueCommand(
1605
2012
  console.log(chalk.bold('Options:'));
1606
2013
  console.log(chalk.gray(' --title <title> Issue/task title'));
1607
2014
  console.log(chalk.gray(' --status <status> Filter by status (comma-separated)'));
1608
- console.log(chalk.gray(' --ids List only IDs (one per line)'));
2015
+ console.log(chalk.gray(' --brief Brief JSON output (minimal fields)'));
1609
2016
  console.log(chalk.gray(' --solution <path> Solution JSON file'));
1610
2017
  console.log(chalk.gray(' --result <json> Execution result'));
1611
2018
  console.log(chalk.gray(' --reason <text> Failure reason'));
@@ -1613,7 +2020,8 @@ export async function issueCommand(
1613
2020
  console.log(chalk.gray(' --force Force operation'));
1614
2021
  console.log();
1615
2022
  console.log(chalk.bold('Storage:'));
1616
- console.log(chalk.gray(' .workflow/issues/issues.jsonl All issues'));
2023
+ console.log(chalk.gray(' .workflow/issues/issues.jsonl Active issues'));
2024
+ console.log(chalk.gray(' .workflow/issues/issue-history.jsonl Completed issues'));
1617
2025
  console.log(chalk.gray(' .workflow/issues/solutions/*.jsonl Solutions per issue'));
1618
2026
  console.log(chalk.gray(' .workflow/issues/queues/ Queue files (multi-queue)'));
1619
2027
  console.log(chalk.gray(' .workflow/issues/queues/index.json Queue index'));