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.
- package/.claude/commands/workflow/lite-execute.md +2 -0
- package/.codex/agents/action-planning-agent.md +885 -0
- package/.codex/agents/ccw-loop-b-complete.md +227 -0
- package/.codex/agents/ccw-loop-b-debug.md +172 -0
- package/.codex/agents/ccw-loop-b-develop.md +147 -0
- package/.codex/agents/ccw-loop-b-init.md +82 -0
- package/.codex/agents/ccw-loop-b-validate.md +204 -0
- package/.codex/agents/ccw-loop-executor.md +260 -0
- package/.codex/agents/cli-discuss-agent.md +391 -0
- package/.codex/agents/cli-execution-agent.md +333 -0
- package/.codex/agents/cli-explore-agent.md +186 -0
- package/.codex/agents/cli-lite-planning-agent.md +736 -0
- package/.codex/agents/cli-planning-agent.md +562 -0
- package/.codex/agents/code-developer.md +408 -0
- package/.codex/agents/conceptual-planning-agent.md +321 -0
- package/.codex/agents/context-search-agent.md +585 -0
- package/.codex/agents/debug-explore-agent.md +436 -0
- package/.codex/agents/doc-generator.md +334 -0
- package/.codex/agents/issue-plan-agent.md +417 -0
- package/.codex/agents/issue-queue-agent.md +311 -0
- package/.codex/agents/memory-bridge.md +96 -0
- package/.codex/agents/test-context-search-agent.md +402 -0
- package/.codex/agents/test-fix-agent.md +359 -0
- package/.codex/agents/ui-design-agent.md +595 -0
- package/.codex/agents/universal-executor.md +135 -0
- package/.codex/prompts/clean.md +409 -0
- package/.codex/prompts/issue-discover-by-prompt.md +364 -0
- package/.codex/prompts/issue-discover.md +261 -0
- package/.codex/prompts/issue-execute.md +10 -0
- package/.codex/prompts/issue-new.md +285 -0
- package/.codex/prompts/issue-plan.md +161 -63
- package/.codex/prompts/issue-queue.md +298 -288
- package/.codex/prompts/lite-execute.md +627 -133
- package/.codex/prompts/lite-fix.md +670 -0
- package/.codex/prompts/lite-plan-a.md +337 -0
- package/.codex/prompts/lite-plan-b.md +485 -0
- package/.codex/prompts/{lite-plan.md → lite-plan-c.md} +601 -469
- package/.codex/skills/ccw-loop/README.md +171 -0
- package/.codex/skills/ccw-loop/SKILL.md +349 -0
- package/.codex/skills/ccw-loop/phases/actions/action-complete.md +269 -0
- package/.codex/skills/ccw-loop/phases/actions/action-debug.md +286 -0
- package/.codex/skills/ccw-loop/phases/actions/action-develop.md +183 -0
- package/.codex/skills/ccw-loop/phases/actions/action-init.md +164 -0
- package/.codex/skills/ccw-loop/phases/actions/action-menu.md +205 -0
- package/.codex/skills/ccw-loop/phases/actions/action-validate.md +250 -0
- package/.codex/skills/ccw-loop/phases/orchestrator.md +416 -0
- package/.codex/skills/ccw-loop/phases/state-schema.md +388 -0
- package/.codex/skills/ccw-loop/specs/action-catalog.md +182 -0
- package/.codex/skills/ccw-loop-b/README.md +301 -0
- package/.codex/skills/ccw-loop-b/SKILL.md +322 -0
- package/.codex/skills/ccw-loop-b/phases/orchestrator.md +257 -0
- package/.codex/skills/ccw-loop-b/phases/state-schema.md +181 -0
- package/.codex/skills/ccw-loop-b/specs/action-catalog.md +383 -0
- package/.codex/skills/parallel-dev-cycle/README.md +382 -0
- package/.codex/skills/parallel-dev-cycle/SKILL.md +512 -0
- package/.codex/skills/parallel-dev-cycle/phases/agents/code-developer.md +242 -0
- package/.codex/skills/parallel-dev-cycle/phases/agents/exploration-planner.md +285 -0
- package/.codex/skills/parallel-dev-cycle/phases/agents/requirements-analyst.md +285 -0
- package/.codex/skills/parallel-dev-cycle/phases/agents/validation-archivist.md +381 -0
- package/.codex/skills/parallel-dev-cycle/phases/orchestrator.md +696 -0
- package/.codex/skills/parallel-dev-cycle/phases/state-schema.md +436 -0
- package/.codex/skills/parallel-dev-cycle/specs/communication-optimization.md +423 -0
- package/.codex/skills/parallel-dev-cycle/specs/coordination-protocol.md +391 -0
- package/.codex/skills/parallel-dev-cycle/specs/versioning-strategy.md +330 -0
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +4 -0
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/install.d.ts.map +1 -1
- package/ccw/dist/commands/install.js +39 -8
- package/ccw/dist/commands/install.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts +3 -0
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +107 -0
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/commands/upgrade.js +1 -1
- package/ccw/dist/commands/upgrade.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.js +3 -2
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -1
- package/ccw/dist/core/memory-embedder-bridge.d.ts.map +1 -1
- package/ccw/dist/core/memory-embedder-bridge.js +2 -5
- package/ccw/dist/core/memory-embedder-bridge.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens/config-handlers.js +7 -6
- package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
- package/ccw/dist/core/routes/codexlens/semantic-handlers.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens/semantic-handlers.js +2 -2
- package/ccw/dist/core/routes/codexlens/semantic-handlers.js.map +1 -1
- package/ccw/dist/core/routes/graph-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/graph-routes.js +17 -2
- package/ccw/dist/core/routes/graph-routes.js.map +1 -1
- package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/issue-routes.js +280 -33
- package/ccw/dist/core/routes/issue-routes.js.map +1 -1
- package/ccw/dist/core/routes/loop-v2-routes.d.ts +9 -0
- package/ccw/dist/core/routes/loop-v2-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/loop-v2-routes.js +56 -4
- package/ccw/dist/core/routes/loop-v2-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +3 -2
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +5 -3
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.js +4 -3
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
- package/ccw/dist/tools/cli-config-manager.d.ts +1 -0
- package/ccw/dist/tools/cli-config-manager.d.ts.map +1 -1
- package/ccw/dist/tools/cli-config-manager.js +2 -1
- package/ccw/dist/tools/cli-config-manager.js.map +1 -1
- package/ccw/dist/tools/codex-lens-lsp.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens-lsp.js +2 -5
- package/ccw/dist/tools/codex-lens-lsp.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +22 -32
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +6 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
- package/ccw/dist/tools/litellm-client.js +15 -2
- package/ccw/dist/tools/litellm-client.js.map +1 -1
- package/ccw/dist/tools/loop-task-manager.d.ts +13 -2
- package/ccw/dist/tools/loop-task-manager.d.ts.map +1 -1
- package/ccw/dist/tools/loop-task-manager.js.map +1 -1
- package/ccw/dist/tools/native-session-discovery.d.ts.map +1 -1
- package/ccw/dist/tools/native-session-discovery.js +35 -7
- package/ccw/dist/tools/native-session-discovery.js.map +1 -1
- package/ccw/dist/utils/codexlens-path.d.ts +36 -0
- package/ccw/dist/utils/codexlens-path.d.ts.map +1 -0
- package/ccw/dist/utils/codexlens-path.js +56 -0
- package/ccw/dist/utils/codexlens-path.js.map +1 -0
- package/ccw/dist/utils/uv-manager.d.ts.map +1 -1
- package/ccw/dist/utils/uv-manager.js +3 -2
- package/ccw/dist/utils/uv-manager.js.map +1 -1
- package/ccw/src/cli.ts +4 -0
- package/ccw/src/commands/install.ts +51 -8
- package/ccw/src/commands/issue.ts +119 -0
- package/ccw/src/commands/upgrade.ts +1 -1
- package/ccw/src/config/litellm-api-config-manager.ts +3 -2
- package/ccw/src/core/memory-embedder-bridge.ts +2 -6
- package/ccw/src/core/routes/cli-routes.ts +1 -1
- package/ccw/src/core/routes/codexlens/config-handlers.ts +7 -6
- package/ccw/src/core/routes/codexlens/semantic-handlers.ts +2 -2
- package/ccw/src/core/routes/graph-routes.ts +18 -2
- package/ccw/src/core/routes/issue-routes.ts +308 -33
- package/ccw/src/core/routes/loop-v2-routes.ts +64 -6
- package/ccw/src/core/routes/system-routes.ts +3 -2
- package/ccw/src/core/server.ts +6 -3
- package/ccw/src/templates/dashboard-css/02-session.css +2 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +103 -1
- package/ccw/src/templates/dashboard-css/32-issue-manager.css +32 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +48 -48
- package/ccw/src/templates/dashboard-js/components/navigation.js +6 -0
- package/ccw/src/templates/dashboard-js/components/notifications.js +6 -0
- package/ccw/src/templates/dashboard-js/components/version-check.js +38 -0
- package/ccw/src/templates/dashboard-js/i18n.js +126 -0
- package/ccw/src/templates/dashboard-js/state.js +2 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +1 -1
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +183 -1
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +55 -11
- package/ccw/src/templates/dashboard-js/views/loop-monitor.js +112 -11
- package/ccw/src/templates/dashboard.html +48 -2
- package/ccw/src/tools/claude-cli-tools.ts +4 -3
- package/ccw/src/tools/cli-config-manager.ts +3 -1
- package/ccw/src/tools/codex-lens-lsp.ts +2 -5
- package/ccw/src/tools/codex-lens.ts +27 -38
- package/ccw/src/tools/litellm-client.ts +16 -2
- package/ccw/src/tools/loop-task-manager.ts +13 -2
- package/ccw/src/tools/native-session-discovery.ts +38 -7
- package/ccw/src/utils/codexlens-path.ts +60 -0
- package/ccw/src/utils/uv-manager.ts +3 -2
- package/package.json +1 -1
|
@@ -212,20 +212,22 @@ function getIssueDetail(issuesDir: string, issueId: string) {
|
|
|
212
212
|
function enrichIssues(issues: any[], issuesDir: string) {
|
|
213
213
|
return issues.map(issue => {
|
|
214
214
|
const solutions = readSolutionsJsonl(issuesDir, issue.id);
|
|
215
|
-
let
|
|
215
|
+
let tasks: any[] = [];
|
|
216
216
|
|
|
217
|
-
// Get
|
|
217
|
+
// Get tasks from bound solution
|
|
218
218
|
if (issue.bound_solution_id) {
|
|
219
219
|
const boundSol = solutions.find(s => s.id === issue.bound_solution_id);
|
|
220
220
|
if (boundSol?.tasks) {
|
|
221
|
-
|
|
221
|
+
tasks = boundSol.tasks;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
return {
|
|
226
226
|
...issue,
|
|
227
|
+
solutions, // Add full solutions array
|
|
228
|
+
tasks, // Add full tasks array
|
|
227
229
|
solution_count: solutions.length,
|
|
228
|
-
task_count:
|
|
230
|
+
task_count: tasks.length
|
|
229
231
|
};
|
|
230
232
|
});
|
|
231
233
|
}
|
|
@@ -337,41 +339,58 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
337
339
|
|
|
338
340
|
const issuesDir = join(projectPath, '.workflow', 'issues');
|
|
339
341
|
|
|
340
|
-
// =====
|
|
342
|
+
// ===== Helper: Normalize queue path (supports both /api/queue/* and /api/issues/queue/*) =====
|
|
343
|
+
const normalizeQueuePath = (path: string): string | null => {
|
|
344
|
+
if (path.startsWith('/api/issues/queue')) {
|
|
345
|
+
return path.replace('/api/issues/queue', '/api/queue');
|
|
346
|
+
}
|
|
347
|
+
if (path.startsWith('/api/queue')) {
|
|
348
|
+
return path;
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
};
|
|
341
352
|
|
|
342
|
-
|
|
343
|
-
|
|
353
|
+
const normalizedPath = normalizeQueuePath(pathname);
|
|
354
|
+
|
|
355
|
+
// ===== Queue Routes (supports both /api/queue/* and /api/issues/queue/*) =====
|
|
356
|
+
|
|
357
|
+
// GET /api/queue or /api/issues/queue - Get execution queue
|
|
358
|
+
if ((normalizedPath === '/api/queue') && req.method === 'GET') {
|
|
344
359
|
const queue = groupQueueByExecutionGroup(readQueue(issuesDir));
|
|
345
360
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
346
361
|
res.end(JSON.stringify(queue));
|
|
347
362
|
return true;
|
|
348
363
|
}
|
|
349
364
|
|
|
350
|
-
// GET /api/queue/history - Get queue history (all queues from index)
|
|
351
|
-
if (
|
|
365
|
+
// GET /api/queue/history or /api/issues/queue/history - Get queue history (all queues from index)
|
|
366
|
+
if (normalizedPath === '/api/queue/history' && req.method === 'GET') {
|
|
352
367
|
const queuesDir = join(issuesDir, 'queues');
|
|
353
368
|
const indexPath = join(queuesDir, 'index.json');
|
|
354
369
|
|
|
355
370
|
if (!existsSync(indexPath)) {
|
|
356
371
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
357
|
-
res.end(JSON.stringify({ queues: [], active_queue_id: null }));
|
|
372
|
+
res.end(JSON.stringify({ queues: [], active_queue_id: null, active_queue_ids: [] }));
|
|
358
373
|
return true;
|
|
359
374
|
}
|
|
360
375
|
|
|
361
376
|
try {
|
|
362
377
|
const index = JSON.parse(readFileSync(indexPath, 'utf8'));
|
|
378
|
+
// Ensure active_queue_ids is always returned for multi-queue support
|
|
379
|
+
if (!index.active_queue_ids) {
|
|
380
|
+
index.active_queue_ids = index.active_queue_id ? [index.active_queue_id] : [];
|
|
381
|
+
}
|
|
363
382
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
364
383
|
res.end(JSON.stringify(index));
|
|
365
384
|
} catch {
|
|
366
385
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
367
|
-
res.end(JSON.stringify({ queues: [], active_queue_id: null }));
|
|
386
|
+
res.end(JSON.stringify({ queues: [], active_queue_id: null, active_queue_ids: [] }));
|
|
368
387
|
}
|
|
369
388
|
return true;
|
|
370
389
|
}
|
|
371
390
|
|
|
372
|
-
// GET /api/queue/:id - Get specific queue by ID
|
|
373
|
-
const queueDetailMatch =
|
|
374
|
-
const reservedQueuePaths = ['history', 'reorder', 'switch', 'deactivate', 'merge'];
|
|
391
|
+
// GET /api/queue/:id or /api/issues/queue/:id - Get specific queue by ID
|
|
392
|
+
const queueDetailMatch = normalizedPath?.match(/^\/api\/queue\/([^/]+)$/);
|
|
393
|
+
const reservedQueuePaths = ['history', 'reorder', 'switch', 'deactivate', 'merge', 'activate'];
|
|
375
394
|
if (queueDetailMatch && req.method === 'GET' && !reservedQueuePaths.includes(queueDetailMatch[1])) {
|
|
376
395
|
const queueId = queueDetailMatch[1];
|
|
377
396
|
const queuesDir = join(issuesDir, 'queues');
|
|
@@ -394,8 +413,55 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
394
413
|
return true;
|
|
395
414
|
}
|
|
396
415
|
|
|
397
|
-
// POST /api/queue/
|
|
398
|
-
if (
|
|
416
|
+
// POST /api/queue/activate or /api/issues/queue/activate - Activate one or more queues (multi-queue support)
|
|
417
|
+
if (normalizedPath === '/api/queue/activate' && req.method === 'POST') {
|
|
418
|
+
handlePostRequest(req, res, async (body: any) => {
|
|
419
|
+
const { queueId, queueIds } = body;
|
|
420
|
+
|
|
421
|
+
// Support both single queueId and array queueIds
|
|
422
|
+
const idsToActivate: string[] = queueIds
|
|
423
|
+
? (Array.isArray(queueIds) ? queueIds : [queueIds])
|
|
424
|
+
: (queueId ? [queueId] : []);
|
|
425
|
+
|
|
426
|
+
if (idsToActivate.length === 0) {
|
|
427
|
+
return { error: 'queueId or queueIds required' };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const queuesDir = join(issuesDir, 'queues');
|
|
431
|
+
const indexPath = join(queuesDir, 'index.json');
|
|
432
|
+
|
|
433
|
+
// Validate all queue IDs exist
|
|
434
|
+
for (const id of idsToActivate) {
|
|
435
|
+
const queueFilePath = join(queuesDir, `${id}.json`);
|
|
436
|
+
if (!existsSync(queueFilePath)) {
|
|
437
|
+
return { error: `Queue ${id} not found` };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
const index = existsSync(indexPath)
|
|
443
|
+
? JSON.parse(readFileSync(indexPath, 'utf8'))
|
|
444
|
+
: { active_queue_id: null, active_queue_ids: [], queues: [] };
|
|
445
|
+
|
|
446
|
+
index.active_queue_ids = idsToActivate;
|
|
447
|
+
index.active_queue_id = idsToActivate[0] || null; // Backward compat
|
|
448
|
+
|
|
449
|
+
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
success: true,
|
|
453
|
+
active_queue_ids: idsToActivate,
|
|
454
|
+
active_queue_id: idsToActivate[0] || null // Backward compat
|
|
455
|
+
};
|
|
456
|
+
} catch (err) {
|
|
457
|
+
return { error: 'Failed to activate queue(s)' };
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// POST /api/queue/switch or /api/issues/queue/switch - Switch active queue (legacy, single queue)
|
|
464
|
+
if (normalizedPath === '/api/queue/switch' && req.method === 'POST') {
|
|
399
465
|
handlePostRequest(req, res, async (body: any) => {
|
|
400
466
|
const { queueId } = body;
|
|
401
467
|
if (!queueId) return { error: 'queueId required' };
|
|
@@ -411,12 +477,18 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
411
477
|
try {
|
|
412
478
|
const index = existsSync(indexPath)
|
|
413
479
|
? JSON.parse(readFileSync(indexPath, 'utf8'))
|
|
414
|
-
: { active_queue_id: null, queues: [] };
|
|
480
|
+
: { active_queue_id: null, active_queue_ids: [], queues: [] };
|
|
415
481
|
|
|
416
482
|
index.active_queue_id = queueId;
|
|
483
|
+
index.active_queue_ids = [queueId]; // Also update multi-queue array
|
|
484
|
+
|
|
417
485
|
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
418
486
|
|
|
419
|
-
return {
|
|
487
|
+
return {
|
|
488
|
+
success: true,
|
|
489
|
+
active_queue_id: queueId,
|
|
490
|
+
active_queue_ids: [queueId]
|
|
491
|
+
};
|
|
420
492
|
} catch (err) {
|
|
421
493
|
return { error: 'Failed to switch queue' };
|
|
422
494
|
}
|
|
@@ -424,22 +496,43 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
424
496
|
return true;
|
|
425
497
|
}
|
|
426
498
|
|
|
427
|
-
// POST /api/queue/deactivate - Deactivate
|
|
428
|
-
if (
|
|
499
|
+
// POST /api/queue/deactivate or /api/issues/queue/deactivate - Deactivate queue(s)
|
|
500
|
+
if (normalizedPath === '/api/queue/deactivate' && req.method === 'POST') {
|
|
429
501
|
handlePostRequest(req, res, async (body: any) => {
|
|
502
|
+
const { queueId } = body; // Optional: specific queue to deactivate
|
|
430
503
|
const queuesDir = join(issuesDir, 'queues');
|
|
431
504
|
const indexPath = join(queuesDir, 'index.json');
|
|
432
505
|
|
|
433
506
|
try {
|
|
434
507
|
const index = existsSync(indexPath)
|
|
435
508
|
? JSON.parse(readFileSync(indexPath, 'utf8'))
|
|
436
|
-
: { active_queue_id: null, queues: [] };
|
|
509
|
+
: { active_queue_id: null, active_queue_ids: [], queues: [] };
|
|
510
|
+
|
|
511
|
+
const currentActiveIds = index.active_queue_ids || (index.active_queue_id ? [index.active_queue_id] : []);
|
|
512
|
+
let deactivatedIds: string[] = [];
|
|
513
|
+
let remainingIds: string[] = [];
|
|
514
|
+
|
|
515
|
+
if (queueId) {
|
|
516
|
+
// Deactivate specific queue
|
|
517
|
+
deactivatedIds = currentActiveIds.includes(queueId) ? [queueId] : [];
|
|
518
|
+
remainingIds = currentActiveIds.filter((id: string) => id !== queueId);
|
|
519
|
+
} else {
|
|
520
|
+
// Deactivate all
|
|
521
|
+
deactivatedIds = [...currentActiveIds];
|
|
522
|
+
remainingIds = [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
index.active_queue_ids = remainingIds;
|
|
526
|
+
index.active_queue_id = remainingIds[0] || null; // Backward compat
|
|
437
527
|
|
|
438
|
-
const previousActiveId = index.active_queue_id;
|
|
439
|
-
index.active_queue_id = null;
|
|
440
528
|
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
441
529
|
|
|
442
|
-
return {
|
|
530
|
+
return {
|
|
531
|
+
success: true,
|
|
532
|
+
deactivated_queue_ids: deactivatedIds,
|
|
533
|
+
active_queue_ids: remainingIds,
|
|
534
|
+
active_queue_id: remainingIds[0] || null // Backward compat
|
|
535
|
+
};
|
|
443
536
|
} catch (err) {
|
|
444
537
|
return { error: 'Failed to deactivate queue' };
|
|
445
538
|
}
|
|
@@ -447,8 +540,8 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
447
540
|
return true;
|
|
448
541
|
}
|
|
449
542
|
|
|
450
|
-
// POST /api/queue/reorder - Reorder queue items (supports both solutions and tasks)
|
|
451
|
-
if (
|
|
543
|
+
// POST /api/queue/reorder or /api/issues/queue/reorder - Reorder queue items (supports both solutions and tasks)
|
|
544
|
+
if (normalizedPath === '/api/queue/reorder' && req.method === 'POST') {
|
|
452
545
|
handlePostRequest(req, res, async (body: any) => {
|
|
453
546
|
const { groupId, newOrder } = body;
|
|
454
547
|
if (!groupId || !Array.isArray(newOrder)) {
|
|
@@ -499,8 +592,8 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
499
592
|
return true;
|
|
500
593
|
}
|
|
501
594
|
|
|
502
|
-
// DELETE /api/queue/:queueId/item/:itemId
|
|
503
|
-
const queueItemDeleteMatch =
|
|
595
|
+
// DELETE /api/queue/:queueId/item/:itemId or /api/issues/queue/:queueId/item/:itemId
|
|
596
|
+
const queueItemDeleteMatch = normalizedPath?.match(/^\/api\/queue\/([^/]+)\/item\/([^/]+)$/);
|
|
504
597
|
if (queueItemDeleteMatch && req.method === 'DELETE') {
|
|
505
598
|
const queueId = queueItemDeleteMatch[1];
|
|
506
599
|
const itemId = decodeURIComponent(queueItemDeleteMatch[2]);
|
|
@@ -574,8 +667,8 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
574
667
|
return true;
|
|
575
668
|
}
|
|
576
669
|
|
|
577
|
-
// DELETE /api/queue/:queueId - Delete entire queue
|
|
578
|
-
const queueDeleteMatch =
|
|
670
|
+
// DELETE /api/queue/:queueId or /api/issues/queue/:queueId - Delete entire queue
|
|
671
|
+
const queueDeleteMatch = normalizedPath?.match(/^\/api\/queue\/([^/]+)$/);
|
|
579
672
|
if (queueDeleteMatch && req.method === 'DELETE') {
|
|
580
673
|
const queueId = queueDeleteMatch[1];
|
|
581
674
|
const queuesDir = join(issuesDir, 'queues');
|
|
@@ -616,8 +709,8 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
616
709
|
return true;
|
|
617
710
|
}
|
|
618
711
|
|
|
619
|
-
// POST /api/queue/merge - Merge source queue into target queue
|
|
620
|
-
if (
|
|
712
|
+
// POST /api/queue/merge or /api/issues/queue/merge - Merge source queue into target queue
|
|
713
|
+
if (normalizedPath === '/api/queue/merge' && req.method === 'POST') {
|
|
621
714
|
handlePostRequest(req, res, async (body: any) => {
|
|
622
715
|
const { sourceQueueId, targetQueueId } = body;
|
|
623
716
|
if (!sourceQueueId || !targetQueueId) {
|
|
@@ -755,7 +848,7 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
755
848
|
}
|
|
756
849
|
|
|
757
850
|
// POST /api/queue/split - Split items from source queue into a new queue
|
|
758
|
-
if (
|
|
851
|
+
if (normalizedPath === '/api/queue/split' && req.method === 'POST') {
|
|
759
852
|
handlePostRequest(req, res, async (body: any) => {
|
|
760
853
|
const { sourceQueueId, itemIds } = body;
|
|
761
854
|
if (!sourceQueueId || !itemIds || !Array.isArray(itemIds) || itemIds.length === 0) {
|
|
@@ -992,6 +1085,188 @@ export async function handleIssueRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
992
1085
|
return true;
|
|
993
1086
|
}
|
|
994
1087
|
|
|
1088
|
+
// POST /api/issues/pull - Pull issues from GitHub
|
|
1089
|
+
if (pathname === '/api/issues/pull' && req.method === 'POST') {
|
|
1090
|
+
const state = url.searchParams.get('state') || 'open';
|
|
1091
|
+
const limit = parseInt(url.searchParams.get('limit') || '100');
|
|
1092
|
+
const labels = url.searchParams.get('labels') || '';
|
|
1093
|
+
const downloadImages = url.searchParams.get('downloadImages') === 'true';
|
|
1094
|
+
|
|
1095
|
+
try {
|
|
1096
|
+
const { execSync } = await import('child_process');
|
|
1097
|
+
const https = await import('https');
|
|
1098
|
+
const http = await import('http');
|
|
1099
|
+
|
|
1100
|
+
// Check if gh CLI is available
|
|
1101
|
+
try {
|
|
1102
|
+
execSync('gh --version', { stdio: 'ignore', timeout: 5000 });
|
|
1103
|
+
} catch {
|
|
1104
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1105
|
+
res.end(JSON.stringify({ error: 'GitHub CLI (gh) is not installed or not in PATH' }));
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Build gh command
|
|
1110
|
+
let ghCommand = `gh issue list --state ${state} --limit ${limit} --json number,title,body,labels,url,state`;
|
|
1111
|
+
if (labels) ghCommand += ` --label "${labels}"`;
|
|
1112
|
+
|
|
1113
|
+
// Execute gh command from project root
|
|
1114
|
+
const ghOutput = execSync(ghCommand, {
|
|
1115
|
+
encoding: 'utf-8',
|
|
1116
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1117
|
+
timeout: 60000,
|
|
1118
|
+
cwd: issuesDir.replace(/[\\/]\.workflow[\\/]issues$/, '')
|
|
1119
|
+
}).trim();
|
|
1120
|
+
|
|
1121
|
+
if (!ghOutput) {
|
|
1122
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1123
|
+
res.end(JSON.stringify({ imported: 0, updated: 0, skipped: 0, images_downloaded: 0 }));
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const ghIssues = JSON.parse(ghOutput);
|
|
1128
|
+
const existingIssues = readIssuesJsonl(issuesDir);
|
|
1129
|
+
|
|
1130
|
+
let imported = 0;
|
|
1131
|
+
let skipped = 0;
|
|
1132
|
+
let updated = 0;
|
|
1133
|
+
let imagesDownloaded = 0;
|
|
1134
|
+
|
|
1135
|
+
// Create images directory if needed
|
|
1136
|
+
const imagesDir = join(issuesDir, 'images');
|
|
1137
|
+
if (downloadImages && !existsSync(imagesDir)) {
|
|
1138
|
+
mkdirSync(imagesDir, { recursive: true });
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Helper function to download image
|
|
1142
|
+
const downloadImage = async (imageUrl: string, issueNumber: number, imageIndex: number): Promise<string | null> => {
|
|
1143
|
+
return new Promise((resolveDownload) => {
|
|
1144
|
+
try {
|
|
1145
|
+
const ext = imageUrl.match(/\.(png|jpg|jpeg|gif|webp|svg)/i)?.[1] || 'png';
|
|
1146
|
+
const filename = `GH-${issueNumber}-${imageIndex}.${ext}`;
|
|
1147
|
+
const filePath = join(imagesDir, filename);
|
|
1148
|
+
|
|
1149
|
+
// Skip if already downloaded
|
|
1150
|
+
if (existsSync(filePath)) {
|
|
1151
|
+
resolveDownload(`.workflow/issues/images/${filename}`);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const protocol = imageUrl.startsWith('https') ? https : http;
|
|
1156
|
+
protocol.get(imageUrl, { timeout: 30000 }, (response: any) => {
|
|
1157
|
+
// Handle redirect
|
|
1158
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
1159
|
+
const redirectUrl = response.headers.location;
|
|
1160
|
+
if (redirectUrl) {
|
|
1161
|
+
downloadImage(redirectUrl, issueNumber, imageIndex).then(resolveDownload);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (response.statusCode !== 200) {
|
|
1166
|
+
resolveDownload(null);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const chunks: Buffer[] = [];
|
|
1171
|
+
response.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
1172
|
+
response.on('end', () => {
|
|
1173
|
+
try {
|
|
1174
|
+
writeFileSync(filePath, Buffer.concat(chunks));
|
|
1175
|
+
resolveDownload(`.workflow/issues/images/${filename}`);
|
|
1176
|
+
} catch {
|
|
1177
|
+
resolveDownload(null);
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
response.on('error', () => resolveDownload(null));
|
|
1181
|
+
}).on('error', () => resolveDownload(null));
|
|
1182
|
+
} catch {
|
|
1183
|
+
resolveDownload(null);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// Process issues
|
|
1189
|
+
for (const ghIssue of ghIssues) {
|
|
1190
|
+
const issueId = `GH-${ghIssue.number}`;
|
|
1191
|
+
const existingIssue = existingIssues.find((i: any) => i.id === issueId);
|
|
1192
|
+
|
|
1193
|
+
let context = ghIssue.body || ghIssue.title;
|
|
1194
|
+
|
|
1195
|
+
// Extract and download images if enabled
|
|
1196
|
+
if (downloadImages && ghIssue.body) {
|
|
1197
|
+
// Find all image URLs in the body
|
|
1198
|
+
const imgPattern = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)|<img[^>]+src=["'](https?:\/\/[^"']+)["']/gi;
|
|
1199
|
+
const imageUrls: string[] = [];
|
|
1200
|
+
let match;
|
|
1201
|
+
while ((match = imgPattern.exec(ghIssue.body)) !== null) {
|
|
1202
|
+
imageUrls.push(match[1] || match[2]);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Download images and build reference list
|
|
1206
|
+
if (imageUrls.length > 0) {
|
|
1207
|
+
const downloadedImages: string[] = [];
|
|
1208
|
+
for (let i = 0; i < imageUrls.length; i++) {
|
|
1209
|
+
const localPath = await downloadImage(imageUrls[i], ghIssue.number, i + 1);
|
|
1210
|
+
if (localPath) {
|
|
1211
|
+
downloadedImages.push(localPath);
|
|
1212
|
+
imagesDownloaded++;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// Append image references to context
|
|
1217
|
+
if (downloadedImages.length > 0) {
|
|
1218
|
+
context += '\n\n---\n**Downloaded Images:**\n';
|
|
1219
|
+
downloadedImages.forEach((path, idx) => {
|
|
1220
|
+
context += `- Image ${idx + 1}: \`${path}\`\n`;
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Prepare issue data (truncate context to 2000 chars max)
|
|
1227
|
+
const issueData = {
|
|
1228
|
+
id: issueId,
|
|
1229
|
+
title: ghIssue.title,
|
|
1230
|
+
status: ghIssue.state === 'OPEN' ? 'registered' : 'completed',
|
|
1231
|
+
priority: 3,
|
|
1232
|
+
context: context.substring(0, 2000),
|
|
1233
|
+
source: 'github',
|
|
1234
|
+
source_url: ghIssue.url,
|
|
1235
|
+
tags: ghIssue.labels?.map((l: any) => l.name) || [],
|
|
1236
|
+
created_at: new Date().toISOString(),
|
|
1237
|
+
updated_at: new Date().toISOString()
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
if (existingIssue) {
|
|
1241
|
+
// Update if changed
|
|
1242
|
+
const newStatus = ghIssue.state === 'OPEN' ? 'registered' : 'completed';
|
|
1243
|
+
if (existingIssue.status !== newStatus || existingIssue.title !== ghIssue.title) {
|
|
1244
|
+
existingIssue.title = ghIssue.title;
|
|
1245
|
+
existingIssue.status = newStatus;
|
|
1246
|
+
existingIssue.context = issueData.context;
|
|
1247
|
+
existingIssue.updated_at = new Date().toISOString();
|
|
1248
|
+
updated++;
|
|
1249
|
+
} else {
|
|
1250
|
+
skipped++;
|
|
1251
|
+
}
|
|
1252
|
+
} else {
|
|
1253
|
+
existingIssues.push(issueData);
|
|
1254
|
+
imported++;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Save all issues
|
|
1259
|
+
writeIssuesJsonl(issuesDir, existingIssues);
|
|
1260
|
+
|
|
1261
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1262
|
+
res.end(JSON.stringify({ imported, updated, skipped, images_downloaded: imagesDownloaded, total: ghIssues.length }));
|
|
1263
|
+
} catch (err: any) {
|
|
1264
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1265
|
+
res.end(JSON.stringify({ error: err.message || 'Failed to pull issues from GitHub' }));
|
|
1266
|
+
}
|
|
1267
|
+
return true;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
995
1270
|
// GET /api/issues/:id - Get issue detail
|
|
996
1271
|
const detailMatch = pathname.match(/^\/api\/issues\/([^/]+)$/);
|
|
997
1272
|
if (detailMatch && req.method === 'GET') {
|
|
@@ -29,11 +29,45 @@
|
|
|
29
29
|
|
|
30
30
|
import { join } from 'path';
|
|
31
31
|
import { randomBytes } from 'crypto';
|
|
32
|
+
import * as os from 'os';
|
|
32
33
|
import type { RouteContext } from './types.js';
|
|
33
34
|
import { LoopStatus } from '../../types/loop.js';
|
|
34
35
|
import type { LoopState } from '../../types/loop.js';
|
|
35
36
|
import { TaskStorageManager, type TaskCreateRequest, type TaskUpdateRequest, type TaskReorderRequest } from '../../tools/loop-task-manager.js';
|
|
36
37
|
import { executeCliTool } from '../../tools/cli-executor.js';
|
|
38
|
+
import { loadClaudeCliTools } from '../../tools/claude-cli-tools.js';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Module-level cache for CLI tools configuration
|
|
42
|
+
* Loaded once at server startup to avoid repeated file I/O
|
|
43
|
+
*/
|
|
44
|
+
let cachedEnabledTools: string[] | null = null;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize CLI tools cache at server startup
|
|
48
|
+
* Should be called once when the server starts
|
|
49
|
+
*/
|
|
50
|
+
export function initializeCliToolsCache(): void {
|
|
51
|
+
try {
|
|
52
|
+
const cliToolsConfig = loadClaudeCliTools(os.homedir());
|
|
53
|
+
const enabledTools = Object.entries(cliToolsConfig.tools || {})
|
|
54
|
+
.filter(([_, config]) => config.enabled === true)
|
|
55
|
+
.map(([name]) => name);
|
|
56
|
+
cachedEnabledTools = ['bash', ...enabledTools];
|
|
57
|
+
console.log('[Loop V2] CLI tools cache initialized:', cachedEnabledTools);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('[Loop V2] Failed to initialize CLI tools cache:', err);
|
|
60
|
+
// Fallback to basic tools if config loading fails
|
|
61
|
+
cachedEnabledTools = ['bash', 'gemini', 'qwen', 'codex', 'claude'];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Clear CLI tools cache (for testing or config reload)
|
|
67
|
+
*/
|
|
68
|
+
export function clearCliToolsCache(): void {
|
|
69
|
+
cachedEnabledTools = null;
|
|
70
|
+
}
|
|
37
71
|
|
|
38
72
|
/**
|
|
39
73
|
* V2 Loop Create Request
|
|
@@ -710,9 +744,17 @@ export async function handleLoopV2Routes(ctx: RouteContext): Promise<boolean> {
|
|
|
710
744
|
return { success: false, error: 'tool is required', status: 400 };
|
|
711
745
|
}
|
|
712
746
|
|
|
713
|
-
|
|
747
|
+
// Get enabled tools from cli-tools.json dynamically
|
|
748
|
+
const cliToolsConfig = loadClaudeCliTools(os.homedir());
|
|
749
|
+
const enabledTools = Object.entries(cliToolsConfig.tools || {})
|
|
750
|
+
.filter(([_, config]) => config.enabled === true)
|
|
751
|
+
.map(([name]) => name);
|
|
752
|
+
|
|
753
|
+
// Also allow 'bash' as a special case (built-in tool)
|
|
754
|
+
const validTools = ['bash', ...enabledTools];
|
|
755
|
+
|
|
714
756
|
if (!validTools.includes(tool)) {
|
|
715
|
-
return { success: false, error: `tool must be one of: ${validTools.join(', ')}`, status: 400 };
|
|
757
|
+
return { success: false, error: `tool must be one of enabled tools: ${validTools.join(', ')}`, status: 400 };
|
|
716
758
|
}
|
|
717
759
|
|
|
718
760
|
if (!mode || typeof mode !== 'string') {
|
|
@@ -1303,12 +1345,28 @@ function isValidId(id: string): boolean {
|
|
|
1303
1345
|
return true;
|
|
1304
1346
|
}
|
|
1305
1347
|
|
|
1348
|
+
/**
|
|
1349
|
+
* Get enabled tools list from cache
|
|
1350
|
+
* If cache is not initialized, it will load from config (fallback for lazy initialization)
|
|
1351
|
+
*/
|
|
1352
|
+
function getEnabledToolsList(): string[] {
|
|
1353
|
+
// Return cached value if available
|
|
1354
|
+
if (cachedEnabledTools) {
|
|
1355
|
+
return cachedEnabledTools;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Fallback: lazy initialization if cache not initialized (shouldn't happen in normal operation)
|
|
1359
|
+
console.warn('[Loop V2] CLI tools cache not initialized, performing lazy load');
|
|
1360
|
+
initializeCliToolsCache();
|
|
1361
|
+
return cachedEnabledTools || ['bash', 'gemini', 'qwen', 'codex', 'claude'];
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1306
1364
|
/**
|
|
1307
1365
|
* Map issue tool to loop tool
|
|
1308
1366
|
*/
|
|
1309
|
-
function mapIssueToolToLoopTool(tool: any):
|
|
1310
|
-
const validTools =
|
|
1311
|
-
if (validTools.includes(tool)) return tool
|
|
1367
|
+
function mapIssueToolToLoopTool(tool: any): string | null {
|
|
1368
|
+
const validTools = getEnabledToolsList();
|
|
1369
|
+
if (validTools.includes(tool)) return tool;
|
|
1312
1370
|
// Map aliases
|
|
1313
1371
|
if (tool === 'ccw') return 'gemini';
|
|
1314
1372
|
if (tool === 'ai') return 'gemini';
|
|
@@ -1343,7 +1401,7 @@ function mapIssueOnError(onError: any): 'continue' | 'pause' | 'fail_fast' | und
|
|
|
1343
1401
|
* Validate tool value
|
|
1344
1402
|
*/
|
|
1345
1403
|
function validateTool(tool: any): boolean {
|
|
1346
|
-
const validTools =
|
|
1404
|
+
const validTools = getEnabledToolsList();
|
|
1347
1405
|
return validTools.includes(tool);
|
|
1348
1406
|
}
|
|
1349
1407
|
|
|
@@ -145,7 +145,7 @@ async function getWorkflowData(projectPath: string): Promise<any> {
|
|
|
145
145
|
generatedAt: new Date().toISOString(),
|
|
146
146
|
activeSessions: [],
|
|
147
147
|
archivedSessions: [],
|
|
148
|
-
liteTasks: { litePlan: [], liteFix: [] },
|
|
148
|
+
liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
|
|
149
149
|
reviewData: { dimensions: {} },
|
|
150
150
|
projectOverview: null,
|
|
151
151
|
statistics: {
|
|
@@ -155,7 +155,8 @@ async function getWorkflowData(projectPath: string): Promise<any> {
|
|
|
155
155
|
completedTasks: 0,
|
|
156
156
|
reviewFindings: 0,
|
|
157
157
|
litePlanCount: 0,
|
|
158
|
-
liteFixCount: 0
|
|
158
|
+
liteFixCount: 0,
|
|
159
|
+
multiCliPlanCount: 0
|
|
159
160
|
},
|
|
160
161
|
projectPath: normalizePathForDisplay(resolvedPath),
|
|
161
162
|
recentPaths: getRecentPaths()
|
package/ccw/src/core/server.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { handleLiteLLMApiRoutes } from './routes/litellm-api-routes.js';
|
|
|
29
29
|
import { handleNavStatusRoutes } from './routes/nav-status-routes.js';
|
|
30
30
|
import { handleAuthRoutes } from './routes/auth-routes.js';
|
|
31
31
|
import { handleLoopRoutes } from './routes/loop-routes.js';
|
|
32
|
-
import { handleLoopV2Routes } from './routes/loop-v2-routes.js';
|
|
32
|
+
import { handleLoopV2Routes, initializeCliToolsCache } from './routes/loop-v2-routes.js';
|
|
33
33
|
import { handleTestLoopRoutes } from './routes/test-loop-routes.js';
|
|
34
34
|
import { handleTaskRoutes } from './routes/task-routes.js';
|
|
35
35
|
|
|
@@ -383,10 +383,10 @@ function generateServerDashboard(initialPath: string): string {
|
|
|
383
383
|
generatedAt: new Date().toISOString(),
|
|
384
384
|
activeSessions: [],
|
|
385
385
|
archivedSessions: [],
|
|
386
|
-
liteTasks: { litePlan: [], liteFix: [] },
|
|
386
|
+
liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
|
|
387
387
|
reviewData: { dimensions: {} },
|
|
388
388
|
projectOverview: null,
|
|
389
|
-
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
|
389
|
+
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0, multiCliPlanCount: 0 }
|
|
390
390
|
};
|
|
391
391
|
|
|
392
392
|
// Replace JS placeholders
|
|
@@ -723,6 +723,9 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
|
|
|
723
723
|
console.log(`WebSocket endpoint available at ws://${host}:${serverPort}/ws`);
|
|
724
724
|
console.log(`Hook endpoint available at POST http://${host}:${serverPort}/api/hook`);
|
|
725
725
|
|
|
726
|
+
// Initialize CLI tools cache for Loop V2 routes
|
|
727
|
+
initializeCliToolsCache();
|
|
728
|
+
|
|
726
729
|
// Start periodic cleanup of stale CLI executions (every 2 minutes)
|
|
727
730
|
const CLEANUP_INTERVAL_MS = 2 * 60 * 1000;
|
|
728
731
|
const cleanupInterval = setInterval(cleanupStaleExecutions, CLEANUP_INTERVAL_MS);
|
|
@@ -464,6 +464,7 @@
|
|
|
464
464
|
display: flex;
|
|
465
465
|
flex-direction: column;
|
|
466
466
|
gap: 1rem;
|
|
467
|
+
align-items: flex-start;
|
|
467
468
|
}
|
|
468
469
|
|
|
469
470
|
.btn-back {
|
|
@@ -492,6 +493,7 @@
|
|
|
492
493
|
.detail-title-row {
|
|
493
494
|
display: flex;
|
|
494
495
|
align-items: center;
|
|
496
|
+
justify-content: flex-start;
|
|
495
497
|
gap: 1rem;
|
|
496
498
|
flex-wrap: wrap;
|
|
497
499
|
}
|