claude-code-workflow 6.3.26 → 6.3.28

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 (129) hide show
  1. package/.claude/CLAUDE.md +7 -1
  2. package/.claude/agents/action-planning-agent.md +1 -0
  3. package/.claude/agents/cli-discuss-agent.md +391 -0
  4. package/.claude/agents/cli-execution-agent.md +2 -0
  5. package/.claude/agents/cli-explore-agent.md +2 -1
  6. package/.claude/agents/cli-lite-planning-agent.md +1 -0
  7. package/.claude/agents/cli-planning-agent.md +1 -0
  8. package/.claude/agents/code-developer.md +1 -0
  9. package/.claude/agents/conceptual-planning-agent.md +2 -0
  10. package/.claude/agents/context-search-agent.md +1 -0
  11. package/.claude/agents/debug-explore-agent.md +2 -0
  12. package/.claude/agents/doc-generator.md +1 -0
  13. package/.claude/agents/issue-plan-agent.md +2 -1
  14. package/.claude/agents/issue-queue-agent.md +2 -1
  15. package/.claude/agents/memory-bridge.md +2 -0
  16. package/.claude/agents/test-context-search-agent.md +2 -0
  17. package/.claude/agents/test-fix-agent.md +1 -0
  18. package/.claude/agents/ui-design-agent.md +2 -0
  19. package/.claude/agents/universal-executor.md +1 -0
  20. package/.claude/commands/issue/execute.md +141 -163
  21. package/.claude/commands/workflow/lite-lite-lite.md +798 -0
  22. package/.claude/commands/workflow/multi-cli-plan.md +510 -0
  23. package/.claude/skills/ccw/SKILL.md +262 -372
  24. package/.claude/skills/ccw/command.json +547 -0
  25. package/.claude/skills/ccw-help/SKILL.md +46 -107
  26. package/.claude/skills/ccw-help/command.json +511 -0
  27. package/.claude/skills/skill-tuning/SKILL.md +303 -0
  28. package/.claude/skills/skill-tuning/phases/actions/action-abort.md +164 -0
  29. package/.claude/skills/skill-tuning/phases/actions/action-analyze-requirements.md +406 -0
  30. package/.claude/skills/skill-tuning/phases/actions/action-apply-fix.md +206 -0
  31. package/.claude/skills/skill-tuning/phases/actions/action-complete.md +195 -0
  32. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-agent.md +317 -0
  33. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-context.md +243 -0
  34. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-dataflow.md +318 -0
  35. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-docs.md +299 -0
  36. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-memory.md +269 -0
  37. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-token-consumption.md +200 -0
  38. package/.claude/skills/skill-tuning/phases/actions/action-gemini-analysis.md +322 -0
  39. package/.claude/skills/skill-tuning/phases/actions/action-generate-report.md +228 -0
  40. package/.claude/skills/skill-tuning/phases/actions/action-init.md +149 -0
  41. package/.claude/skills/skill-tuning/phases/actions/action-propose-fixes.md +317 -0
  42. package/.claude/skills/skill-tuning/phases/actions/action-verify.md +222 -0
  43. package/.claude/skills/skill-tuning/phases/orchestrator.md +377 -0
  44. package/.claude/skills/skill-tuning/phases/state-schema.md +378 -0
  45. package/.claude/skills/skill-tuning/specs/category-mappings.json +284 -0
  46. package/.claude/skills/skill-tuning/specs/dimension-mapping.md +212 -0
  47. package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +318 -0
  48. package/.claude/skills/skill-tuning/specs/quality-gates.md +263 -0
  49. package/.claude/skills/skill-tuning/specs/skill-authoring-principles.md +189 -0
  50. package/.claude/skills/skill-tuning/specs/tuning-strategies.md +1537 -0
  51. package/.claude/skills/skill-tuning/templates/diagnosis-report.md +153 -0
  52. package/.claude/skills/skill-tuning/templates/fix-proposal.md +204 -0
  53. package/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json +421 -0
  54. package/.claude/workflows/cli-tools-usage.md +0 -41
  55. package/ccw/dist/core/auth/csrf-middleware.d.ts.map +1 -1
  56. package/ccw/dist/core/auth/csrf-middleware.js +3 -1
  57. package/ccw/dist/core/auth/csrf-middleware.js.map +1 -1
  58. package/ccw/dist/core/data-aggregator.d.ts +2 -0
  59. package/ccw/dist/core/data-aggregator.d.ts.map +1 -1
  60. package/ccw/dist/core/data-aggregator.js +5 -2
  61. package/ccw/dist/core/data-aggregator.js.map +1 -1
  62. package/ccw/dist/core/lite-scanner.d.ts +2 -1
  63. package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
  64. package/ccw/dist/core/lite-scanner.js +295 -6
  65. package/ccw/dist/core/lite-scanner.js.map +1 -1
  66. package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
  67. package/ccw/dist/core/routes/codexlens/config-handlers.js +5 -5
  68. package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
  69. package/ccw/dist/core/routes/session-routes.d.ts.map +1 -1
  70. package/ccw/dist/core/routes/session-routes.js +166 -48
  71. package/ccw/dist/core/routes/session-routes.js.map +1 -1
  72. package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
  73. package/ccw/dist/core/routes/system-routes.js +87 -0
  74. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  75. package/ccw/dist/core/server.js +2 -2
  76. package/ccw/dist/core/server.js.map +1 -1
  77. package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +226 -0
  78. package/ccw/scripts/QUICK-REFERENCE.md +135 -0
  79. package/ccw/scripts/README-memory-embedder.md +157 -0
  80. package/ccw/scripts/__pycache__/memory_embedder.cpython-313.pyc +0 -0
  81. package/ccw/scripts/__pycache__/test_memory_embedder.cpython-313-pytest-8.4.2.pyc +0 -0
  82. package/ccw/scripts/memory-embedder-example.ts +184 -0
  83. package/ccw/scripts/memory_embedder.py +428 -0
  84. package/ccw/scripts/test_memory_embedder.py +245 -0
  85. package/ccw/src/core/auth/csrf-middleware.ts +3 -1
  86. package/ccw/src/core/data-aggregator.ts +7 -2
  87. package/ccw/src/core/lite-scanner.ts +440 -6
  88. package/ccw/src/core/routes/codexlens/config-handlers.ts +12 -9
  89. package/ccw/src/core/routes/session-routes.ts +201 -48
  90. package/ccw/src/core/routes/system-routes.ts +102 -0
  91. package/ccw/src/core/server.ts +2 -2
  92. package/ccw/src/templates/dashboard-css/01-base.css +8 -0
  93. package/ccw/src/templates/dashboard-css/02-session.css +81 -0
  94. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +2442 -0
  95. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +157 -0
  96. package/ccw/src/templates/dashboard-css/32-issue-manager.css +23 -0
  97. package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +38 -4
  98. package/ccw/src/templates/dashboard-js/components/hook-manager.js +38 -13
  99. package/ccw/src/templates/dashboard-js/components/navigation.js +24 -4
  100. package/ccw/src/templates/dashboard-js/i18n.js +194 -6
  101. package/ccw/src/templates/dashboard-js/views/api-settings.js +32 -0
  102. package/ccw/src/templates/dashboard-js/views/claude-manager.js +44 -3
  103. package/ccw/src/templates/dashboard-js/views/cli-manager.js +303 -31
  104. package/ccw/src/templates/dashboard-js/views/history.js +44 -6
  105. package/ccw/src/templates/dashboard-js/views/home.js +1 -0
  106. package/ccw/src/templates/dashboard-js/views/issue-manager.js +54 -7
  107. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +1817 -4
  108. package/ccw/src/templates/dashboard.html +5 -0
  109. package/package.json +2 -1
  110. package/.claude/skills/ccw/index/command-capabilities.json +0 -127
  111. package/.claude/skills/ccw/index/intent-rules.json +0 -136
  112. package/.claude/skills/ccw/index/workflow-chains.json +0 -451
  113. package/.claude/skills/ccw/phases/actions/bugfix.md +0 -218
  114. package/.claude/skills/ccw/phases/actions/coupled.md +0 -194
  115. package/.claude/skills/ccw/phases/actions/docs.md +0 -93
  116. package/.claude/skills/ccw/phases/actions/full.md +0 -154
  117. package/.claude/skills/ccw/phases/actions/issue.md +0 -201
  118. package/.claude/skills/ccw/phases/actions/rapid.md +0 -104
  119. package/.claude/skills/ccw/phases/actions/review-fix.md +0 -84
  120. package/.claude/skills/ccw/phases/actions/tdd.md +0 -66
  121. package/.claude/skills/ccw/phases/actions/ui.md +0 -79
  122. package/.claude/skills/ccw/phases/orchestrator.md +0 -435
  123. package/.claude/skills/ccw/specs/intent-classification.md +0 -336
  124. package/.claude/skills/ccw-help/index/all-agents.json +0 -82
  125. package/.claude/skills/ccw-help/index/all-commands.json +0 -882
  126. package/.claude/skills/ccw-help/index/by-category.json +0 -914
  127. package/.claude/skills/ccw-help/index/by-use-case.json +0 -896
  128. package/.claude/skills/ccw-help/index/command-relationships.json +0 -160
  129. package/.claude/skills/ccw-help/index/essential-commands.json +0 -112
@@ -77,6 +77,7 @@ interface DashboardData {
77
77
  liteTasks: {
78
78
  litePlan: unknown[];
79
79
  liteFix: unknown[];
80
+ multiCliPlan: unknown[];
80
81
  };
81
82
  reviewData: ReviewData | null;
82
83
  projectOverview: ProjectOverview | null;
@@ -88,6 +89,7 @@ interface DashboardData {
88
89
  reviewFindings: number;
89
90
  litePlanCount: number;
90
91
  liteFixCount: number;
92
+ multiCliPlanCount: number;
91
93
  };
92
94
  }
93
95
 
@@ -211,7 +213,8 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
211
213
  archivedSessions: [],
212
214
  liteTasks: {
213
215
  litePlan: [],
214
- liteFix: []
216
+ liteFix: [],
217
+ multiCliPlan: []
215
218
  },
216
219
  reviewData: null,
217
220
  projectOverview: null,
@@ -222,7 +225,8 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
222
225
  completedTasks: 0,
223
226
  reviewFindings: 0,
224
227
  litePlanCount: 0,
225
- liteFixCount: 0
228
+ liteFixCount: 0,
229
+ multiCliPlanCount: 0
226
230
  }
227
231
  };
228
232
 
@@ -257,6 +261,7 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
257
261
  data.liteTasks = liteTasks;
258
262
  data.statistics.litePlanCount = liteTasks.litePlan.length;
259
263
  data.statistics.liteFixCount = liteTasks.liteFix.length;
264
+ data.statistics.multiCliPlanCount = liteTasks.multiCliPlan.length;
260
265
  } catch (err) {
261
266
  console.error('Error scanning lite tasks:', (err as Error).message);
262
267
  }
@@ -60,9 +60,47 @@ interface LiteSession {
60
60
  progress: Progress;
61
61
  }
62
62
 
63
+ // Multi-CLI specific session state from session-state.json
64
+ interface MultiCliSessionState {
65
+ session_id: string;
66
+ task_description: string;
67
+ status: string;
68
+ current_phase: number;
69
+ phases: Record<string, { status: string; rounds_completed?: number }>;
70
+ ace_context?: { relevant_files: string[]; detected_patterns: string[] };
71
+ user_decisions?: Array<{ round: number; decision: string; selected: string }>;
72
+ updated_at?: string;
73
+ }
74
+
75
+ // Discussion topic structure for frontend rendering
76
+ interface DiscussionTopic {
77
+ title: string;
78
+ description: string;
79
+ scope: { included: string[]; excluded: string[] };
80
+ keyQuestions: string[];
81
+ status: string;
82
+ tags: string[];
83
+ }
84
+
85
+ // Extended session interface for multi-cli-plan
86
+ interface MultiCliSession extends LiteSession {
87
+ roundCount: number;
88
+ topicTitle: string;
89
+ status: string;
90
+ metadata: {
91
+ roundId: number;
92
+ timestamp: string;
93
+ currentPhase: number;
94
+ };
95
+ discussionTopic: DiscussionTopic;
96
+ rounds: RoundSynthesis[];
97
+ latestSynthesis: RoundSynthesis | null;
98
+ }
99
+
63
100
  interface LiteTasks {
64
101
  litePlan: LiteSession[];
65
102
  liteFix: LiteSession[];
103
+ multiCliPlan: LiteSession[];
66
104
  }
67
105
 
68
106
  interface LiteTaskDetail {
@@ -84,13 +122,15 @@ interface LiteTaskDetail {
84
122
  export async function scanLiteTasks(workflowDir: string): Promise<LiteTasks> {
85
123
  const litePlanDir = join(workflowDir, '.lite-plan');
86
124
  const liteFixDir = join(workflowDir, '.lite-fix');
125
+ const multiCliDir = join(workflowDir, '.multi-cli-plan');
87
126
 
88
- const [litePlan, liteFix] = await Promise.all([
127
+ const [litePlan, liteFix, multiCliPlan] = await Promise.all([
89
128
  scanLiteDir(litePlanDir, 'lite-plan'),
90
129
  scanLiteDir(liteFixDir, 'lite-fix'),
130
+ scanMultiCliDir(multiCliDir),
91
131
  ]);
92
132
 
93
- return { litePlan, liteFix };
133
+ return { litePlan, liteFix, multiCliPlan };
94
134
  }
95
135
 
96
136
  /**
@@ -142,6 +182,372 @@ async function scanLiteDir(dir: string, type: string): Promise<LiteSession[]> {
142
182
  }
143
183
  }
144
184
 
185
+ /**
186
+ * Load session-state.json from multi-cli session directory
187
+ * @param sessionPath - Session directory path
188
+ * @returns Session state or null if not found
189
+ */
190
+ async function loadSessionState(sessionPath: string): Promise<MultiCliSessionState | null> {
191
+ const statePath = join(sessionPath, 'session-state.json');
192
+ try {
193
+ const content = await readFile(statePath, 'utf8');
194
+ return JSON.parse(content);
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Build discussion topic structure from session state and synthesis
202
+ * @param state - Session state from session-state.json
203
+ * @param synthesis - Latest round synthesis
204
+ * @returns Discussion topic for frontend rendering
205
+ */
206
+ function buildDiscussionTopic(
207
+ state: MultiCliSessionState | null,
208
+ synthesis: RoundSynthesis | null
209
+ ): DiscussionTopic {
210
+ const keyQuestions = synthesis?.clarification_questions || [];
211
+ const solutions = synthesis?.solutions || [];
212
+
213
+ return {
214
+ title: state?.task_description || 'Discussion Topic',
215
+ description: solutions[0]?.summary || '',
216
+ scope: {
217
+ included: state?.ace_context?.relevant_files || [],
218
+ excluded: [],
219
+ },
220
+ keyQuestions,
221
+ status: state?.status || 'analyzing',
222
+ tags: solutions.map((s) => s.name).slice(0, 3),
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Scan multi-cli-plan directory for sessions
228
+ * @param dir - Directory path to .multi-cli-plan
229
+ * @returns Array of multi-cli sessions with extended metadata
230
+ */
231
+ async function scanMultiCliDir(dir: string): Promise<MultiCliSession[]> {
232
+ try {
233
+ const entries = await readdir(dir, { withFileTypes: true });
234
+
235
+ const sessions = (await Promise.all(
236
+ entries
237
+ .filter((entry) => entry.isDirectory())
238
+ .map(async (entry) => {
239
+ const sessionPath = join(dir, entry.name);
240
+
241
+ const [createdAt, syntheses, sessionState] = await Promise.all([
242
+ getCreatedTime(sessionPath),
243
+ loadRoundSyntheses(sessionPath),
244
+ loadSessionState(sessionPath),
245
+ ]);
246
+
247
+ // Extract data from syntheses
248
+ const roundCount = syntheses.length;
249
+ const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null;
250
+
251
+ // Calculate progress based on round count and convergence
252
+ const progress = calculateMultiCliProgress(syntheses);
253
+
254
+ // Build discussion topic for frontend
255
+ const discussionTopic = buildDiscussionTopic(sessionState, latestSynthesis);
256
+
257
+ // Determine status from session state or synthesis convergence
258
+ const status = sessionState?.status ||
259
+ (latestSynthesis?.convergence?.recommendation === 'converged' ? 'converged' : 'analyzing');
260
+
261
+ const session: MultiCliSession = {
262
+ id: entry.name,
263
+ type: 'multi-cli-plan',
264
+ path: sessionPath,
265
+ createdAt,
266
+ plan: latestSynthesis,
267
+ tasks: extractTasksFromSyntheses(syntheses),
268
+ progress,
269
+ // Extended multi-cli specific fields
270
+ roundCount,
271
+ topicTitle: sessionState?.task_description || 'Discussion Topic',
272
+ status,
273
+ metadata: {
274
+ roundId: roundCount,
275
+ timestamp: sessionState?.updated_at || createdAt,
276
+ currentPhase: sessionState?.current_phase || 1,
277
+ },
278
+ discussionTopic,
279
+ rounds: syntheses,
280
+ latestSynthesis,
281
+ };
282
+
283
+ return session;
284
+ }),
285
+ ))
286
+ .filter((session): session is MultiCliSession => session !== null)
287
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
288
+
289
+ return sessions;
290
+ } catch (err: any) {
291
+ if (err?.code === 'ENOENT') return [];
292
+ console.error(`Error scanning ${dir}:`, err?.message || String(err));
293
+ return [];
294
+ }
295
+ }
296
+
297
+ // NEW Schema types for multi-cli synthesis
298
+ interface SolutionFileAction {
299
+ file: string;
300
+ line: number;
301
+ action: 'modify' | 'create' | 'delete';
302
+ }
303
+
304
+ interface SolutionTask {
305
+ id: string;
306
+ name: string;
307
+ depends_on: string[];
308
+ files: SolutionFileAction[];
309
+ key_point: string | null;
310
+ }
311
+
312
+ interface SolutionImplementationPlan {
313
+ approach: string;
314
+ tasks: SolutionTask[];
315
+ execution_flow: string;
316
+ milestones: string[];
317
+ }
318
+
319
+ interface SolutionDependencies {
320
+ internal: string[];
321
+ external: string[];
322
+ }
323
+
324
+ interface Solution {
325
+ name: string;
326
+ source_cli: string[];
327
+ feasibility: number; // 0-1
328
+ effort: 'low' | 'medium' | 'high';
329
+ risk: 'low' | 'medium' | 'high';
330
+ summary: string;
331
+ implementation_plan: SolutionImplementationPlan;
332
+ dependencies: SolutionDependencies;
333
+ technical_concerns: string[];
334
+ }
335
+
336
+ interface SynthesisConvergence {
337
+ score: number;
338
+ new_insights: boolean;
339
+ recommendation: 'converged' | 'continue' | 'user_input_needed';
340
+ }
341
+
342
+ interface SynthesisCrossVerification {
343
+ agreements: string[];
344
+ disagreements: string[];
345
+ resolution: string;
346
+ }
347
+
348
+ interface RoundSynthesis {
349
+ round: number;
350
+ // NEW schema fields
351
+ solutions?: Solution[];
352
+ convergence?: SynthesisConvergence;
353
+ cross_verification?: SynthesisCrossVerification;
354
+ clarification_questions?: string[];
355
+ // OLD schema fields (backward compatibility)
356
+ converged?: boolean;
357
+ tasks?: unknown[];
358
+ synthesis?: unknown;
359
+ [key: string]: unknown;
360
+ }
361
+
362
+ /**
363
+ * Load all synthesis.json files from rounds subdirectories
364
+ * @param sessionPath - Session directory path
365
+ * @returns Array of synthesis objects sorted by round number
366
+ */
367
+ async function loadRoundSyntheses(sessionPath: string): Promise<RoundSynthesis[]> {
368
+ const roundsDir = join(sessionPath, 'rounds');
369
+ const syntheses: RoundSynthesis[] = [];
370
+
371
+ try {
372
+ const roundEntries = await readdir(roundsDir, { withFileTypes: true });
373
+
374
+ const roundDirs = roundEntries
375
+ .filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
376
+ .map((entry) => ({
377
+ name: entry.name,
378
+ num: parseInt(entry.name, 10),
379
+ }))
380
+ .sort((a, b) => a.num - b.num);
381
+
382
+ for (const roundDir of roundDirs) {
383
+ const synthesisPath = join(roundsDir, roundDir.name, 'synthesis.json');
384
+ try {
385
+ const content = await readFile(synthesisPath, 'utf8');
386
+ const synthesis = JSON.parse(content) as RoundSynthesis;
387
+ synthesis.round = roundDir.num;
388
+ syntheses.push(synthesis);
389
+ } catch (e) {
390
+ console.warn('Failed to parse synthesis file:', synthesisPath, (e as Error).message);
391
+ }
392
+ }
393
+ } catch (e) {
394
+ // Ignore ENOENT errors (directory doesn't exist), warn on others
395
+ if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {
396
+ console.warn('Failed to read rounds directory:', roundsDir, (e as Error).message);
397
+ }
398
+ }
399
+
400
+ return syntheses;
401
+ }
402
+
403
+ // Extended Progress interface for multi-cli sessions
404
+ interface MultiCliProgress extends Progress {
405
+ convergenceScore?: number;
406
+ recommendation?: 'converged' | 'continue' | 'user_input_needed';
407
+ solutionsCount?: number;
408
+ avgFeasibility?: number;
409
+ }
410
+
411
+ /**
412
+ * Calculate progress for multi-cli-plan sessions
413
+ * Uses new convergence.score and convergence.recommendation when available
414
+ * Falls back to old converged boolean for backward compatibility
415
+ * @param syntheses - Array of round syntheses
416
+ * @returns Progress info with convergence metrics
417
+ */
418
+ function calculateMultiCliProgress(syntheses: RoundSynthesis[]): MultiCliProgress {
419
+ if (syntheses.length === 0) {
420
+ return { total: 0, completed: 0, percentage: 0 };
421
+ }
422
+
423
+ const latestSynthesis = syntheses[syntheses.length - 1];
424
+
425
+ // NEW schema: Use convergence object
426
+ if (latestSynthesis.convergence) {
427
+ const { score, recommendation } = latestSynthesis.convergence;
428
+ const isConverged = recommendation === 'converged';
429
+
430
+ // Calculate solutions metrics
431
+ const solutions = latestSynthesis.solutions || [];
432
+ const solutionsCount = solutions.length;
433
+ const avgFeasibility = solutionsCount > 0
434
+ ? solutions.reduce((sum, s) => sum + (s.feasibility || 0), 0) / solutionsCount
435
+ : 0;
436
+
437
+ // Total is based on rounds, percentage derived from convergence score
438
+ const total = syntheses.length;
439
+ const completed = isConverged ? total : Math.max(0, total - 1);
440
+ const percentage = isConverged ? 100 : Math.round(score * 100);
441
+
442
+ return {
443
+ total,
444
+ completed,
445
+ percentage,
446
+ convergenceScore: score,
447
+ recommendation,
448
+ solutionsCount,
449
+ avgFeasibility: Math.round(avgFeasibility * 100) / 100
450
+ };
451
+ }
452
+
453
+ // OLD schema: Fallback to converged boolean
454
+ const isConverged = latestSynthesis.converged === true;
455
+ const total = syntheses.length;
456
+ const completed = isConverged ? total : Math.max(0, total - 1);
457
+ const percentage = isConverged ? 100 : Math.round((completed / Math.max(total, 1)) * 100);
458
+
459
+ return { total, completed, percentage };
460
+ }
461
+
462
+ /**
463
+ * Extract tasks from synthesis objects
464
+ * NEW schema: Extract from solutions[].implementation_plan.tasks
465
+ * OLD schema: Extract from tasks[] array directly
466
+ * @param syntheses - Array of round syntheses
467
+ * @returns Normalized tasks from latest synthesis
468
+ */
469
+ function extractTasksFromSyntheses(syntheses: RoundSynthesis[]): NormalizedTask[] {
470
+ if (syntheses.length === 0) return [];
471
+
472
+ const latestSynthesis = syntheses[syntheses.length - 1];
473
+
474
+ // NEW schema: Extract tasks from solutions
475
+ if (latestSynthesis.solutions && Array.isArray(latestSynthesis.solutions)) {
476
+ const allTasks: NormalizedTask[] = [];
477
+
478
+ for (const solution of latestSynthesis.solutions) {
479
+ const implPlan = solution.implementation_plan;
480
+ if (!implPlan?.tasks || !Array.isArray(implPlan.tasks)) continue;
481
+
482
+ for (const task of implPlan.tasks) {
483
+ const normalizedTask = normalizeSolutionTask(task, solution);
484
+ if (normalizedTask) {
485
+ allTasks.push(normalizedTask);
486
+ }
487
+ }
488
+ }
489
+
490
+ // Sort by task ID
491
+ return allTasks.sort((a, b) => {
492
+ const aNum = parseInt(a.id?.replace(/\D/g, '') || '0');
493
+ const bNum = parseInt(b.id?.replace(/\D/g, '') || '0');
494
+ return aNum - bNum;
495
+ });
496
+ }
497
+
498
+ // OLD schema: Extract from tasks array directly
499
+ const tasks = latestSynthesis.tasks;
500
+ if (!Array.isArray(tasks)) return [];
501
+
502
+ return tasks
503
+ .map((task) => normalizeTask(task))
504
+ .filter((task): task is NormalizedTask => task !== null);
505
+ }
506
+
507
+ /**
508
+ * Normalize a solution task from NEW schema to NormalizedTask
509
+ * @param task - SolutionTask from new schema
510
+ * @param solution - Parent solution for context
511
+ * @returns Normalized task
512
+ */
513
+ function normalizeSolutionTask(task: SolutionTask, solution: Solution): NormalizedTask | null {
514
+ if (!task || !task.id) return null;
515
+
516
+ return {
517
+ id: task.id,
518
+ title: task.name || 'Untitled Task',
519
+ status: (task as unknown as { status?: string }).status || 'pending',
520
+ meta: {
521
+ type: 'implementation',
522
+ agent: null,
523
+ scope: solution.name || null,
524
+ module: null
525
+ },
526
+ context: {
527
+ requirements: task.key_point ? [task.key_point] : [],
528
+ focus_paths: task.files?.map(f => f.file) || [],
529
+ acceptance: [],
530
+ depends_on: task.depends_on || []
531
+ },
532
+ flow_control: {
533
+ implementation_approach: task.files?.map((f, i) => ({
534
+ step: `Step ${i + 1}`,
535
+ action: `${f.action} ${f.file}${f.line ? ` at line ${f.line}` : ''}`
536
+ })) || []
537
+ },
538
+ _raw: {
539
+ task,
540
+ solution: {
541
+ name: solution.name,
542
+ source_cli: solution.source_cli,
543
+ feasibility: solution.feasibility,
544
+ effort: solution.effort,
545
+ risk: solution.risk
546
+ }
547
+ }
548
+ };
549
+ }
550
+
145
551
  /**
146
552
  * Load plan.json or fix-plan.json from session directory
147
553
  * @param sessionPath - Session directory path
@@ -368,14 +774,19 @@ function calculateProgress(tasks: NormalizedTask[]): Progress {
368
774
  /**
369
775
  * Get detailed lite task info
370
776
  * @param workflowDir - Workflow directory
371
- * @param type - 'lite-plan' or 'lite-fix'
777
+ * @param type - 'lite-plan', 'lite-fix', or 'multi-cli-plan'
372
778
  * @param sessionId - Session ID
373
779
  * @returns Detailed task info
374
780
  */
375
781
  export async function getLiteTaskDetail(workflowDir: string, type: string, sessionId: string): Promise<LiteTaskDetail | null> {
376
- const dir = type === 'lite-plan'
377
- ? join(workflowDir, '.lite-plan', sessionId)
378
- : join(workflowDir, '.lite-fix', sessionId);
782
+ let dir: string;
783
+ if (type === 'lite-plan') {
784
+ dir = join(workflowDir, '.lite-plan', sessionId);
785
+ } else if (type === 'multi-cli-plan') {
786
+ dir = join(workflowDir, '.multi-cli-plan', sessionId);
787
+ } else {
788
+ dir = join(workflowDir, '.lite-fix', sessionId);
789
+ }
379
790
 
380
791
  try {
381
792
  const stats = await stat(dir);
@@ -384,6 +795,29 @@ export async function getLiteTaskDetail(workflowDir: string, type: string, sessi
384
795
  return null;
385
796
  }
386
797
 
798
+ // For multi-cli-plan, use synthesis-based loading
799
+ if (type === 'multi-cli-plan') {
800
+ const [syntheses, explorations, clarifications] = await Promise.all([
801
+ loadRoundSyntheses(dir),
802
+ loadExplorations(dir),
803
+ loadClarifications(dir),
804
+ ]);
805
+
806
+ const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null;
807
+
808
+ const detail: LiteTaskDetail = {
809
+ id: sessionId,
810
+ type,
811
+ path: dir,
812
+ plan: latestSynthesis,
813
+ tasks: extractTasksFromSyntheses(syntheses),
814
+ explorations,
815
+ clarifications,
816
+ };
817
+
818
+ return detail;
819
+ }
820
+
387
821
  const [plan, tasks, explorations, clarifications, diagnoses] = await Promise.all([
388
822
  loadPlanJson(dir),
389
823
  loadTaskJsons(dir),
@@ -451,18 +451,21 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise<bo
451
451
  const devices: Array<{ name: string; type: string; index: number }> = [];
452
452
 
453
453
  if (process.platform === 'win32') {
454
- // Windows: Use WMIC to get GPU info
454
+ // Windows: Use PowerShell Get-CimInstance (wmic is deprecated in Windows 11)
455
455
  try {
456
456
  const { execSync } = await import('child_process');
457
- const wmicOutput = execSync('wmic path win32_VideoController get name', {
458
- encoding: 'utf-8',
459
- timeout: EXEC_TIMEOUTS.SYSTEM_INFO,
460
- stdio: ['pipe', 'pipe', 'pipe']
461
- });
457
+ const psOutput = execSync(
458
+ 'powershell -NoProfile -Command "(Get-CimInstance Win32_VideoController).Name"',
459
+ {
460
+ encoding: 'utf-8',
461
+ timeout: EXEC_TIMEOUTS.SYSTEM_INFO,
462
+ stdio: ['pipe', 'pipe', 'pipe']
463
+ }
464
+ );
462
465
 
463
- const lines = wmicOutput.split('\n')
466
+ const lines = psOutput.split('\n')
464
467
  .map(line => line.trim())
465
- .filter(line => line && line !== 'Name');
468
+ .filter(line => line);
466
469
 
467
470
  lines.forEach((name, index) => {
468
471
  if (name) {
@@ -476,7 +479,7 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise<bo
476
479
  }
477
480
  });
478
481
  } catch (e) {
479
- console.warn('[CodexLens] WMIC GPU detection failed:', (e as Error).message);
482
+ console.warn('[CodexLens] PowerShell GPU detection failed:', (e as Error).message);
480
483
  }
481
484
  } else {
482
485
  // Linux/Mac: Try nvidia-smi for NVIDIA GPUs