claude-code-workflow 6.3.37 → 6.3.39

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 (173) hide show
  1. package/.claude/commands/workflow/lite-execute.md +2 -0
  2. package/.codex/agents/action-planning-agent.md +885 -0
  3. package/.codex/agents/ccw-loop-b-complete.md +227 -0
  4. package/.codex/agents/ccw-loop-b-debug.md +172 -0
  5. package/.codex/agents/ccw-loop-b-develop.md +147 -0
  6. package/.codex/agents/ccw-loop-b-init.md +82 -0
  7. package/.codex/agents/ccw-loop-b-validate.md +204 -0
  8. package/.codex/agents/ccw-loop-executor.md +260 -0
  9. package/.codex/agents/cli-discuss-agent.md +391 -0
  10. package/.codex/agents/cli-execution-agent.md +333 -0
  11. package/.codex/agents/cli-explore-agent.md +186 -0
  12. package/.codex/agents/cli-lite-planning-agent.md +736 -0
  13. package/.codex/agents/cli-planning-agent.md +562 -0
  14. package/.codex/agents/code-developer.md +408 -0
  15. package/.codex/agents/conceptual-planning-agent.md +321 -0
  16. package/.codex/agents/context-search-agent.md +585 -0
  17. package/.codex/agents/debug-explore-agent.md +436 -0
  18. package/.codex/agents/doc-generator.md +334 -0
  19. package/.codex/agents/issue-plan-agent.md +417 -0
  20. package/.codex/agents/issue-queue-agent.md +311 -0
  21. package/.codex/agents/memory-bridge.md +96 -0
  22. package/.codex/agents/test-context-search-agent.md +402 -0
  23. package/.codex/agents/test-fix-agent.md +359 -0
  24. package/.codex/agents/ui-design-agent.md +595 -0
  25. package/.codex/agents/universal-executor.md +135 -0
  26. package/.codex/prompts/clean.md +409 -0
  27. package/.codex/prompts/issue-discover-by-prompt.md +364 -0
  28. package/.codex/prompts/issue-discover.md +261 -0
  29. package/.codex/prompts/issue-execute.md +10 -0
  30. package/.codex/prompts/issue-new.md +285 -0
  31. package/.codex/prompts/issue-plan.md +161 -63
  32. package/.codex/prompts/issue-queue.md +298 -288
  33. package/.codex/prompts/lite-execute.md +627 -133
  34. package/.codex/prompts/lite-fix.md +670 -0
  35. package/.codex/prompts/lite-plan-a.md +337 -0
  36. package/.codex/prompts/lite-plan-b.md +485 -0
  37. package/.codex/prompts/{lite-plan.md → lite-plan-c.md} +601 -469
  38. package/.codex/skills/ccw-loop/README.md +171 -0
  39. package/.codex/skills/ccw-loop/SKILL.md +349 -0
  40. package/.codex/skills/ccw-loop/phases/actions/action-complete.md +269 -0
  41. package/.codex/skills/ccw-loop/phases/actions/action-debug.md +286 -0
  42. package/.codex/skills/ccw-loop/phases/actions/action-develop.md +183 -0
  43. package/.codex/skills/ccw-loop/phases/actions/action-init.md +164 -0
  44. package/.codex/skills/ccw-loop/phases/actions/action-menu.md +205 -0
  45. package/.codex/skills/ccw-loop/phases/actions/action-validate.md +250 -0
  46. package/.codex/skills/ccw-loop/phases/orchestrator.md +416 -0
  47. package/.codex/skills/ccw-loop/phases/state-schema.md +388 -0
  48. package/.codex/skills/ccw-loop/specs/action-catalog.md +182 -0
  49. package/.codex/skills/ccw-loop-b/README.md +301 -0
  50. package/.codex/skills/ccw-loop-b/SKILL.md +322 -0
  51. package/.codex/skills/ccw-loop-b/phases/orchestrator.md +257 -0
  52. package/.codex/skills/ccw-loop-b/phases/state-schema.md +181 -0
  53. package/.codex/skills/ccw-loop-b/specs/action-catalog.md +383 -0
  54. package/.codex/skills/parallel-dev-cycle/README.md +382 -0
  55. package/.codex/skills/parallel-dev-cycle/SKILL.md +512 -0
  56. package/.codex/skills/parallel-dev-cycle/phases/agents/code-developer.md +242 -0
  57. package/.codex/skills/parallel-dev-cycle/phases/agents/exploration-planner.md +285 -0
  58. package/.codex/skills/parallel-dev-cycle/phases/agents/requirements-analyst.md +285 -0
  59. package/.codex/skills/parallel-dev-cycle/phases/agents/validation-archivist.md +381 -0
  60. package/.codex/skills/parallel-dev-cycle/phases/orchestrator.md +696 -0
  61. package/.codex/skills/parallel-dev-cycle/phases/state-schema.md +436 -0
  62. package/.codex/skills/parallel-dev-cycle/specs/communication-optimization.md +423 -0
  63. package/.codex/skills/parallel-dev-cycle/specs/coordination-protocol.md +391 -0
  64. package/.codex/skills/parallel-dev-cycle/specs/versioning-strategy.md +330 -0
  65. package/ccw/dist/cli.d.ts.map +1 -1
  66. package/ccw/dist/cli.js +4 -0
  67. package/ccw/dist/cli.js.map +1 -1
  68. package/ccw/dist/commands/install.d.ts.map +1 -1
  69. package/ccw/dist/commands/install.js +39 -8
  70. package/ccw/dist/commands/install.js.map +1 -1
  71. package/ccw/dist/commands/issue.d.ts +3 -0
  72. package/ccw/dist/commands/issue.d.ts.map +1 -1
  73. package/ccw/dist/commands/issue.js +107 -0
  74. package/ccw/dist/commands/issue.js.map +1 -1
  75. package/ccw/dist/commands/upgrade.js +1 -1
  76. package/ccw/dist/commands/upgrade.js.map +1 -1
  77. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -1
  78. package/ccw/dist/config/litellm-api-config-manager.js +3 -2
  79. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -1
  80. package/ccw/dist/core/memory-embedder-bridge.d.ts.map +1 -1
  81. package/ccw/dist/core/memory-embedder-bridge.js +2 -5
  82. package/ccw/dist/core/memory-embedder-bridge.js.map +1 -1
  83. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  84. package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
  85. package/ccw/dist/core/routes/codexlens/config-handlers.js +7 -6
  86. package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
  87. package/ccw/dist/core/routes/codexlens/semantic-handlers.d.ts.map +1 -1
  88. package/ccw/dist/core/routes/codexlens/semantic-handlers.js +2 -2
  89. package/ccw/dist/core/routes/codexlens/semantic-handlers.js.map +1 -1
  90. package/ccw/dist/core/routes/graph-routes.d.ts.map +1 -1
  91. package/ccw/dist/core/routes/graph-routes.js +17 -2
  92. package/ccw/dist/core/routes/graph-routes.js.map +1 -1
  93. package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
  94. package/ccw/dist/core/routes/issue-routes.js +280 -33
  95. package/ccw/dist/core/routes/issue-routes.js.map +1 -1
  96. package/ccw/dist/core/routes/loop-v2-routes.d.ts +9 -0
  97. package/ccw/dist/core/routes/loop-v2-routes.d.ts.map +1 -1
  98. package/ccw/dist/core/routes/loop-v2-routes.js +56 -4
  99. package/ccw/dist/core/routes/loop-v2-routes.js.map +1 -1
  100. package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
  101. package/ccw/dist/core/routes/system-routes.js +3 -2
  102. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  103. package/ccw/dist/core/server.d.ts.map +1 -1
  104. package/ccw/dist/core/server.js +5 -3
  105. package/ccw/dist/core/server.js.map +1 -1
  106. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  107. package/ccw/dist/tools/claude-cli-tools.js +4 -3
  108. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  109. package/ccw/dist/tools/cli-config-manager.d.ts +1 -0
  110. package/ccw/dist/tools/cli-config-manager.d.ts.map +1 -1
  111. package/ccw/dist/tools/cli-config-manager.js +2 -1
  112. package/ccw/dist/tools/cli-config-manager.js.map +1 -1
  113. package/ccw/dist/tools/codex-lens-lsp.d.ts.map +1 -1
  114. package/ccw/dist/tools/codex-lens-lsp.js +2 -5
  115. package/ccw/dist/tools/codex-lens-lsp.js.map +1 -1
  116. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  117. package/ccw/dist/tools/codex-lens.js +22 -32
  118. package/ccw/dist/tools/codex-lens.js.map +1 -1
  119. package/ccw/dist/tools/litellm-client.d.ts +6 -0
  120. package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
  121. package/ccw/dist/tools/litellm-client.js +15 -2
  122. package/ccw/dist/tools/litellm-client.js.map +1 -1
  123. package/ccw/dist/tools/loop-task-manager.d.ts +13 -2
  124. package/ccw/dist/tools/loop-task-manager.d.ts.map +1 -1
  125. package/ccw/dist/tools/loop-task-manager.js.map +1 -1
  126. package/ccw/dist/tools/native-session-discovery.d.ts.map +1 -1
  127. package/ccw/dist/tools/native-session-discovery.js +35 -7
  128. package/ccw/dist/tools/native-session-discovery.js.map +1 -1
  129. package/ccw/dist/utils/codexlens-path.d.ts +36 -0
  130. package/ccw/dist/utils/codexlens-path.d.ts.map +1 -0
  131. package/ccw/dist/utils/codexlens-path.js +56 -0
  132. package/ccw/dist/utils/codexlens-path.js.map +1 -0
  133. package/ccw/dist/utils/uv-manager.d.ts.map +1 -1
  134. package/ccw/dist/utils/uv-manager.js +3 -2
  135. package/ccw/dist/utils/uv-manager.js.map +1 -1
  136. package/ccw/src/cli.ts +4 -0
  137. package/ccw/src/commands/install.ts +51 -8
  138. package/ccw/src/commands/issue.ts +119 -0
  139. package/ccw/src/commands/upgrade.ts +1 -1
  140. package/ccw/src/config/litellm-api-config-manager.ts +3 -2
  141. package/ccw/src/core/memory-embedder-bridge.ts +2 -6
  142. package/ccw/src/core/routes/cli-routes.ts +1 -1
  143. package/ccw/src/core/routes/codexlens/config-handlers.ts +7 -6
  144. package/ccw/src/core/routes/codexlens/semantic-handlers.ts +2 -2
  145. package/ccw/src/core/routes/graph-routes.ts +18 -2
  146. package/ccw/src/core/routes/issue-routes.ts +308 -33
  147. package/ccw/src/core/routes/loop-v2-routes.ts +64 -6
  148. package/ccw/src/core/routes/system-routes.ts +3 -2
  149. package/ccw/src/core/server.ts +6 -3
  150. package/ccw/src/templates/dashboard-css/02-session.css +2 -0
  151. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +103 -1
  152. package/ccw/src/templates/dashboard-css/32-issue-manager.css +32 -0
  153. package/ccw/src/templates/dashboard-js/components/cli-history.js +48 -48
  154. package/ccw/src/templates/dashboard-js/components/navigation.js +6 -0
  155. package/ccw/src/templates/dashboard-js/components/notifications.js +6 -0
  156. package/ccw/src/templates/dashboard-js/components/version-check.js +38 -0
  157. package/ccw/src/templates/dashboard-js/i18n.js +126 -0
  158. package/ccw/src/templates/dashboard-js/state.js +2 -0
  159. package/ccw/src/templates/dashboard-js/views/cli-manager.js +1 -1
  160. package/ccw/src/templates/dashboard-js/views/issue-manager.js +183 -1
  161. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +55 -11
  162. package/ccw/src/templates/dashboard-js/views/loop-monitor.js +112 -11
  163. package/ccw/src/templates/dashboard.html +48 -2
  164. package/ccw/src/tools/claude-cli-tools.ts +4 -3
  165. package/ccw/src/tools/cli-config-manager.ts +3 -1
  166. package/ccw/src/tools/codex-lens-lsp.ts +2 -5
  167. package/ccw/src/tools/codex-lens.ts +27 -38
  168. package/ccw/src/tools/litellm-client.ts +16 -2
  169. package/ccw/src/tools/loop-task-manager.ts +13 -2
  170. package/ccw/src/tools/native-session-discovery.ts +38 -7
  171. package/ccw/src/utils/codexlens-path.ts +60 -0
  172. package/ccw/src/utils/uv-manager.ts +3 -2
  173. package/package.json +1 -1
@@ -203,18 +203,20 @@ function getIssueDetail(issuesDir, issueId) {
203
203
  function enrichIssues(issues, issuesDir) {
204
204
  return issues.map(issue => {
205
205
  const solutions = readSolutionsJsonl(issuesDir, issue.id);
206
- let taskCount = 0;
207
- // Get task count from bound solution
206
+ let tasks = [];
207
+ // Get tasks from bound solution
208
208
  if (issue.bound_solution_id) {
209
209
  const boundSol = solutions.find(s => s.id === issue.bound_solution_id);
210
210
  if (boundSol?.tasks) {
211
- taskCount = boundSol.tasks.length;
211
+ tasks = boundSol.tasks;
212
212
  }
213
213
  }
214
214
  return {
215
215
  ...issue,
216
+ solutions, // Add full solutions array
217
+ tasks, // Add full tasks array
216
218
  solution_count: solutions.length,
217
- task_count: taskCount
219
+ task_count: tasks.length
218
220
  };
219
221
  });
220
222
  }
@@ -310,37 +312,52 @@ export async function handleIssueRoutes(ctx) {
310
312
  return true;
311
313
  }
312
314
  const issuesDir = join(projectPath, '.workflow', 'issues');
313
- // ===== Queue Routes (top-level /api/queue) =====
314
- // GET /api/queue - Get execution queue
315
- if (pathname === '/api/queue' && req.method === 'GET') {
315
+ // ===== Helper: Normalize queue path (supports both /api/queue/* and /api/issues/queue/*) =====
316
+ const normalizeQueuePath = (path) => {
317
+ if (path.startsWith('/api/issues/queue')) {
318
+ return path.replace('/api/issues/queue', '/api/queue');
319
+ }
320
+ if (path.startsWith('/api/queue')) {
321
+ return path;
322
+ }
323
+ return null;
324
+ };
325
+ const normalizedPath = normalizeQueuePath(pathname);
326
+ // ===== Queue Routes (supports both /api/queue/* and /api/issues/queue/*) =====
327
+ // GET /api/queue or /api/issues/queue - Get execution queue
328
+ if ((normalizedPath === '/api/queue') && req.method === 'GET') {
316
329
  const queue = groupQueueByExecutionGroup(readQueue(issuesDir));
317
330
  res.writeHead(200, { 'Content-Type': 'application/json' });
318
331
  res.end(JSON.stringify(queue));
319
332
  return true;
320
333
  }
321
- // GET /api/queue/history - Get queue history (all queues from index)
322
- if (pathname === '/api/queue/history' && req.method === 'GET') {
334
+ // GET /api/queue/history or /api/issues/queue/history - Get queue history (all queues from index)
335
+ if (normalizedPath === '/api/queue/history' && req.method === 'GET') {
323
336
  const queuesDir = join(issuesDir, 'queues');
324
337
  const indexPath = join(queuesDir, 'index.json');
325
338
  if (!existsSync(indexPath)) {
326
339
  res.writeHead(200, { 'Content-Type': 'application/json' });
327
- res.end(JSON.stringify({ queues: [], active_queue_id: null }));
340
+ res.end(JSON.stringify({ queues: [], active_queue_id: null, active_queue_ids: [] }));
328
341
  return true;
329
342
  }
330
343
  try {
331
344
  const index = JSON.parse(readFileSync(indexPath, 'utf8'));
345
+ // Ensure active_queue_ids is always returned for multi-queue support
346
+ if (!index.active_queue_ids) {
347
+ index.active_queue_ids = index.active_queue_id ? [index.active_queue_id] : [];
348
+ }
332
349
  res.writeHead(200, { 'Content-Type': 'application/json' });
333
350
  res.end(JSON.stringify(index));
334
351
  }
335
352
  catch {
336
353
  res.writeHead(200, { 'Content-Type': 'application/json' });
337
- res.end(JSON.stringify({ queues: [], active_queue_id: null }));
354
+ res.end(JSON.stringify({ queues: [], active_queue_id: null, active_queue_ids: [] }));
338
355
  }
339
356
  return true;
340
357
  }
341
- // GET /api/queue/:id - Get specific queue by ID
342
- const queueDetailMatch = pathname.match(/^\/api\/queue\/([^/]+)$/);
343
- const reservedQueuePaths = ['history', 'reorder', 'switch', 'deactivate', 'merge'];
358
+ // GET /api/queue/:id or /api/issues/queue/:id - Get specific queue by ID
359
+ const queueDetailMatch = normalizedPath?.match(/^\/api\/queue\/([^/]+)$/);
360
+ const reservedQueuePaths = ['history', 'reorder', 'switch', 'deactivate', 'merge', 'activate'];
344
361
  if (queueDetailMatch && req.method === 'GET' && !reservedQueuePaths.includes(queueDetailMatch[1])) {
345
362
  const queueId = queueDetailMatch[1];
346
363
  const queuesDir = join(issuesDir, 'queues');
@@ -361,8 +378,47 @@ export async function handleIssueRoutes(ctx) {
361
378
  }
362
379
  return true;
363
380
  }
364
- // POST /api/queue/switch - Switch active queue
365
- if (pathname === '/api/queue/switch' && req.method === 'POST') {
381
+ // POST /api/queue/activate or /api/issues/queue/activate - Activate one or more queues (multi-queue support)
382
+ if (normalizedPath === '/api/queue/activate' && req.method === 'POST') {
383
+ handlePostRequest(req, res, async (body) => {
384
+ const { queueId, queueIds } = body;
385
+ // Support both single queueId and array queueIds
386
+ const idsToActivate = queueIds
387
+ ? (Array.isArray(queueIds) ? queueIds : [queueIds])
388
+ : (queueId ? [queueId] : []);
389
+ if (idsToActivate.length === 0) {
390
+ return { error: 'queueId or queueIds required' };
391
+ }
392
+ const queuesDir = join(issuesDir, 'queues');
393
+ const indexPath = join(queuesDir, 'index.json');
394
+ // Validate all queue IDs exist
395
+ for (const id of idsToActivate) {
396
+ const queueFilePath = join(queuesDir, `${id}.json`);
397
+ if (!existsSync(queueFilePath)) {
398
+ return { error: `Queue ${id} not found` };
399
+ }
400
+ }
401
+ try {
402
+ const index = existsSync(indexPath)
403
+ ? JSON.parse(readFileSync(indexPath, 'utf8'))
404
+ : { active_queue_id: null, active_queue_ids: [], queues: [] };
405
+ index.active_queue_ids = idsToActivate;
406
+ index.active_queue_id = idsToActivate[0] || null; // Backward compat
407
+ writeFileSync(indexPath, JSON.stringify(index, null, 2));
408
+ return {
409
+ success: true,
410
+ active_queue_ids: idsToActivate,
411
+ active_queue_id: idsToActivate[0] || null // Backward compat
412
+ };
413
+ }
414
+ catch (err) {
415
+ return { error: 'Failed to activate queue(s)' };
416
+ }
417
+ });
418
+ return true;
419
+ }
420
+ // POST /api/queue/switch or /api/issues/queue/switch - Switch active queue (legacy, single queue)
421
+ if (normalizedPath === '/api/queue/switch' && req.method === 'POST') {
366
422
  handlePostRequest(req, res, async (body) => {
367
423
  const { queueId } = body;
368
424
  if (!queueId)
@@ -376,10 +432,15 @@ export async function handleIssueRoutes(ctx) {
376
432
  try {
377
433
  const index = existsSync(indexPath)
378
434
  ? JSON.parse(readFileSync(indexPath, 'utf8'))
379
- : { active_queue_id: null, queues: [] };
435
+ : { active_queue_id: null, active_queue_ids: [], queues: [] };
380
436
  index.active_queue_id = queueId;
437
+ index.active_queue_ids = [queueId]; // Also update multi-queue array
381
438
  writeFileSync(indexPath, JSON.stringify(index, null, 2));
382
- return { success: true, active_queue_id: queueId };
439
+ return {
440
+ success: true,
441
+ active_queue_id: queueId,
442
+ active_queue_ids: [queueId]
443
+ };
383
444
  }
384
445
  catch (err) {
385
446
  return { error: 'Failed to switch queue' };
@@ -387,19 +448,38 @@ export async function handleIssueRoutes(ctx) {
387
448
  });
388
449
  return true;
389
450
  }
390
- // POST /api/queue/deactivate - Deactivate current queue (set active to null)
391
- if (pathname === '/api/queue/deactivate' && req.method === 'POST') {
451
+ // POST /api/queue/deactivate or /api/issues/queue/deactivate - Deactivate queue(s)
452
+ if (normalizedPath === '/api/queue/deactivate' && req.method === 'POST') {
392
453
  handlePostRequest(req, res, async (body) => {
454
+ const { queueId } = body; // Optional: specific queue to deactivate
393
455
  const queuesDir = join(issuesDir, 'queues');
394
456
  const indexPath = join(queuesDir, 'index.json');
395
457
  try {
396
458
  const index = existsSync(indexPath)
397
459
  ? JSON.parse(readFileSync(indexPath, 'utf8'))
398
- : { active_queue_id: null, queues: [] };
399
- const previousActiveId = index.active_queue_id;
400
- index.active_queue_id = null;
460
+ : { active_queue_id: null, active_queue_ids: [], queues: [] };
461
+ const currentActiveIds = index.active_queue_ids || (index.active_queue_id ? [index.active_queue_id] : []);
462
+ let deactivatedIds = [];
463
+ let remainingIds = [];
464
+ if (queueId) {
465
+ // Deactivate specific queue
466
+ deactivatedIds = currentActiveIds.includes(queueId) ? [queueId] : [];
467
+ remainingIds = currentActiveIds.filter((id) => id !== queueId);
468
+ }
469
+ else {
470
+ // Deactivate all
471
+ deactivatedIds = [...currentActiveIds];
472
+ remainingIds = [];
473
+ }
474
+ index.active_queue_ids = remainingIds;
475
+ index.active_queue_id = remainingIds[0] || null; // Backward compat
401
476
  writeFileSync(indexPath, JSON.stringify(index, null, 2));
402
- return { success: true, previous_active_id: previousActiveId };
477
+ return {
478
+ success: true,
479
+ deactivated_queue_ids: deactivatedIds,
480
+ active_queue_ids: remainingIds,
481
+ active_queue_id: remainingIds[0] || null // Backward compat
482
+ };
403
483
  }
404
484
  catch (err) {
405
485
  return { error: 'Failed to deactivate queue' };
@@ -407,8 +487,8 @@ export async function handleIssueRoutes(ctx) {
407
487
  });
408
488
  return true;
409
489
  }
410
- // POST /api/queue/reorder - Reorder queue items (supports both solutions and tasks)
411
- if (pathname === '/api/queue/reorder' && req.method === 'POST') {
490
+ // POST /api/queue/reorder or /api/issues/queue/reorder - Reorder queue items (supports both solutions and tasks)
491
+ if (normalizedPath === '/api/queue/reorder' && req.method === 'POST') {
412
492
  handlePostRequest(req, res, async (body) => {
413
493
  const { groupId, newOrder } = body;
414
494
  if (!groupId || !Array.isArray(newOrder)) {
@@ -454,8 +534,8 @@ export async function handleIssueRoutes(ctx) {
454
534
  });
455
535
  return true;
456
536
  }
457
- // DELETE /api/queue/:queueId/item/:itemId - Delete item from queue
458
- const queueItemDeleteMatch = pathname.match(/^\/api\/queue\/([^/]+)\/item\/([^/]+)$/);
537
+ // DELETE /api/queue/:queueId/item/:itemId or /api/issues/queue/:queueId/item/:itemId
538
+ const queueItemDeleteMatch = normalizedPath?.match(/^\/api\/queue\/([^/]+)\/item\/([^/]+)$/);
459
539
  if (queueItemDeleteMatch && req.method === 'DELETE') {
460
540
  const queueId = queueItemDeleteMatch[1];
461
541
  const itemId = decodeURIComponent(queueItemDeleteMatch[2]);
@@ -523,8 +603,8 @@ export async function handleIssueRoutes(ctx) {
523
603
  }
524
604
  return true;
525
605
  }
526
- // DELETE /api/queue/:queueId - Delete entire queue
527
- const queueDeleteMatch = pathname.match(/^\/api\/queue\/([^/]+)$/);
606
+ // DELETE /api/queue/:queueId or /api/issues/queue/:queueId - Delete entire queue
607
+ const queueDeleteMatch = normalizedPath?.match(/^\/api\/queue\/([^/]+)$/);
528
608
  if (queueDeleteMatch && req.method === 'DELETE') {
529
609
  const queueId = queueDeleteMatch[1];
530
610
  const queuesDir = join(issuesDir, 'queues');
@@ -558,8 +638,8 @@ export async function handleIssueRoutes(ctx) {
558
638
  }
559
639
  return true;
560
640
  }
561
- // POST /api/queue/merge - Merge source queue into target queue
562
- if (pathname === '/api/queue/merge' && req.method === 'POST') {
641
+ // POST /api/queue/merge or /api/issues/queue/merge - Merge source queue into target queue
642
+ if (normalizedPath === '/api/queue/merge' && req.method === 'POST') {
563
643
  handlePostRequest(req, res, async (body) => {
564
644
  const { sourceQueueId, targetQueueId } = body;
565
645
  if (!sourceQueueId || !targetQueueId) {
@@ -681,7 +761,7 @@ export async function handleIssueRoutes(ctx) {
681
761
  return true;
682
762
  }
683
763
  // POST /api/queue/split - Split items from source queue into a new queue
684
- if (pathname === '/api/queue/split' && req.method === 'POST') {
764
+ if (normalizedPath === '/api/queue/split' && req.method === 'POST') {
685
765
  handlePostRequest(req, res, async (body) => {
686
766
  const { sourceQueueId, itemIds } = body;
687
767
  if (!sourceQueueId || !itemIds || !Array.isArray(itemIds) || itemIds.length === 0) {
@@ -889,6 +969,173 @@ export async function handleIssueRoutes(ctx) {
889
969
  });
890
970
  return true;
891
971
  }
972
+ // POST /api/issues/pull - Pull issues from GitHub
973
+ if (pathname === '/api/issues/pull' && req.method === 'POST') {
974
+ const state = url.searchParams.get('state') || 'open';
975
+ const limit = parseInt(url.searchParams.get('limit') || '100');
976
+ const labels = url.searchParams.get('labels') || '';
977
+ const downloadImages = url.searchParams.get('downloadImages') === 'true';
978
+ try {
979
+ const { execSync } = await import('child_process');
980
+ const https = await import('https');
981
+ const http = await import('http');
982
+ // Check if gh CLI is available
983
+ try {
984
+ execSync('gh --version', { stdio: 'ignore', timeout: 5000 });
985
+ }
986
+ catch {
987
+ res.writeHead(500, { 'Content-Type': 'application/json' });
988
+ res.end(JSON.stringify({ error: 'GitHub CLI (gh) is not installed or not in PATH' }));
989
+ return true;
990
+ }
991
+ // Build gh command
992
+ let ghCommand = `gh issue list --state ${state} --limit ${limit} --json number,title,body,labels,url,state`;
993
+ if (labels)
994
+ ghCommand += ` --label "${labels}"`;
995
+ // Execute gh command from project root
996
+ const ghOutput = execSync(ghCommand, {
997
+ encoding: 'utf-8',
998
+ stdio: ['pipe', 'pipe', 'pipe'],
999
+ timeout: 60000,
1000
+ cwd: issuesDir.replace(/[\\/]\.workflow[\\/]issues$/, '')
1001
+ }).trim();
1002
+ if (!ghOutput) {
1003
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1004
+ res.end(JSON.stringify({ imported: 0, updated: 0, skipped: 0, images_downloaded: 0 }));
1005
+ return true;
1006
+ }
1007
+ const ghIssues = JSON.parse(ghOutput);
1008
+ const existingIssues = readIssuesJsonl(issuesDir);
1009
+ let imported = 0;
1010
+ let skipped = 0;
1011
+ let updated = 0;
1012
+ let imagesDownloaded = 0;
1013
+ // Create images directory if needed
1014
+ const imagesDir = join(issuesDir, 'images');
1015
+ if (downloadImages && !existsSync(imagesDir)) {
1016
+ mkdirSync(imagesDir, { recursive: true });
1017
+ }
1018
+ // Helper function to download image
1019
+ const downloadImage = async (imageUrl, issueNumber, imageIndex) => {
1020
+ return new Promise((resolveDownload) => {
1021
+ try {
1022
+ const ext = imageUrl.match(/\.(png|jpg|jpeg|gif|webp|svg)/i)?.[1] || 'png';
1023
+ const filename = `GH-${issueNumber}-${imageIndex}.${ext}`;
1024
+ const filePath = join(imagesDir, filename);
1025
+ // Skip if already downloaded
1026
+ if (existsSync(filePath)) {
1027
+ resolveDownload(`.workflow/issues/images/${filename}`);
1028
+ return;
1029
+ }
1030
+ const protocol = imageUrl.startsWith('https') ? https : http;
1031
+ protocol.get(imageUrl, { timeout: 30000 }, (response) => {
1032
+ // Handle redirect
1033
+ if (response.statusCode === 301 || response.statusCode === 302) {
1034
+ const redirectUrl = response.headers.location;
1035
+ if (redirectUrl) {
1036
+ downloadImage(redirectUrl, issueNumber, imageIndex).then(resolveDownload);
1037
+ return;
1038
+ }
1039
+ }
1040
+ if (response.statusCode !== 200) {
1041
+ resolveDownload(null);
1042
+ return;
1043
+ }
1044
+ const chunks = [];
1045
+ response.on('data', (chunk) => chunks.push(chunk));
1046
+ response.on('end', () => {
1047
+ try {
1048
+ writeFileSync(filePath, Buffer.concat(chunks));
1049
+ resolveDownload(`.workflow/issues/images/${filename}`);
1050
+ }
1051
+ catch {
1052
+ resolveDownload(null);
1053
+ }
1054
+ });
1055
+ response.on('error', () => resolveDownload(null));
1056
+ }).on('error', () => resolveDownload(null));
1057
+ }
1058
+ catch {
1059
+ resolveDownload(null);
1060
+ }
1061
+ });
1062
+ };
1063
+ // Process issues
1064
+ for (const ghIssue of ghIssues) {
1065
+ const issueId = `GH-${ghIssue.number}`;
1066
+ const existingIssue = existingIssues.find((i) => i.id === issueId);
1067
+ let context = ghIssue.body || ghIssue.title;
1068
+ // Extract and download images if enabled
1069
+ if (downloadImages && ghIssue.body) {
1070
+ // Find all image URLs in the body
1071
+ const imgPattern = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)|<img[^>]+src=["'](https?:\/\/[^"']+)["']/gi;
1072
+ const imageUrls = [];
1073
+ let match;
1074
+ while ((match = imgPattern.exec(ghIssue.body)) !== null) {
1075
+ imageUrls.push(match[1] || match[2]);
1076
+ }
1077
+ // Download images and build reference list
1078
+ if (imageUrls.length > 0) {
1079
+ const downloadedImages = [];
1080
+ for (let i = 0; i < imageUrls.length; i++) {
1081
+ const localPath = await downloadImage(imageUrls[i], ghIssue.number, i + 1);
1082
+ if (localPath) {
1083
+ downloadedImages.push(localPath);
1084
+ imagesDownloaded++;
1085
+ }
1086
+ }
1087
+ // Append image references to context
1088
+ if (downloadedImages.length > 0) {
1089
+ context += '\n\n---\n**Downloaded Images:**\n';
1090
+ downloadedImages.forEach((path, idx) => {
1091
+ context += `- Image ${idx + 1}: \`${path}\`\n`;
1092
+ });
1093
+ }
1094
+ }
1095
+ }
1096
+ // Prepare issue data (truncate context to 2000 chars max)
1097
+ const issueData = {
1098
+ id: issueId,
1099
+ title: ghIssue.title,
1100
+ status: ghIssue.state === 'OPEN' ? 'registered' : 'completed',
1101
+ priority: 3,
1102
+ context: context.substring(0, 2000),
1103
+ source: 'github',
1104
+ source_url: ghIssue.url,
1105
+ tags: ghIssue.labels?.map((l) => l.name) || [],
1106
+ created_at: new Date().toISOString(),
1107
+ updated_at: new Date().toISOString()
1108
+ };
1109
+ if (existingIssue) {
1110
+ // Update if changed
1111
+ const newStatus = ghIssue.state === 'OPEN' ? 'registered' : 'completed';
1112
+ if (existingIssue.status !== newStatus || existingIssue.title !== ghIssue.title) {
1113
+ existingIssue.title = ghIssue.title;
1114
+ existingIssue.status = newStatus;
1115
+ existingIssue.context = issueData.context;
1116
+ existingIssue.updated_at = new Date().toISOString();
1117
+ updated++;
1118
+ }
1119
+ else {
1120
+ skipped++;
1121
+ }
1122
+ }
1123
+ else {
1124
+ existingIssues.push(issueData);
1125
+ imported++;
1126
+ }
1127
+ }
1128
+ // Save all issues
1129
+ writeIssuesJsonl(issuesDir, existingIssues);
1130
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1131
+ res.end(JSON.stringify({ imported, updated, skipped, images_downloaded: imagesDownloaded, total: ghIssues.length }));
1132
+ }
1133
+ catch (err) {
1134
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1135
+ res.end(JSON.stringify({ error: err.message || 'Failed to pull issues from GitHub' }));
1136
+ }
1137
+ return true;
1138
+ }
892
1139
  // GET /api/issues/:id - Get issue detail
893
1140
  const detailMatch = pathname.match(/^\/api\/issues\/([^/]+)$/);
894
1141
  if (detailMatch && req.method === 'GET') {