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
@@ -7,9 +7,17 @@ function renderLiteTasks() {
7
7
  const container = document.getElementById('mainContent');
8
8
 
9
9
  const liteTasks = workflowData.liteTasks || {};
10
- const sessions = currentLiteType === 'lite-plan'
11
- ? liteTasks.litePlan || []
12
- : liteTasks.liteFix || [];
10
+ let sessions;
11
+
12
+ if (currentLiteType === 'lite-plan') {
13
+ sessions = liteTasks.litePlan || [];
14
+ } else if (currentLiteType === 'lite-fix') {
15
+ sessions = liteTasks.liteFix || [];
16
+ } else if (currentLiteType === 'multi-cli-plan') {
17
+ sessions = liteTasks.multiCliPlan || [];
18
+ } else {
19
+ sessions = [];
20
+ }
13
21
 
14
22
  if (sessions.length === 0) {
15
23
  container.innerHTML = `
@@ -23,7 +31,12 @@ function renderLiteTasks() {
23
31
  return;
24
32
  }
25
33
 
26
- container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderLiteTaskCard(session)).join('')}</div>`;
34
+ // Render based on type
35
+ if (currentLiteType === 'multi-cli-plan') {
36
+ container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderMultiCliCard(session)).join('')}</div>`;
37
+ } else {
38
+ container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderLiteTaskCard(session)).join('')}</div>`;
39
+ }
27
40
 
28
41
  // Initialize Lucide icons
29
42
  if (typeof lucide !== 'undefined') lucide.createIcons();
@@ -68,6 +81,1806 @@ function renderLiteTaskCard(session) {
68
81
  `;
69
82
  }
70
83
 
84
+ // ============================================
85
+ // MULTI-CLI PLAN VIEW
86
+ // ============================================
87
+
88
+ /**
89
+ * Render a card for multi-cli-plan session
90
+ * Shows: Session ID, round count, topic title, status, created date
91
+ */
92
+ function renderMultiCliCard(session) {
93
+ const sessionKey = `multi-cli-${session.id}`.replace(/[^a-zA-Z0-9-]/g, '-');
94
+ liteTaskDataStore[sessionKey] = session;
95
+
96
+ // Extract info from latest synthesis or metadata
97
+ const metadata = session.metadata || {};
98
+ const latestSynthesis = session.latestSynthesis || session.discussionTopic || {};
99
+ const roundCount = metadata.roundId || session.roundCount || 1;
100
+ const topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
101
+ const status = latestSynthesis.status || session.status || 'analyzing';
102
+ const createdAt = metadata.timestamp || session.createdAt || '';
103
+
104
+ // Status badge color mapping
105
+ const statusColors = {
106
+ 'decided': 'success',
107
+ 'converged': 'success',
108
+ 'plan_generated': 'success',
109
+ 'completed': 'success',
110
+ 'exploring': 'info',
111
+ 'initialized': 'info',
112
+ 'analyzing': 'warning',
113
+ 'debating': 'warning',
114
+ 'blocked': 'error',
115
+ 'conflict': 'error'
116
+ };
117
+ const statusColor = statusColors[status] || 'default';
118
+
119
+ return `
120
+ <div class="session-card multi-cli-card" onclick="showMultiCliDetailPage('${sessionKey}')" style="cursor: pointer;">
121
+ <div class="session-header">
122
+ <div class="session-title">${escapeHtml(session.id)}</div>
123
+ <span class="session-status multi-cli-plan">
124
+ <i data-lucide="messages-square" class="w-3 h-3 inline"></i> ${t('lite.multiCli') || 'Multi-CLI'}
125
+ </span>
126
+ </div>
127
+ <div class="session-body">
128
+ <div class="multi-cli-topic">
129
+ <i data-lucide="message-circle" class="w-4 h-4 inline mr-1"></i>
130
+ <span class="topic-title">${escapeHtml(topicTitle)}</span>
131
+ </div>
132
+ <div class="session-meta">
133
+ <span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(createdAt)}</span>
134
+ <span class="session-meta-item"><i data-lucide="repeat" class="w-3.5 h-3.5 inline mr-1"></i>${roundCount} ${t('multiCli.rounds') || 'rounds'}</span>
135
+ <span class="session-meta-item status-badge ${statusColor}"><i data-lucide="activity" class="w-3.5 h-3.5 inline mr-1"></i>${escapeHtml(status)}</span>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ `;
140
+ }
141
+
142
+ /**
143
+ * Get text from i18n label object (supports {en, zh} format)
144
+ */
145
+ function getI18nText(label) {
146
+ if (!label) return '';
147
+ if (typeof label === 'string') return label;
148
+ // Return based on current language or default to English
149
+ const lang = window.currentLanguage || 'en';
150
+ return label[lang] || label.en || label.zh || '';
151
+ }
152
+
153
+ // ============================================
154
+ // SYNTHESIS DATA TRANSFORMATION HELPERS
155
+ // ============================================
156
+
157
+ /**
158
+ * Extract files from synthesis solutions[].implementation_plan.tasks[].files
159
+ * Returns object with fileTree and impactSummary arrays
160
+ */
161
+ function extractFilesFromSynthesis(synthesis) {
162
+ if (!synthesis || !synthesis.solutions) {
163
+ return { fileTree: [], impactSummary: [], dependencyGraph: [] };
164
+ }
165
+
166
+ const fileSet = new Set();
167
+ const impactMap = new Map();
168
+
169
+ synthesis.solutions.forEach(solution => {
170
+ const tasks = solution.implementation_plan?.tasks || [];
171
+ tasks.forEach(task => {
172
+ const files = task.files || [];
173
+ files.forEach(filePath => {
174
+ fileSet.add(filePath);
175
+ // Build impact summary based on task context
176
+ if (!impactMap.has(filePath)) {
177
+ impactMap.set(filePath, {
178
+ filePath: filePath,
179
+ score: 'medium',
180
+ reasoning: { en: `Part of ${solution.title?.en || solution.id} implementation`, zh: `${solution.title?.zh || solution.id} 实现的一部分` }
181
+ });
182
+ }
183
+ });
184
+ });
185
+ });
186
+
187
+ // Convert to fileTree format (flat list with file type)
188
+ const fileTree = Array.from(fileSet).map(path => ({
189
+ path: path,
190
+ type: 'file',
191
+ modificationStatus: 'modified',
192
+ impactScore: 'medium'
193
+ }));
194
+
195
+ return {
196
+ fileTree: fileTree,
197
+ impactSummary: Array.from(impactMap.values()),
198
+ dependencyGraph: []
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Extract planning data from synthesis solutions[].implementation_plan
204
+ * Builds planning object with functional requirements format
205
+ */
206
+ function extractPlanningFromSynthesis(synthesis) {
207
+ if (!synthesis || !synthesis.solutions) {
208
+ return { functional: [], nonFunctional: [], acceptanceCriteria: [] };
209
+ }
210
+
211
+ const functional = [];
212
+ const acceptanceCriteria = [];
213
+ let reqCounter = 1;
214
+ let acCounter = 1;
215
+
216
+ synthesis.solutions.forEach(solution => {
217
+ const plan = solution.implementation_plan;
218
+ if (!plan) return;
219
+
220
+ // Extract approach as functional requirement
221
+ if (plan.approach) {
222
+ functional.push({
223
+ id: `FR-${String(reqCounter++).padStart(3, '0')}`,
224
+ description: plan.approach,
225
+ priority: solution.feasibility?.score >= 0.8 ? 'high' : 'medium',
226
+ source: solution.id
227
+ });
228
+ }
229
+
230
+ // Extract tasks as acceptance criteria
231
+ const tasks = plan.tasks || [];
232
+ tasks.forEach(task => {
233
+ acceptanceCriteria.push({
234
+ id: `AC-${String(acCounter++).padStart(3, '0')}`,
235
+ description: task.title || { en: task.id, zh: task.id },
236
+ isMet: false
237
+ });
238
+ });
239
+ });
240
+
241
+ return {
242
+ functional: functional,
243
+ nonFunctional: [],
244
+ acceptanceCriteria: acceptanceCriteria
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Extract decision data from synthesis solutions
250
+ * Sorts by feasibility score, returns highest as selected, rest as rejected
251
+ */
252
+ function extractDecisionFromSynthesis(synthesis) {
253
+ if (!synthesis || !synthesis.solutions || synthesis.solutions.length === 0) {
254
+ return {};
255
+ }
256
+
257
+ // Sort solutions by feasibility score (highest first)
258
+ const sortedSolutions = [...synthesis.solutions].sort((a, b) => {
259
+ const scoreA = a.feasibility?.score || 0;
260
+ const scoreB = b.feasibility?.score || 0;
261
+ return scoreB - scoreA;
262
+ });
263
+
264
+ const selectedSolution = sortedSolutions[0];
265
+ const rejectedAlternatives = sortedSolutions.slice(1).map(sol => ({
266
+ ...sol,
267
+ rejectionReason: sol.cons?.length > 0 ? sol.cons[0] : { en: 'Lower feasibility score', zh: '可行性评分较低' }
268
+ }));
269
+
270
+ // Calculate confidence from convergence level
271
+ let confidenceScore = 0.5;
272
+ if (synthesis.convergence) {
273
+ const level = synthesis.convergence.level;
274
+ if (level === 'high' || level === 'converged') confidenceScore = 0.9;
275
+ else if (level === 'medium') confidenceScore = 0.7;
276
+ else if (level === 'low') confidenceScore = 0.4;
277
+ }
278
+
279
+ return {
280
+ status: synthesis.convergence?.recommendation || 'pending',
281
+ summary: synthesis.convergence?.summary || {},
282
+ selectedSolution: selectedSolution,
283
+ rejectedAlternatives: rejectedAlternatives,
284
+ confidenceScore: confidenceScore
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Extract timeline data from synthesis convergence and cross_verification
290
+ * Builds timeline array with events from discussion process
291
+ */
292
+ function extractTimelineFromSynthesis(synthesis) {
293
+ if (!synthesis) {
294
+ return [];
295
+ }
296
+
297
+ const timeline = [];
298
+ const now = new Date().toISOString();
299
+
300
+ // Add convergence summary as decision event
301
+ if (synthesis.convergence?.summary) {
302
+ timeline.push({
303
+ type: 'decision',
304
+ timestamp: now,
305
+ summary: synthesis.convergence.summary,
306
+ contributor: { name: 'Synthesis', id: 'synthesis' },
307
+ reversibility: synthesis.convergence.recommendation === 'proceed' ? 'irreversible' : 'reversible'
308
+ });
309
+ }
310
+
311
+ // Add cross-verification agreements as agreement events
312
+ const agreements = synthesis.cross_verification?.agreements || [];
313
+ agreements.forEach((agreement, idx) => {
314
+ timeline.push({
315
+ type: 'agreement',
316
+ timestamp: now,
317
+ summary: typeof agreement === 'string' ? { en: agreement, zh: agreement } : agreement,
318
+ contributor: { name: 'Cross-Verification', id: 'cross-verify' },
319
+ evidence: []
320
+ });
321
+ });
322
+
323
+ // Add cross-verification disagreements as disagreement events
324
+ const disagreements = synthesis.cross_verification?.disagreements || [];
325
+ disagreements.forEach((disagreement, idx) => {
326
+ timeline.push({
327
+ type: 'disagreement',
328
+ timestamp: now,
329
+ summary: typeof disagreement === 'string' ? { en: disagreement, zh: disagreement } : disagreement,
330
+ contributor: { name: 'Cross-Verification', id: 'cross-verify' },
331
+ evidence: []
332
+ });
333
+ });
334
+
335
+ // Add solutions as proposal events
336
+ const solutions = synthesis.solutions || [];
337
+ solutions.forEach(solution => {
338
+ timeline.push({
339
+ type: 'proposal',
340
+ timestamp: now,
341
+ summary: solution.description || solution.title || {},
342
+ contributor: { name: solution.id, id: solution.id },
343
+ evidence: solution.pros?.map(p => ({ type: 'pro', description: p })) || []
344
+ });
345
+ });
346
+
347
+ return timeline;
348
+ }
349
+
350
+ // Track multi-cli toolbar state
351
+ let isMultiCliToolbarExpanded = false;
352
+
353
+ /**
354
+ * Show multi-cli detail page with tabs
355
+ */
356
+ function showMultiCliDetailPage(sessionKey) {
357
+ const session = liteTaskDataStore[sessionKey];
358
+ if (!session) return;
359
+
360
+ currentView = 'multiCliDetail';
361
+ currentSessionDetailKey = sessionKey;
362
+
363
+ hideStatsAndCarousel();
364
+
365
+ const container = document.getElementById('mainContent');
366
+ const metadata = session.metadata || {};
367
+ const discussionTopic = session.discussionTopic || {};
368
+ const latestSynthesis = session.latestSynthesis || discussionTopic;
369
+ const roundCount = metadata.roundId || session.roundCount || 1;
370
+ const topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
371
+ const status = latestSynthesis.status || session.status || 'analyzing';
372
+
373
+ container.innerHTML = `
374
+ <div class="session-detail-page multi-cli-detail-page multi-cli-detail-with-toolbar">
375
+ <!-- Main Content Area -->
376
+ <div class="multi-cli-main-content">
377
+ <!-- Header -->
378
+ <div class="detail-header">
379
+ <button class="btn-back" onclick="goBackToLiteTasks()">
380
+ <span class="back-icon">&larr;</span>
381
+ <span>${t('multiCli.backToList') || 'Back to Multi-CLI Plan'}</span>
382
+ </button>
383
+ <div class="detail-title-row">
384
+ <h2 class="detail-session-id"><i data-lucide="messages-square" class="w-5 h-5 inline mr-2"></i> ${escapeHtml(session.id)}</h2>
385
+ <div class="detail-badges">
386
+ <span class="session-type-badge multi-cli-plan">multi-cli-plan</span>
387
+ <span class="session-status-badge ${status}">${escapeHtml(status)}</span>
388
+ </div>
389
+ </div>
390
+ </div>
391
+
392
+ <!-- Session Info Bar -->
393
+ <div class="detail-info-bar">
394
+ <div class="info-item">
395
+ <span class="info-label">${t('detail.created') || 'Created'}</span>
396
+ <span class="info-value">${formatDate(metadata.timestamp || session.createdAt)}</span>
397
+ </div>
398
+ <div class="info-item">
399
+ <span class="info-label">${t('multiCli.roundCount') || 'Rounds'}</span>
400
+ <span class="info-value">${roundCount}</span>
401
+ </div>
402
+ <div class="info-item">
403
+ <span class="info-label">${t('multiCli.topic') || 'Topic'}</span>
404
+ <span class="info-value">${escapeHtml(topicTitle)}</span>
405
+ </div>
406
+ </div>
407
+
408
+ <!-- Tab Navigation -->
409
+ <div class="detail-tabs">
410
+ <button class="detail-tab active" data-tab="planning" onclick="switchMultiCliDetailTab('planning')">
411
+ <span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
412
+ <span class="tab-text">${t('multiCli.tab.planning') || 'Planning'}</span>
413
+ </button>
414
+ <button class="detail-tab" data-tab="discussion" onclick="switchMultiCliDetailTab('discussion')">
415
+ <span class="tab-icon"><i data-lucide="messages-square" class="w-4 h-4"></i></span>
416
+ <span class="tab-text">${t('multiCli.tab.discussion') || 'Discussion'}</span>
417
+ <span class="tab-count">${roundCount}</span>
418
+ </button>
419
+ <button class="detail-tab" data-tab="association" onclick="switchMultiCliDetailTab('association')">
420
+ <span class="tab-icon"><i data-lucide="link-2" class="w-4 h-4"></i></span>
421
+ <span class="tab-text">${t('multiCli.tab.association') || 'Association'}</span>
422
+ </button>
423
+ </div>
424
+
425
+ <!-- Tab Content -->
426
+ <div class="detail-tab-content" id="multiCliDetailTabContent">
427
+ ${renderMultiCliPlanningTab(session)}
428
+ </div>
429
+ </div>
430
+
431
+ <!-- Right Toolbar -->
432
+ <div class="multi-cli-toolbar ${isMultiCliToolbarExpanded ? 'expanded' : 'collapsed'}" id="multiCliToolbar">
433
+ <button class="toolbar-toggle-btn" onclick="toggleMultiCliToolbar()" title="${t('multiCli.toolbar.toggle') || 'Toggle Task Panel'}">
434
+ <i data-lucide="${isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open'}" class="w-5 h-5"></i>
435
+ </button>
436
+ <div class="toolbar-content">
437
+ ${renderMultiCliToolbar(session)}
438
+ </div>
439
+ </div>
440
+ </div>
441
+ `;
442
+
443
+ // Initialize icons and collapsible sections
444
+ setTimeout(() => {
445
+ if (typeof lucide !== 'undefined') lucide.createIcons();
446
+ initCollapsibleSections(container);
447
+ }, 50);
448
+ }
449
+
450
+ /**
451
+ * Toggle multi-cli toolbar expanded/collapsed state
452
+ */
453
+ function toggleMultiCliToolbar() {
454
+ isMultiCliToolbarExpanded = !isMultiCliToolbarExpanded;
455
+ const toolbar = document.getElementById('multiCliToolbar');
456
+ const toggleBtn = toolbar?.querySelector('.toolbar-toggle-btn i');
457
+
458
+ if (toolbar) {
459
+ toolbar.classList.toggle('expanded', isMultiCliToolbarExpanded);
460
+ toolbar.classList.toggle('collapsed', !isMultiCliToolbarExpanded);
461
+
462
+ // Update icon
463
+ if (toggleBtn) {
464
+ toggleBtn.setAttribute('data-lucide', isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open');
465
+ if (typeof lucide !== 'undefined') lucide.createIcons();
466
+ }
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Render the multi-cli toolbar content
472
+ */
473
+ function renderMultiCliToolbar(session) {
474
+ const plan = session.plan;
475
+ const tasks = plan?.tasks || [];
476
+ const taskCount = tasks.length;
477
+
478
+ let toolbarHtml = `
479
+ <div class="toolbar-header">
480
+ <h4 class="toolbar-title">
481
+ <i data-lucide="list-checks" class="w-4 h-4"></i>
482
+ <span>${t('multiCli.toolbar.tasks') || 'Tasks'}</span>
483
+ <span class="toolbar-count">${taskCount}</span>
484
+ </h4>
485
+ </div>
486
+ `;
487
+
488
+ // Quick Actions
489
+ toolbarHtml += `
490
+ <div class="toolbar-actions">
491
+ <button class="toolbar-action-btn" onclick="refreshMultiCliToolbar()" title="${t('multiCli.toolbar.refresh') || 'Refresh'}">
492
+ <i data-lucide="refresh-cw" class="w-4 h-4"></i>
493
+ </button>
494
+ <button class="toolbar-action-btn" onclick="exportMultiCliPlanJson()" title="${t('multiCli.toolbar.export') || 'Export JSON'}">
495
+ <i data-lucide="download" class="w-4 h-4"></i>
496
+ </button>
497
+ <button class="toolbar-action-btn" onclick="viewMultiCliRawJson()" title="${t('multiCli.toolbar.viewRaw') || 'View Raw Data'}">
498
+ <i data-lucide="code" class="w-4 h-4"></i>
499
+ </button>
500
+ </div>
501
+ `;
502
+
503
+ // Task List
504
+ if (tasks.length > 0) {
505
+ toolbarHtml += `
506
+ <div class="toolbar-task-list">
507
+ ${tasks.map((task, idx) => {
508
+ const taskTitle = task.title || task.summary || `Task ${idx + 1}`;
509
+ const taskScope = task.scope || '';
510
+ const taskId = `task-${idx}`;
511
+
512
+ return `
513
+ <div class="toolbar-task-item" onclick="scrollToMultiCliTask(${idx})" data-task-idx="${idx}">
514
+ <span class="toolbar-task-num">#${idx + 1}</span>
515
+ <div class="toolbar-task-info">
516
+ <span class="toolbar-task-title" title="${escapeHtml(taskTitle)}">${escapeHtml(taskTitle)}</span>
517
+ ${taskScope ? `<span class="toolbar-task-scope">${escapeHtml(taskScope)}</span>` : ''}
518
+ </div>
519
+ </div>
520
+ `;
521
+ }).join('')}
522
+ </div>
523
+ `;
524
+ } else {
525
+ toolbarHtml += `
526
+ <div class="toolbar-empty">
527
+ <i data-lucide="inbox" class="w-8 h-8"></i>
528
+ <span>${t('multiCli.toolbar.noTasks') || 'No tasks available'}</span>
529
+ </div>
530
+ `;
531
+ }
532
+
533
+ // Session Info
534
+ toolbarHtml += `
535
+ <div class="toolbar-session-info">
536
+ <div class="toolbar-info-item">
537
+ <span class="toolbar-info-label">${t('multiCli.toolbar.sessionId') || 'Session'}</span>
538
+ <span class="toolbar-info-value" title="${escapeHtml(session.id)}">${escapeHtml(session.id)}</span>
539
+ </div>
540
+ ${plan?.summary ? `
541
+ <div class="toolbar-info-item">
542
+ <span class="toolbar-info-label">${t('multiCli.toolbar.summary') || 'Summary'}</span>
543
+ <span class="toolbar-info-value toolbar-summary" title="${escapeHtml(plan.summary)}">${escapeHtml(plan.summary)}</span>
544
+ </div>
545
+ ` : ''}
546
+ </div>
547
+ `;
548
+
549
+ return toolbarHtml;
550
+ }
551
+
552
+ /**
553
+ * Scroll to a specific task in the planning tab
554
+ */
555
+ function scrollToMultiCliTask(taskIdx) {
556
+ // Switch to planning tab if not active
557
+ const planningTab = document.querySelector('.detail-tab[data-tab="planning"]');
558
+ if (planningTab && !planningTab.classList.contains('active')) {
559
+ switchMultiCliDetailTab('planning');
560
+ // Wait for tab content to render
561
+ setTimeout(() => scrollToTaskElement(taskIdx), 100);
562
+ } else {
563
+ scrollToTaskElement(taskIdx);
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Scroll to task element in the DOM
569
+ */
570
+ function scrollToTaskElement(taskIdx) {
571
+ const taskItems = document.querySelectorAll('.fix-task-summary-item');
572
+ if (taskItems[taskIdx]) {
573
+ taskItems[taskIdx].scrollIntoView({ behavior: 'smooth', block: 'center' });
574
+ // Highlight the task briefly
575
+ taskItems[taskIdx].classList.add('toolbar-highlight');
576
+ setTimeout(() => {
577
+ taskItems[taskIdx].classList.remove('toolbar-highlight');
578
+ }, 2000);
579
+ // Expand the collapsible if collapsed
580
+ const header = taskItems[taskIdx].querySelector('.collapsible-header');
581
+ const content = taskItems[taskIdx].querySelector('.collapsible-content');
582
+ if (header && content && content.classList.contains('collapsed')) {
583
+ header.click();
584
+ }
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Refresh the toolbar content
590
+ */
591
+ function refreshMultiCliToolbar() {
592
+ const session = liteTaskDataStore[currentSessionDetailKey];
593
+ if (!session) return;
594
+
595
+ const toolbarContent = document.querySelector('.toolbar-content');
596
+ if (toolbarContent) {
597
+ toolbarContent.innerHTML = renderMultiCliToolbar(session);
598
+ if (typeof lucide !== 'undefined') lucide.createIcons();
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Export plan.json content
604
+ */
605
+ function exportMultiCliPlanJson() {
606
+ const session = liteTaskDataStore[currentSessionDetailKey];
607
+ if (!session || !session.plan) {
608
+ if (typeof showRefreshToast === 'function') {
609
+ showRefreshToast(t('multiCli.toolbar.noPlan') || 'No plan data available', 'warning');
610
+ }
611
+ return;
612
+ }
613
+
614
+ const jsonStr = JSON.stringify(session.plan, null, 2);
615
+ const blob = new Blob([jsonStr], { type: 'application/json' });
616
+ const url = URL.createObjectURL(blob);
617
+ const a = document.createElement('a');
618
+ a.href = url;
619
+ a.download = `plan-${session.id}.json`;
620
+ document.body.appendChild(a);
621
+ a.click();
622
+ document.body.removeChild(a);
623
+ URL.revokeObjectURL(url);
624
+
625
+ if (typeof showRefreshToast === 'function') {
626
+ showRefreshToast(t('multiCli.toolbar.exported') || 'Plan exported successfully', 'success');
627
+ }
628
+ }
629
+
630
+ /**
631
+ * View raw session JSON in modal
632
+ */
633
+ function viewMultiCliRawJson() {
634
+ const session = liteTaskDataStore[currentSessionDetailKey];
635
+ if (!session) return;
636
+
637
+ // Reuse existing JSON modal pattern
638
+ const overlay = document.createElement('div');
639
+ overlay.className = 'json-modal-overlay active';
640
+ overlay.innerHTML = `
641
+ <div class="json-modal">
642
+ <div class="json-modal-header">
643
+ <div class="json-modal-title">
644
+ <span class="session-id-badge">${escapeHtml(session.id)}</span>
645
+ <span>${t('multiCli.toolbar.rawData') || 'Raw Session Data'}</span>
646
+ </div>
647
+ <button class="json-modal-close" onclick="closeJsonModal(this)">&times;</button>
648
+ </div>
649
+ <div class="json-modal-body">
650
+ <pre class="json-modal-content">${escapeHtml(JSON.stringify(session, null, 2))}</pre>
651
+ </div>
652
+ <div class="json-modal-footer">
653
+ <button class="btn-copy-json" onclick="copyJsonToClipboard(this)">${t('action.copy') || 'Copy to Clipboard'}</button>
654
+ </div>
655
+ </div>
656
+ `;
657
+ document.body.appendChild(overlay);
658
+ }
659
+
660
+ /**
661
+ * Switch between multi-cli detail tabs
662
+ */
663
+ function switchMultiCliDetailTab(tabName) {
664
+ // Update active tab
665
+ document.querySelectorAll('.detail-tab').forEach(tab => {
666
+ tab.classList.toggle('active', tab.dataset.tab === tabName);
667
+ });
668
+
669
+ const session = liteTaskDataStore[currentSessionDetailKey];
670
+ if (!session) return;
671
+
672
+ const contentArea = document.getElementById('multiCliDetailTabContent');
673
+
674
+ switch (tabName) {
675
+ case 'planning':
676
+ contentArea.innerHTML = renderMultiCliPlanningTab(session);
677
+ break;
678
+ case 'discussion':
679
+ contentArea.innerHTML = renderMultiCliDiscussionSection(session);
680
+ break;
681
+ case 'association':
682
+ contentArea.innerHTML = renderMultiCliAssociationSection(session);
683
+ break;
684
+ }
685
+
686
+ // Re-initialize after tab switch
687
+ setTimeout(() => {
688
+ if (typeof lucide !== 'undefined') lucide.createIcons();
689
+ initCollapsibleSections(contentArea);
690
+ }, 50);
691
+ }
692
+
693
+ // ============================================
694
+ // MULTI-CLI TAB RENDERERS
695
+ // ============================================
696
+
697
+ /**
698
+ * Render Discussion Topic tab
699
+ * Shows: title, description, scope, keyQuestions, status, tags
700
+ */
701
+ function renderMultiCliTopicTab(session) {
702
+ const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
703
+
704
+ if (!topic || Object.keys(topic).length === 0) {
705
+ return `
706
+ <div class="tab-empty-state">
707
+ <div class="empty-icon"><i data-lucide="message-circle" class="w-12 h-12"></i></div>
708
+ <div class="empty-title">${t('multiCli.empty.topic') || 'No Discussion Topic'}</div>
709
+ <div class="empty-text">${t('multiCli.empty.topicText') || 'No discussion topic data available for this session.'}</div>
710
+ </div>
711
+ `;
712
+ }
713
+
714
+ const title = getI18nText(topic.title) || 'Untitled';
715
+ const description = getI18nText(topic.description) || '';
716
+ const scope = topic.scope || {};
717
+ const keyQuestions = topic.keyQuestions || [];
718
+ const status = topic.status || 'unknown';
719
+ const tags = topic.tags || [];
720
+
721
+ let sections = [];
722
+
723
+ // Title and Description
724
+ sections.push(`
725
+ <div class="multi-cli-topic-section">
726
+ <h3 class="multi-cli-topic-title">${escapeHtml(title)}</h3>
727
+ ${description ? `<p class="multi-cli-topic-description">${escapeHtml(description)}</p>` : ''}
728
+ <div class="topic-meta">
729
+ <span class="multi-cli-status ${status}">${escapeHtml(status)}</span>
730
+ ${tags.length ? tags.map(tag => `<span class="tag-badge">${escapeHtml(tag)}</span>`).join('') : ''}
731
+ </div>
732
+ </div>
733
+ `);
734
+
735
+ // Scope (included/excluded)
736
+ if (scope.included?.length || scope.excluded?.length) {
737
+ sections.push(`
738
+ <div class="multi-cli-section scope-section">
739
+ <h4 class="section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.scope') || 'Scope'}</h4>
740
+ ${scope.included?.length ? `
741
+ <div class="scope-included">
742
+ <strong>${t('multiCli.scope.included') || 'Included'}:</strong>
743
+ <ul class="scope-list">
744
+ ${scope.included.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
745
+ </ul>
746
+ </div>
747
+ ` : ''}
748
+ ${scope.excluded?.length ? `
749
+ <div class="scope-excluded">
750
+ <strong>${t('multiCli.scope.excluded') || 'Excluded'}:</strong>
751
+ <ul class="scope-list excluded">
752
+ ${scope.excluded.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
753
+ </ul>
754
+ </div>
755
+ ` : ''}
756
+ </div>
757
+ `);
758
+ }
759
+
760
+ // Key Questions
761
+ if (keyQuestions.length) {
762
+ sections.push(`
763
+ <div class="multi-cli-section questions-section">
764
+ <h4 class="section-title"><i data-lucide="help-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.keyQuestions') || 'Key Questions'}</h4>
765
+ <ol class="key-questions-list">
766
+ ${keyQuestions.map(q => `<li>${escapeHtml(getI18nText(q))}</li>`).join('')}
767
+ </ol>
768
+ </div>
769
+ `);
770
+ }
771
+
772
+ return `<div class="multi-cli-topic-tab">${sections.join('')}</div>`;
773
+ }
774
+
775
+ /**
776
+ * Render Related Files tab
777
+ * Shows: fileTree, impactSummary
778
+ */
779
+ function renderMultiCliFilesTab(session) {
780
+ // Use helper to extract files from synthesis data structure
781
+ const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
782
+
783
+ if (!relatedFiles || (!relatedFiles.fileTree?.length && !relatedFiles.impactSummary?.length)) {
784
+ return `
785
+ <div class="tab-empty-state">
786
+ <div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
787
+ <div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
788
+ <div class="empty-text">${t('multiCli.empty.filesText') || 'No file analysis data available for this session.'}</div>
789
+ </div>
790
+ `;
791
+ }
792
+
793
+ const fileTree = relatedFiles.fileTree || [];
794
+ const impactSummary = relatedFiles.impactSummary || [];
795
+ const dependencyGraph = relatedFiles.dependencyGraph || [];
796
+
797
+ let sections = [];
798
+
799
+ // File Tree
800
+ if (fileTree.length) {
801
+ sections.push(`
802
+ <div class="multi-cli-section file-tree-section collapsible-section">
803
+ <div class="collapsible-header">
804
+ <span class="collapse-icon">&#9658;</span>
805
+ <span class="section-label"><i data-lucide="folder-tree" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.fileTree') || 'File Tree'} (${fileTree.length})</span>
806
+ </div>
807
+ <div class="collapsible-content collapsed">
808
+ <div class="file-tree-list">
809
+ ${renderFileTreeNodes(fileTree)}
810
+ </div>
811
+ </div>
812
+ </div>
813
+ `);
814
+ }
815
+
816
+ // Impact Summary
817
+ if (impactSummary.length) {
818
+ sections.push(`
819
+ <div class="multi-cli-section impact-section collapsible-section">
820
+ <div class="collapsible-header">
821
+ <span class="collapse-icon">&#9658;</span>
822
+ <span class="section-label"><i data-lucide="alert-triangle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.impactSummary') || 'Impact Summary'} (${impactSummary.length})</span>
823
+ </div>
824
+ <div class="collapsible-content collapsed">
825
+ <div class="impact-list">
826
+ ${impactSummary.map(impact => `
827
+ <div class="impact-item impact-${impact.score || 'medium'}">
828
+ <div class="impact-header">
829
+ <code class="impact-file">${escapeHtml(impact.filePath || '')}</code>
830
+ ${impact.line ? `<span class="impact-line">:${impact.line}</span>` : ''}
831
+ <span class="impact-score ${impact.score || 'medium'}">${escapeHtml(impact.score || 'medium')}</span>
832
+ </div>
833
+ ${impact.reasoning ? `<div class="impact-reason">${escapeHtml(getI18nText(impact.reasoning))}</div>` : ''}
834
+ </div>
835
+ `).join('')}
836
+ </div>
837
+ </div>
838
+ </div>
839
+ `);
840
+ }
841
+
842
+ // Dependency Graph
843
+ if (dependencyGraph.length) {
844
+ sections.push(`
845
+ <div class="multi-cli-section deps-section collapsible-section">
846
+ <div class="collapsible-header">
847
+ <span class="collapse-icon">&#9658;</span>
848
+ <span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.dependencies') || 'Dependencies'} (${dependencyGraph.length})</span>
849
+ </div>
850
+ <div class="collapsible-content collapsed">
851
+ <div class="deps-list">
852
+ ${dependencyGraph.map(edge => `
853
+ <div class="dep-edge">
854
+ <code>${escapeHtml(edge.source || '')}</code>
855
+ <span class="dep-arrow">&rarr;</span>
856
+ <code>${escapeHtml(edge.target || '')}</code>
857
+ <span class="dep-relationship">(${escapeHtml(edge.relationship || 'depends')})</span>
858
+ </div>
859
+ `).join('')}
860
+ </div>
861
+ </div>
862
+ </div>
863
+ `);
864
+ }
865
+
866
+ return sections.length ? `<div class="multi-cli-files-tab">${sections.join('')}</div>` : `
867
+ <div class="tab-empty-state">
868
+ <div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
869
+ <div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
870
+ </div>
871
+ `;
872
+ }
873
+
874
+ /**
875
+ * Render file tree nodes recursively
876
+ */
877
+ function renderFileTreeNodes(nodes, depth = 0) {
878
+ return nodes.map(node => {
879
+ const indent = depth * 16;
880
+ const isDir = node.type === 'directory';
881
+ const icon = isDir ? 'folder' : 'file';
882
+ const modStatus = node.modificationStatus || 'unchanged';
883
+ const impactScore = node.impactScore || '';
884
+
885
+ let html = `
886
+ <div class="file-tree-node" style="margin-left: ${indent}px;">
887
+ <i data-lucide="${icon}" class="w-4 h-4 inline mr-1 file-icon ${modStatus}"></i>
888
+ <span class="file-path ${modStatus}">${escapeHtml(node.path || '')}</span>
889
+ ${modStatus !== 'unchanged' ? `<span class="mod-status ${modStatus}">${modStatus}</span>` : ''}
890
+ ${impactScore ? `<span class="impact-badge ${impactScore}">${impactScore}</span>` : ''}
891
+ </div>
892
+ `;
893
+
894
+ if (node.children?.length) {
895
+ html += renderFileTreeNodes(node.children, depth + 1);
896
+ }
897
+
898
+ return html;
899
+ }).join('');
900
+ }
901
+
902
+ /**
903
+ * Render Planning tab - displays session.plan (plan.json content)
904
+ * Reuses renderLitePlanTab style with Summary, Approach, Focus Paths, Metadata, and Tasks
905
+ */
906
+ function renderMultiCliPlanningTab(session) {
907
+ const plan = session.plan;
908
+
909
+ if (!plan) {
910
+ return `
911
+ <div class="tab-empty-state">
912
+ <div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
913
+ <div class="empty-title">${t('multiCli.empty.planning') || 'No Planning Data'}</div>
914
+ <div class="empty-text">${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}</div>
915
+ </div>
916
+ `;
917
+ }
918
+
919
+ return `
920
+ <div class="plan-tab-content">
921
+ <!-- Summary -->
922
+ ${plan.summary ? `
923
+ <div class="plan-section">
924
+ <h4 class="plan-section-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> Summary</h4>
925
+ <p class="plan-summary-text">${escapeHtml(plan.summary)}</p>
926
+ </div>
927
+ ` : ''}
928
+
929
+ <!-- Root Cause (fix-plan specific) -->
930
+ ${plan.root_cause ? `
931
+ <div class="plan-section">
932
+ <h4 class="plan-section-title"><i data-lucide="search" class="w-4 h-4 inline mr-1"></i> Root Cause</h4>
933
+ <p class="plan-root-cause-text">${escapeHtml(plan.root_cause)}</p>
934
+ </div>
935
+ ` : ''}
936
+
937
+ <!-- Strategy (fix-plan specific) -->
938
+ ${plan.strategy ? `
939
+ <div class="plan-section">
940
+ <h4 class="plan-section-title"><i data-lucide="route" class="w-4 h-4 inline mr-1"></i> Fix Strategy</h4>
941
+ <p class="plan-strategy-text">${escapeHtml(plan.strategy)}</p>
942
+ </div>
943
+ ` : ''}
944
+
945
+ <!-- Approach -->
946
+ ${plan.approach ? `
947
+ <div class="plan-section">
948
+ <h4 class="plan-section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> Approach</h4>
949
+ <p class="plan-approach-text">${escapeHtml(plan.approach)}</p>
950
+ </div>
951
+ ` : ''}
952
+
953
+ <!-- User Requirements -->
954
+ ${plan.user_requirements ? `
955
+ <div class="plan-section">
956
+ <h4 class="plan-section-title"><i data-lucide="user" class="w-4 h-4 inline mr-1"></i> User Requirements</h4>
957
+ <p class="plan-requirements-text">${escapeHtml(plan.user_requirements)}</p>
958
+ </div>
959
+ ` : ''}
960
+
961
+ <!-- Focus Paths -->
962
+ ${plan.focus_paths?.length ? `
963
+ <div class="plan-section">
964
+ <h4 class="plan-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> Focus Paths</h4>
965
+ <div class="path-tags">
966
+ ${plan.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
967
+ </div>
968
+ </div>
969
+ ` : ''}
970
+
971
+ <!-- Metadata -->
972
+ <div class="plan-section">
973
+ <h4 class="plan-section-title"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Metadata</h4>
974
+ <div class="plan-meta-grid">
975
+ ${plan.severity ? `<div class="meta-item"><span class="meta-label">Severity:</span> <span class="severity-badge ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span></div>` : ''}
976
+ ${plan.risk_level ? `<div class="meta-item"><span class="meta-label">Risk Level:</span> <span class="risk-badge ${escapeHtml(plan.risk_level)}">${escapeHtml(plan.risk_level)}</span></div>` : ''}
977
+ ${plan.estimated_time ? `<div class="meta-item"><span class="meta-label">Estimated Time:</span> ${escapeHtml(plan.estimated_time)}</div>` : ''}
978
+ ${plan.complexity ? `<div class="meta-item"><span class="meta-label">Complexity:</span> ${escapeHtml(plan.complexity)}</div>` : ''}
979
+ ${plan.recommended_execution ? `<div class="meta-item"><span class="meta-label">Execution:</span> ${escapeHtml(plan.recommended_execution)}</div>` : ''}
980
+ </div>
981
+ </div>
982
+
983
+ <!-- Tasks -->
984
+ ${plan.tasks?.length ? `
985
+ <div class="plan-section">
986
+ <h4 class="plan-section-title"><i data-lucide="list-checks" class="w-4 h-4 inline mr-1"></i> Tasks (${plan.tasks.length})</h4>
987
+ <div class="fix-tasks-summary">
988
+ ${plan.tasks.map((task, idx) => `
989
+ <div class="fix-task-summary-item collapsible-section">
990
+ <div class="collapsible-header">
991
+ <span class="collapse-icon">&#9654;</span>
992
+ <span class="task-num">#${idx + 1}</span>
993
+ <span class="task-title-brief">${escapeHtml(task.title || task.summary || 'Untitled')}</span>
994
+ ${task.scope ? `<span class="task-scope-badge">${escapeHtml(task.scope)}</span>` : ''}
995
+ </div>
996
+ <div class="collapsible-content collapsed">
997
+ ${task.modification_points?.length ? `
998
+ <div class="task-detail-section">
999
+ <strong>Modification Points:</strong>
1000
+ <ul class="mod-points-list">
1001
+ ${task.modification_points.map(mp => `
1002
+ <li>
1003
+ <code>${escapeHtml(mp.file || '')}</code>
1004
+ ${mp.function_name ? `<span class="func-name">-> ${escapeHtml(mp.function_name)}</span>` : ''}
1005
+ ${mp.change_type ? `<span class="change-type">(${escapeHtml(mp.change_type)})</span>` : ''}
1006
+ </li>
1007
+ `).join('')}
1008
+ </ul>
1009
+ </div>
1010
+ ` : ''}
1011
+ ${task.implementation?.length ? `
1012
+ <div class="task-detail-section">
1013
+ <strong>Implementation Steps:</strong>
1014
+ <ol class="impl-steps-list">
1015
+ ${task.implementation.map(step => `<li>${escapeHtml(step)}</li>`).join('')}
1016
+ </ol>
1017
+ </div>
1018
+ ` : ''}
1019
+ ${task.verification?.length ? `
1020
+ <div class="task-detail-section">
1021
+ <strong>Verification:</strong>
1022
+ <ul class="verify-list">
1023
+ ${task.verification.map(v => `<li>${escapeHtml(v)}</li>`).join('')}
1024
+ </ul>
1025
+ </div>
1026
+ ` : ''}
1027
+ </div>
1028
+ </div>
1029
+ `).join('')}
1030
+ </div>
1031
+ </div>
1032
+ ` : ''}
1033
+
1034
+ <!-- Raw JSON -->
1035
+ <div class="plan-section collapsible-section">
1036
+ <div class="collapsible-header">
1037
+ <span class="collapse-icon">&#9654;</span>
1038
+ <span class="section-label">{ } Raw JSON</span>
1039
+ </div>
1040
+ <div class="collapsible-content collapsed">
1041
+ <pre class="json-content">${escapeHtml(JSON.stringify(plan, null, 2))}</pre>
1042
+ </div>
1043
+ </div>
1044
+ </div>
1045
+ `;
1046
+ }
1047
+
1048
+ /**
1049
+ * Render a single requirement item
1050
+ */
1051
+ function renderRequirementItem(req) {
1052
+ const priorityColors = {
1053
+ 'critical': 'error',
1054
+ 'high': 'warning',
1055
+ 'medium': 'info',
1056
+ 'low': 'default'
1057
+ };
1058
+ const priority = req.priority || 'medium';
1059
+ const colorClass = priorityColors[priority] || 'default';
1060
+
1061
+ return `
1062
+ <div class="requirement-item">
1063
+ <div class="requirement-header">
1064
+ <span class="requirement-id">${escapeHtml(req.id || '')}</span>
1065
+ <span class="priority-badge ${colorClass}">${escapeHtml(priority)}</span>
1066
+ </div>
1067
+ <div class="requirement-desc">${escapeHtml(getI18nText(req.description))}</div>
1068
+ ${req.source ? `<div class="requirement-source">${t('multiCli.source') || 'Source'}: ${escapeHtml(req.source)}</div>` : ''}
1069
+ </div>
1070
+ `;
1071
+ }
1072
+
1073
+ /**
1074
+ * Render Decision tab
1075
+ * Shows: selectedSolution, rejectedAlternatives, confidenceScore
1076
+ */
1077
+ function renderMultiCliDecisionTab(session) {
1078
+ // Use helper to extract decision from synthesis data structure
1079
+ const decision = extractDecisionFromSynthesis(session.latestSynthesis);
1080
+
1081
+ if (!decision || !decision.selectedSolution) {
1082
+ return `
1083
+ <div class="tab-empty-state">
1084
+ <div class="empty-icon"><i data-lucide="check-circle" class="w-12 h-12"></i></div>
1085
+ <div class="empty-title">${t('multiCli.empty.decision') || 'No Decision Yet'}</div>
1086
+ <div class="empty-text">${t('multiCli.empty.decisionText') || 'No decision has been made for this discussion yet.'}</div>
1087
+ </div>
1088
+ `;
1089
+ }
1090
+
1091
+ const status = decision.status || 'pending';
1092
+ const summary = getI18nText(decision.summary) || '';
1093
+ const selectedSolution = decision.selectedSolution || null;
1094
+ const rejectedAlternatives = decision.rejectedAlternatives || [];
1095
+ const confidenceScore = decision.confidenceScore || 0;
1096
+
1097
+ let sections = [];
1098
+
1099
+ // Decision Status and Summary
1100
+ sections.push(`
1101
+ <div class="multi-cli-section decision-header-section">
1102
+ <div class="decision-status-bar ${confidenceScore >= 0.7 ? 'converged' : 'divergent'}">
1103
+ <div class="decision-status-wrapper">
1104
+ <span class="decision-status ${status}">${escapeHtml(status)}</span>
1105
+ </div>
1106
+ <div class="decision-confidence">
1107
+ <span class="decision-confidence-label">${t('multiCli.confidence') || 'Confidence'}:</span>
1108
+ <div class="decision-confidence-bar">
1109
+ <div class="decision-confidence-fill" style="width: ${(confidenceScore * 100).toFixed(0)}%"></div>
1110
+ </div>
1111
+ <span class="decision-confidence-value">${(confidenceScore * 100).toFixed(0)}%</span>
1112
+ </div>
1113
+ </div>
1114
+ ${summary ? `<p class="decision-summary-text">${escapeHtml(summary)}</p>` : ''}
1115
+ </div>
1116
+ `);
1117
+
1118
+ // Selected Solution
1119
+ if (selectedSolution) {
1120
+ sections.push(`
1121
+ <div class="multi-cli-section selected-solution-section">
1122
+ <h4 class="section-title"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.selectedSolution') || 'Selected Solution'}</h4>
1123
+ ${renderSolutionCard(selectedSolution, true)}
1124
+ </div>
1125
+ `);
1126
+ }
1127
+
1128
+ // Rejected Alternatives
1129
+ if (rejectedAlternatives.length) {
1130
+ sections.push(`
1131
+ <div class="multi-cli-section rejected-section collapsible-section">
1132
+ <div class="collapsible-header">
1133
+ <span class="collapse-icon">&#9658;</span>
1134
+ <span class="section-label"><i data-lucide="x-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.rejectedAlternatives') || 'Rejected Alternatives'} (${rejectedAlternatives.length})</span>
1135
+ </div>
1136
+ <div class="collapsible-content collapsed">
1137
+ ${rejectedAlternatives.map(alt => renderSolutionCard(alt, false)).join('')}
1138
+ </div>
1139
+ </div>
1140
+ `);
1141
+ }
1142
+
1143
+ return `<div class="multi-cli-decision-tab">${sections.join('')}</div>`;
1144
+ }
1145
+
1146
+ /**
1147
+ * Render a solution card
1148
+ */
1149
+ function renderSolutionCard(solution, isSelected) {
1150
+ const title = getI18nText(solution.title) || 'Untitled Solution';
1151
+ const description = getI18nText(solution.description) || '';
1152
+ const pros = solution.pros || [];
1153
+ const cons = solution.cons || [];
1154
+ const risk = solution.risk || 'medium';
1155
+ const effort = getI18nText(solution.estimatedEffort) || '';
1156
+ const rejectionReason = solution.rejectionReason ? getI18nText(solution.rejectionReason) : '';
1157
+ const sourceCLIs = solution.sourceCLIs || [];
1158
+
1159
+ return `
1160
+ <div class="solution-card ${isSelected ? 'selected' : 'rejected'}">
1161
+ <div class="solution-header">
1162
+ <span class="solution-id">${escapeHtml(solution.id || '')}</span>
1163
+ <span class="solution-title">${escapeHtml(title)}</span>
1164
+ <span class="risk-badge ${risk}">${escapeHtml(risk)}</span>
1165
+ </div>
1166
+ ${description ? `<p class="solution-desc">${escapeHtml(description)}</p>` : ''}
1167
+ ${rejectionReason ? `<div class="rejection-reason"><strong>${t('multiCli.rejectionReason') || 'Reason'}:</strong> ${escapeHtml(rejectionReason)}</div>` : ''}
1168
+ <div class="solution-details">
1169
+ ${pros.length ? `
1170
+ <div class="pros-section">
1171
+ <strong>${t('multiCli.pros') || 'Pros'}:</strong>
1172
+ <ul class="pros-list">${pros.map(p => `<li class="pro-item">${escapeHtml(getI18nText(p))}</li>`).join('')}</ul>
1173
+ </div>
1174
+ ` : ''}
1175
+ ${cons.length ? `
1176
+ <div class="cons-section">
1177
+ <strong>${t('multiCli.cons') || 'Cons'}:</strong>
1178
+ <ul class="cons-list">${cons.map(c => `<li class="con-item">${escapeHtml(getI18nText(c))}</li>`).join('')}</ul>
1179
+ </div>
1180
+ ` : ''}
1181
+ ${effort ? `<div class="effort-estimate"><strong>${t('multiCli.effort') || 'Effort'}:</strong> ${escapeHtml(effort)}</div>` : ''}
1182
+ ${sourceCLIs.length ? `<div class="source-clis"><strong>${t('multiCli.sources') || 'Sources'}:</strong> ${sourceCLIs.map(s => `<span class="cli-badge">${escapeHtml(s)}</span>`).join('')}</div>` : ''}
1183
+ </div>
1184
+ </div>
1185
+ `;
1186
+ }
1187
+
1188
+ /**
1189
+ * Render Timeline tab
1190
+ * Shows: decisionRecords.timeline
1191
+ */
1192
+ function renderMultiCliTimelineTab(session) {
1193
+ // Use helper to extract timeline from synthesis data structure
1194
+ const timeline = extractTimelineFromSynthesis(session.latestSynthesis);
1195
+
1196
+ if (!timeline || !timeline.length) {
1197
+ return `
1198
+ <div class="tab-empty-state">
1199
+ <div class="empty-icon"><i data-lucide="git-commit" class="w-12 h-12"></i></div>
1200
+ <div class="empty-title">${t('multiCli.empty.timeline') || 'No Timeline Events'}</div>
1201
+ <div class="empty-text">${t('multiCli.empty.timelineText') || 'No decision timeline available for this session.'}</div>
1202
+ </div>
1203
+ `;
1204
+ }
1205
+
1206
+ const eventTypeIcons = {
1207
+ 'proposal': 'lightbulb',
1208
+ 'argument': 'message-square',
1209
+ 'agreement': 'thumbs-up',
1210
+ 'disagreement': 'thumbs-down',
1211
+ 'decision': 'check-circle',
1212
+ 'reversal': 'rotate-ccw'
1213
+ };
1214
+
1215
+ return `
1216
+ <div class="multi-cli-timeline-tab">
1217
+ <div class="timeline-container">
1218
+ ${timeline.map(event => {
1219
+ const icon = eventTypeIcons[event.type] || 'circle';
1220
+ const contributor = event.contributor || {};
1221
+ const summary = getI18nText(event.summary) || '';
1222
+ const evidence = event.evidence || [];
1223
+
1224
+ return `
1225
+ <div class="timeline-event event-${event.type || 'default'}">
1226
+ <div class="timeline-marker">
1227
+ <i data-lucide="${icon}" class="w-4 h-4"></i>
1228
+ </div>
1229
+ <div class="timeline-content">
1230
+ <div class="event-header">
1231
+ <span class="event-type ${event.type || ''}">${escapeHtml(event.type || 'event')}</span>
1232
+ <span class="event-contributor"><i data-lucide="user" class="w-3.5 h-3.5"></i>${escapeHtml(contributor.name || 'Unknown')}</span>
1233
+ <span class="event-time">${formatDate(event.timestamp)}</span>
1234
+ </div>
1235
+ <div class="event-summary">${escapeHtml(summary)}</div>
1236
+ ${event.reversibility ? `<span class="reversibility-badge ${event.reversibility}">${escapeHtml(event.reversibility)}</span>` : ''}
1237
+ ${evidence.length ? `
1238
+ <div class="event-evidence">
1239
+ ${evidence.map(ev => `
1240
+ <div class="evidence-item evidence-${ev.type || 'reference'}">
1241
+ <span class="evidence-type">${escapeHtml(ev.type || 'reference')}</span>
1242
+ <span class="evidence-desc">${escapeHtml(getI18nText(ev.description))}</span>
1243
+ </div>
1244
+ `).join('')}
1245
+ </div>
1246
+ ` : ''}
1247
+ </div>
1248
+ </div>
1249
+ `;
1250
+ }).join('')}
1251
+ </div>
1252
+ </div>
1253
+ `;
1254
+ }
1255
+
1256
+ /**
1257
+ * Render Rounds tab
1258
+ * Shows: navigation between round synthesis files
1259
+ */
1260
+ function renderMultiCliRoundsTab(session) {
1261
+ const rounds = session.rounds || [];
1262
+ const metadata = session.metadata || {};
1263
+ const totalRounds = metadata.roundId || rounds.length || 1;
1264
+
1265
+ if (!rounds.length && totalRounds <= 1) {
1266
+ // Show current synthesis as single round
1267
+ return `
1268
+ <div class="multi-cli-rounds-tab">
1269
+ <div class="rounds-nav">
1270
+ <div class="round-item active" data-round="1">
1271
+ <span class="round-number">Round 1</span>
1272
+ <span class="round-status">${t('multiCli.currentRound') || 'Current'}</span>
1273
+ </div>
1274
+ </div>
1275
+ <div class="round-content">
1276
+ <div class="round-info">
1277
+ <p>${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View other tabs for details.'}</p>
1278
+ </div>
1279
+ </div>
1280
+ </div>
1281
+ `;
1282
+ }
1283
+
1284
+ // Render round navigation and content
1285
+ return `
1286
+ <div class="multi-cli-rounds-tab">
1287
+ <div class="rounds-nav">
1288
+ ${rounds.map((round, idx) => {
1289
+ const roundNum = idx + 1;
1290
+ const isActive = roundNum === totalRounds;
1291
+ const roundStatus = round.convergence?.recommendation || 'continue';
1292
+
1293
+ return `
1294
+ <div class="round-item ${isActive ? 'active' : ''}" data-round="${roundNum}" onclick="loadMultiCliRound('${currentSessionDetailKey}', ${roundNum})">
1295
+ <span class="round-number">Round ${roundNum}</span>
1296
+ <span class="round-status ${roundStatus}">${escapeHtml(roundStatus)}</span>
1297
+ </div>
1298
+ `;
1299
+ }).join('')}
1300
+ </div>
1301
+ <div class="round-content" id="multiCliRoundContent">
1302
+ ${renderRoundContent(rounds[totalRounds - 1] || rounds[0] || session)}
1303
+ </div>
1304
+ </div>
1305
+ `;
1306
+ }
1307
+
1308
+ /**
1309
+ * Render content for a specific round
1310
+ */
1311
+ function renderRoundContent(round) {
1312
+ if (!round) {
1313
+ return `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
1314
+ }
1315
+
1316
+ const metadata = round.metadata || {};
1317
+ const agents = metadata.contributingAgents || [];
1318
+ const convergence = round._internal?.convergence || {};
1319
+ const crossVerification = round._internal?.cross_verification || {};
1320
+
1321
+ let sections = [];
1322
+
1323
+ // Round metadata
1324
+ sections.push(`
1325
+ <div class="round-metadata">
1326
+ <div class="meta-row">
1327
+ <span class="meta-label">${t('multiCli.roundId') || 'Round'}:</span>
1328
+ <span class="meta-value">${metadata.roundId || 1}</span>
1329
+ </div>
1330
+ <div class="meta-row">
1331
+ <span class="meta-label">${t('multiCli.timestamp') || 'Time'}:</span>
1332
+ <span class="meta-value">${formatDate(metadata.timestamp)}</span>
1333
+ </div>
1334
+ ${metadata.durationSeconds ? `
1335
+ <div class="meta-row">
1336
+ <span class="meta-label">${t('multiCli.duration') || 'Duration'}:</span>
1337
+ <span class="meta-value">${metadata.durationSeconds}s</span>
1338
+ </div>
1339
+ ` : ''}
1340
+ </div>
1341
+ `);
1342
+
1343
+ // Contributing agents
1344
+ if (agents.length) {
1345
+ sections.push(`
1346
+ <div class="round-agents">
1347
+ <strong>${t('multiCli.contributors') || 'Contributors'}:</strong>
1348
+ ${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id)}</span>`).join('')}
1349
+ </div>
1350
+ `);
1351
+ }
1352
+
1353
+ // Convergence metrics
1354
+ if (convergence.score !== undefined) {
1355
+ sections.push(`
1356
+ <div class="round-convergence">
1357
+ <strong>${t('multiCli.convergence') || 'Convergence'}:</strong>
1358
+ <span class="convergence-score">${(convergence.score * 100).toFixed(0)}%</span>
1359
+ <span class="convergence-rec ${convergence.recommendation || ''}">${escapeHtml(convergence.recommendation || '')}</span>
1360
+ ${convergence.new_insights ? `<span class="new-insights-badge">${t('multiCli.newInsights') || 'New Insights'}</span>` : ''}
1361
+ </div>
1362
+ `);
1363
+ }
1364
+
1365
+ // Cross-verification
1366
+ if (crossVerification.agreements?.length || crossVerification.disagreements?.length) {
1367
+ sections.push(`
1368
+ <div class="round-verification collapsible-section">
1369
+ <div class="collapsible-header">
1370
+ <span class="collapse-icon">&#9658;</span>
1371
+ <span class="section-label">${t('multiCli.crossVerification') || 'Cross-Verification'}</span>
1372
+ </div>
1373
+ <div class="collapsible-content collapsed">
1374
+ ${crossVerification.agreements?.length ? `
1375
+ <div class="agreements">
1376
+ <strong>${t('multiCli.agreements') || 'Agreements'}:</strong>
1377
+ <ul>${crossVerification.agreements.map(a => `<li class="agreement">${escapeHtml(a)}</li>`).join('')}</ul>
1378
+ </div>
1379
+ ` : ''}
1380
+ ${crossVerification.disagreements?.length ? `
1381
+ <div class="disagreements">
1382
+ <strong>${t('multiCli.disagreements') || 'Disagreements'}:</strong>
1383
+ <ul>${crossVerification.disagreements.map(d => `<li class="disagreement">${escapeHtml(d)}</li>`).join('')}</ul>
1384
+ </div>
1385
+ ` : ''}
1386
+ ${crossVerification.resolution ? `
1387
+ <div class="resolution">
1388
+ <strong>${t('multiCli.resolution') || 'Resolution'}:</strong>
1389
+ <p>${escapeHtml(crossVerification.resolution)}</p>
1390
+ </div>
1391
+ ` : ''}
1392
+ </div>
1393
+ </div>
1394
+ `);
1395
+ }
1396
+
1397
+ // Round solutions
1398
+ if (round.solutions && round.solutions.length > 0) {
1399
+ sections.push(`
1400
+ <div class="round-solutions">
1401
+ <strong>${t('multiCli.solutions') || 'Solutions'} (${round.solutions.length}):</strong>
1402
+ <div class="solutions-list">
1403
+ ${round.solutions.map((solution, idx) => `
1404
+ <div class="solution-card">
1405
+ <div class="solution-header">
1406
+ <div class="solution-title">
1407
+ <span class="solution-number">${idx + 1}</span>
1408
+ <span class="solution-name">${escapeHtml(solution.name || `Solution ${idx + 1}`)}</span>
1409
+ </div>
1410
+ <div class="solution-meta">
1411
+ ${solution.source_cli?.length ? `
1412
+ <div class="source-clis">
1413
+ ${solution.source_cli.map(cli => `<span class="cli-badge">${escapeHtml(cli)}</span>`).join('')}
1414
+ </div>
1415
+ ` : ''}
1416
+ <div class="solution-scores">
1417
+ <span class="score-badge feasibility" title="Feasibility">
1418
+ ${Math.round((solution.feasibility || 0) * 100)}%
1419
+ </span>
1420
+ <span class="score-badge effort-${solution.effort || 'medium'}" title="Effort">
1421
+ ${escapeHtml(solution.effort || 'medium')}
1422
+ </span>
1423
+ <span class="score-badge risk-${solution.risk || 'medium'}" title="Risk">
1424
+ ${escapeHtml(solution.risk || 'medium')}
1425
+ </span>
1426
+ </div>
1427
+ </div>
1428
+ </div>
1429
+
1430
+ ${solution.summary ? `
1431
+ <div class="solution-summary">
1432
+ ${escapeHtml(getI18nText(solution.summary))}
1433
+ </div>
1434
+ ` : ''}
1435
+
1436
+ ${solution.implementation_plan?.approach ? `
1437
+ <div class="solution-approach collapsible-section">
1438
+ <div class="collapsible-header">
1439
+ <span class="collapse-icon">&#9658;</span>
1440
+ <span class="section-label">${t('multiCli.implementation') || 'Implementation Approach'}</span>
1441
+ </div>
1442
+ <div class="collapsible-content collapsed">
1443
+ <p>${escapeHtml(getI18nText(solution.implementation_plan.approach))}</p>
1444
+
1445
+ ${solution.implementation_plan.tasks?.length ? `
1446
+ <div class="solution-tasks">
1447
+ <strong>${t('multiCli.tasks') || 'Tasks'}:</strong>
1448
+ <ul class="task-list">
1449
+ ${solution.implementation_plan.tasks.map(task => `
1450
+ <li class="task-item">
1451
+ <span class="task-id">${escapeHtml(task.id || '')}</span>
1452
+ <span class="task-name">${escapeHtml(getI18nText(task.name))}</span>
1453
+ ${task.key_point ? `<span class="task-key-point">${escapeHtml(getI18nText(task.key_point))}</span>` : ''}
1454
+ </li>
1455
+ `).join('')}
1456
+ </ul>
1457
+ </div>
1458
+ ` : ''}
1459
+
1460
+ ${solution.implementation_plan.execution_flow ? `
1461
+ <div class="execution-flow">
1462
+ <strong>${t('multiCli.executionFlow') || 'Execution Flow'}:</strong>
1463
+ <code class="flow-code">${escapeHtml(solution.implementation_plan.execution_flow)}</code>
1464
+ </div>
1465
+ ` : ''}
1466
+
1467
+ ${solution.implementation_plan.milestones?.length ? `
1468
+ <div class="solution-milestones">
1469
+ <strong>${t('multiCli.milestones') || 'Milestones'}:</strong>
1470
+ <ul class="milestone-list">
1471
+ ${solution.implementation_plan.milestones.map(milestone => `
1472
+ <li class="milestone-item">${escapeHtml(getI18nText(milestone))}</li>
1473
+ `).join('')}
1474
+ </ul>
1475
+ </div>
1476
+ ` : ''}
1477
+ </div>
1478
+ </div>
1479
+ ` : ''}
1480
+
1481
+ ${(solution.dependencies?.internal?.length || solution.dependencies?.external?.length) ? `
1482
+ <div class="solution-dependencies collapsible-section">
1483
+ <div class="collapsible-header">
1484
+ <span class="collapse-icon">&#9658;</span>
1485
+ <span class="section-label">${t('multiCli.dependencies') || 'Dependencies'}</span>
1486
+ </div>
1487
+ <div class="collapsible-content collapsed">
1488
+ ${solution.dependencies.internal?.length ? `
1489
+ <div class="internal-deps">
1490
+ <strong>${t('multiCli.internalDeps') || 'Internal'}:</strong>
1491
+ <ul class="dep-list">
1492
+ ${solution.dependencies.internal.map(dep => `
1493
+ <li class="dep-item">${escapeHtml(getI18nText(dep))}</li>
1494
+ `).join('')}
1495
+ </ul>
1496
+ </div>
1497
+ ` : ''}
1498
+ ${solution.dependencies.external?.length ? `
1499
+ <div class="external-deps">
1500
+ <strong>${t('multiCli.externalDeps') || 'External'}:</strong>
1501
+ <ul class="dep-list">
1502
+ ${solution.dependencies.external.map(dep => `
1503
+ <li class="dep-item">${escapeHtml(getI18nText(dep))}</li>
1504
+ `).join('')}
1505
+ </ul>
1506
+ </div>
1507
+ ` : ''}
1508
+ </div>
1509
+ </div>
1510
+ ` : ''}
1511
+
1512
+ ${solution.technical_concerns?.length ? `
1513
+ <div class="solution-concerns collapsible-section">
1514
+ <div class="collapsible-header">
1515
+ <span class="collapse-icon">&#9658;</span>
1516
+ <span class="section-label">${t('multiCli.technicalConcerns') || 'Technical Concerns'}</span>
1517
+ </div>
1518
+ <div class="collapsible-content collapsed">
1519
+ <ul class="concern-list">
1520
+ ${solution.technical_concerns.map(concern => `
1521
+ <li class="concern-item">${escapeHtml(getI18nText(concern))}</li>
1522
+ `).join('')}
1523
+ </ul>
1524
+ </div>
1525
+ </div>
1526
+ ` : ''}
1527
+ </div>
1528
+ `).join('')}
1529
+ </div>
1530
+ </div>
1531
+ `);
1532
+ }
1533
+
1534
+ return sections.join('');
1535
+ }
1536
+
1537
+ /**
1538
+ * Load a specific round's data (async, may fetch from server)
1539
+ */
1540
+ async function loadMultiCliRound(sessionKey, roundNum) {
1541
+ const session = liteTaskDataStore[sessionKey];
1542
+ if (!session) return;
1543
+
1544
+ // Update active state in nav
1545
+ document.querySelectorAll('.round-item').forEach(item => {
1546
+ item.classList.toggle('active', parseInt(item.dataset.round) === roundNum);
1547
+ });
1548
+
1549
+ const contentArea = document.getElementById('multiCliRoundContent');
1550
+
1551
+ // If we have rounds array, use it
1552
+ if (session.rounds && session.rounds[roundNum - 1]) {
1553
+ contentArea.innerHTML = renderRoundContent(session.rounds[roundNum - 1]);
1554
+ initCollapsibleSections(contentArea);
1555
+ return;
1556
+ }
1557
+
1558
+ // Otherwise try to fetch from server
1559
+ if (window.SERVER_MODE && session.path) {
1560
+ contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
1561
+ try {
1562
+ const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=round&round=${roundNum}`);
1563
+ if (response.ok) {
1564
+ const data = await response.json();
1565
+ contentArea.innerHTML = renderRoundContent(data.round || {});
1566
+ initCollapsibleSections(contentArea);
1567
+ return;
1568
+ }
1569
+ } catch (err) {
1570
+ console.error('Failed to load round:', err);
1571
+ }
1572
+ }
1573
+
1574
+ // Fallback
1575
+ contentArea.innerHTML = `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
1576
+ }
1577
+
1578
+ /**
1579
+ * Render Discussion Section (combines Topic, Rounds, Decision)
1580
+ * Uses accordion layout to display discussion rounds
1581
+ */
1582
+ function renderMultiCliDiscussionSection(session) {
1583
+ const rounds = session.rounds || [];
1584
+ const metadata = session.metadata || {};
1585
+ const totalRounds = metadata.roundId || rounds.length || 1;
1586
+ const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
1587
+
1588
+ // If no rounds, show topic summary and current synthesis
1589
+ if (!rounds.length) {
1590
+ const title = getI18nText(topic.title) || 'Discussion Topic';
1591
+ const description = getI18nText(topic.description) || '';
1592
+ const status = topic.status || session.status || 'analyzing';
1593
+
1594
+ return `
1595
+ <div class="multi-cli-discussion-section">
1596
+ <div class="discussion-header">
1597
+ <h3 class="discussion-title">${escapeHtml(title)}</h3>
1598
+ <span class="discussion-status ${status}">${escapeHtml(status)}</span>
1599
+ </div>
1600
+ ${description ? `<p class="discussion-description">${escapeHtml(description)}</p>` : ''}
1601
+ <div class="discussion-empty-state">
1602
+ <i data-lucide="message-circle" class="w-8 h-8"></i>
1603
+ <p>${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View Planning tab for execution details.'}</p>
1604
+ </div>
1605
+ </div>
1606
+ `;
1607
+ }
1608
+
1609
+ // Render accordion for multiple rounds
1610
+ const accordionItems = rounds.map((round, idx) => {
1611
+ const roundNum = idx + 1;
1612
+ const isLatest = roundNum === totalRounds;
1613
+ const roundMeta = round.metadata || {};
1614
+ const convergence = round._internal?.convergence || round.convergence || {};
1615
+ const recommendation = convergence.recommendation || 'continue';
1616
+ const score = convergence.score !== undefined ? Math.round(convergence.score * 100) : null;
1617
+ const solutions = round.solutions || [];
1618
+ const agents = roundMeta.contributingAgents || [];
1619
+
1620
+ return `
1621
+ <div class="discussion-round collapsible-section ${isLatest ? 'expanded' : ''}">
1622
+ <div class="collapsible-header discussion-round-header">
1623
+ <span class="collapse-icon">${isLatest ? '&#9660;' : '&#9658;'}</span>
1624
+ <div class="round-title-group">
1625
+ <span class="round-badge">${t('multiCli.round') || 'Round'} ${roundNum}</span>
1626
+ <span class="round-timestamp">${formatDate(roundMeta.timestamp)}</span>
1627
+ </div>
1628
+ <div class="round-indicators">
1629
+ ${score !== null ? `<span class="convergence-badge" title="${t('multiCli.convergence') || 'Convergence'}">${score}%</span>` : ''}
1630
+ <span class="recommendation-badge ${recommendation}">${escapeHtml(recommendation)}</span>
1631
+ </div>
1632
+ </div>
1633
+ <div class="collapsible-content ${isLatest ? '' : 'collapsed'}">
1634
+ <!-- Discussion Topic for this round -->
1635
+ ${round.discussionTopic ? `
1636
+ <div class="round-topic">
1637
+ <h4 class="round-section-title"><i data-lucide="message-circle" class="w-4 h-4 inline"></i> ${t('multiCli.topic') || 'Topic'}</h4>
1638
+ <p>${escapeHtml(getI18nText(round.discussionTopic.title || round.discussionTopic))}</p>
1639
+ </div>
1640
+ ` : ''}
1641
+
1642
+ <!-- Contributing Agents -->
1643
+ ${agents.length ? `
1644
+ <div class="round-agents">
1645
+ <h4 class="round-section-title"><i data-lucide="users" class="w-4 h-4 inline"></i> ${t('multiCli.contributors') || 'Contributors'}</h4>
1646
+ <div class="agent-badges">
1647
+ ${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id || agent)}</span>`).join('')}
1648
+ </div>
1649
+ </div>
1650
+ ` : ''}
1651
+
1652
+ <!-- Solutions -->
1653
+ ${solutions.length ? `
1654
+ <div class="round-solutions-summary">
1655
+ <h4 class="round-section-title"><i data-lucide="lightbulb" class="w-4 h-4 inline"></i> ${t('multiCli.solutions') || 'Solutions'} (${solutions.length})</h4>
1656
+ <div class="solution-cards-grid">
1657
+ ${solutions.map((sol, sidx) => `
1658
+ <div class="solution-mini-card">
1659
+ <div class="solution-mini-header">
1660
+ <span class="solution-number">${sidx + 1}</span>
1661
+ <span class="solution-name">${escapeHtml(sol.name || 'Solution ' + (sidx + 1))}</span>
1662
+ </div>
1663
+ <div class="solution-mini-scores">
1664
+ <span class="score-pill feasibility" title="Feasibility">${Math.round((sol.feasibility || 0) * 100)}%</span>
1665
+ <span class="score-pill effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'M')}</span>
1666
+ <span class="score-pill risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'M')}</span>
1667
+ </div>
1668
+ ${sol.summary ? `<p class="solution-mini-summary">${escapeHtml(getI18nText(sol.summary).substring(0, 100))}${getI18nText(sol.summary).length > 100 ? '...' : ''}</p>` : ''}
1669
+ </div>
1670
+ `).join('')}
1671
+ </div>
1672
+ </div>
1673
+ ` : ''}
1674
+
1675
+ <!-- Decision/Recommendation -->
1676
+ ${convergence.reasoning || round.decision ? `
1677
+ <div class="round-decision">
1678
+ <h4 class="round-section-title"><i data-lucide="check-circle" class="w-4 h-4 inline"></i> ${t('multiCli.decision') || 'Decision'}</h4>
1679
+ <p class="decision-text">${escapeHtml(convergence.reasoning || round.decision || '')}</p>
1680
+ </div>
1681
+ ` : ''}
1682
+ </div>
1683
+ </div>
1684
+ `;
1685
+ }).join('');
1686
+
1687
+ return `
1688
+ <div class="multi-cli-discussion-section">
1689
+ <div class="discussion-header">
1690
+ <h3 class="discussion-title">${escapeHtml(getI18nText(topic.title) || 'Discussion')}</h3>
1691
+ <span class="rounds-count">${totalRounds} ${t('multiCli.tab.rounds') || 'Rounds'}</span>
1692
+ </div>
1693
+ <div class="discussion-accordion">
1694
+ ${accordionItems}
1695
+ </div>
1696
+ </div>
1697
+ `;
1698
+ }
1699
+
1700
+ /**
1701
+ * Render Association Section (context-package key fields)
1702
+ * Shows solution summary, dependencies, consensus
1703
+ */
1704
+ function renderMultiCliAssociationSection(session) {
1705
+ const contextPkg = session.contextPackage || session.context_package || session.latestSynthesis?.context_package || {};
1706
+ const solutions = contextPkg.solutions || session.latestSynthesis?.solutions || [];
1707
+ const dependencies = contextPkg.dependencies || {};
1708
+ const consensus = contextPkg.consensus || session.latestSynthesis?._internal?.cross_verification || {};
1709
+ const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
1710
+
1711
+ // Check if we have any content to display
1712
+ const hasSolutions = solutions.length > 0;
1713
+ const hasDependencies = dependencies.internal?.length || dependencies.external?.length;
1714
+ const hasConsensus = consensus.agreements?.length || consensus.resolved_conflicts?.length || consensus.disagreements?.length;
1715
+ const hasFiles = relatedFiles?.fileTree?.length || relatedFiles?.impactSummary?.length;
1716
+
1717
+ if (!hasSolutions && !hasDependencies && !hasConsensus && !hasFiles) {
1718
+ return `
1719
+ <div class="tab-empty-state">
1720
+ <div class="empty-icon"><i data-lucide="link-2" class="w-12 h-12"></i></div>
1721
+ <div class="empty-title">${t('multiCli.empty.association') || 'No Association Data'}</div>
1722
+ <div class="empty-text">${t('multiCli.empty.associationText') || 'No context package or related files available for this session.'}</div>
1723
+ </div>
1724
+ `;
1725
+ }
1726
+
1727
+ let sections = [];
1728
+
1729
+ // Solution Summary Cards
1730
+ if (hasSolutions) {
1731
+ sections.push(`
1732
+ <div class="association-section solutions-section">
1733
+ <h4 class="association-section-title">
1734
+ <i data-lucide="lightbulb" class="w-4 h-4 inline"></i>
1735
+ ${t('multiCli.solutionSummary') || 'Solution Summary'}
1736
+ </h4>
1737
+ <div class="association-cards-grid">
1738
+ ${solutions.map((sol, idx) => `
1739
+ <div class="association-card solution-card">
1740
+ <div class="card-header">
1741
+ <span class="card-number">${idx + 1}</span>
1742
+ <span class="card-title">${escapeHtml(sol.name || 'Solution ' + (idx + 1))}</span>
1743
+ </div>
1744
+ <div class="card-metrics">
1745
+ <div class="metric">
1746
+ <span class="metric-label">${t('multiCli.feasibility') || 'Feasibility'}</span>
1747
+ <span class="metric-value">${Math.round((sol.feasibility || 0) * 100)}%</span>
1748
+ </div>
1749
+ <div class="metric">
1750
+ <span class="metric-label">${t('multiCli.effort') || 'Effort'}</span>
1751
+ <span class="metric-value effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'medium')}</span>
1752
+ </div>
1753
+ <div class="metric">
1754
+ <span class="metric-label">${t('multiCli.risk') || 'Risk'}</span>
1755
+ <span class="metric-value risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'medium')}</span>
1756
+ </div>
1757
+ </div>
1758
+ ${sol.summary ? `<p class="card-description">${escapeHtml(getI18nText(sol.summary))}</p>` : ''}
1759
+ </div>
1760
+ `).join('')}
1761
+ </div>
1762
+ </div>
1763
+ `);
1764
+ }
1765
+
1766
+ // Dependencies Card
1767
+ if (hasDependencies) {
1768
+ sections.push(`
1769
+ <div class="association-section dependencies-section">
1770
+ <h4 class="association-section-title">
1771
+ <i data-lucide="git-branch" class="w-4 h-4 inline"></i>
1772
+ ${t('multiCli.dependencies') || 'Dependencies'}
1773
+ </h4>
1774
+ <div class="dependencies-grid">
1775
+ ${dependencies.internal?.length ? `
1776
+ <div class="association-card dependency-card">
1777
+ <div class="card-header">
1778
+ <i data-lucide="folder" class="w-4 h-4"></i>
1779
+ <span class="card-title">${t('multiCli.internalDeps') || 'Internal'}</span>
1780
+ <span class="card-count">${dependencies.internal.length}</span>
1781
+ </div>
1782
+ <ul class="dependency-list">
1783
+ ${dependencies.internal.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
1784
+ </ul>
1785
+ </div>
1786
+ ` : ''}
1787
+ ${dependencies.external?.length ? `
1788
+ <div class="association-card dependency-card">
1789
+ <div class="card-header">
1790
+ <i data-lucide="package" class="w-4 h-4"></i>
1791
+ <span class="card-title">${t('multiCli.externalDeps') || 'External'}</span>
1792
+ <span class="card-count">${dependencies.external.length}</span>
1793
+ </div>
1794
+ <ul class="dependency-list">
1795
+ ${dependencies.external.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
1796
+ </ul>
1797
+ </div>
1798
+ ` : ''}
1799
+ </div>
1800
+ </div>
1801
+ `);
1802
+ }
1803
+
1804
+ // Consensus Card
1805
+ if (hasConsensus) {
1806
+ sections.push(`
1807
+ <div class="association-section consensus-section">
1808
+ <h4 class="association-section-title">
1809
+ <i data-lucide="check-check" class="w-4 h-4 inline"></i>
1810
+ ${t('multiCli.consensus') || 'Consensus'}
1811
+ </h4>
1812
+ <div class="consensus-grid">
1813
+ ${consensus.agreements?.length ? `
1814
+ <div class="association-card consensus-card agreements">
1815
+ <div class="card-header">
1816
+ <i data-lucide="thumbs-up" class="w-4 h-4"></i>
1817
+ <span class="card-title">${t('multiCli.agreements') || 'Agreements'}</span>
1818
+ <span class="card-count">${consensus.agreements.length}</span>
1819
+ </div>
1820
+ <ul class="consensus-list">
1821
+ ${consensus.agreements.map(item => `<li class="agreement-item">${escapeHtml(item)}</li>`).join('')}
1822
+ </ul>
1823
+ </div>
1824
+ ` : ''}
1825
+ ${(consensus.resolved_conflicts?.length || consensus.disagreements?.length) ? `
1826
+ <div class="association-card consensus-card conflicts">
1827
+ <div class="card-header">
1828
+ <i data-lucide="git-merge" class="w-4 h-4"></i>
1829
+ <span class="card-title">${t('multiCli.resolvedConflicts') || 'Resolved Conflicts'}</span>
1830
+ <span class="card-count">${(consensus.resolved_conflicts || consensus.disagreements || []).length}</span>
1831
+ </div>
1832
+ <ul class="consensus-list">
1833
+ ${(consensus.resolved_conflicts || consensus.disagreements || []).map(item => `<li class="conflict-item">${escapeHtml(item)}</li>`).join('')}
1834
+ </ul>
1835
+ </div>
1836
+ ` : ''}
1837
+ </div>
1838
+ </div>
1839
+ `);
1840
+ }
1841
+
1842
+ // Related Files (from existing Files tab logic)
1843
+ if (hasFiles) {
1844
+ sections.push(`
1845
+ <div class="association-section files-section">
1846
+ <h4 class="association-section-title">
1847
+ <i data-lucide="folder-tree" class="w-4 h-4 inline"></i>
1848
+ ${t('multiCli.tab.files') || 'Related Files'}
1849
+ </h4>
1850
+ <div class="files-summary">
1851
+ ${relatedFiles.fileTree?.length ? `
1852
+ <div class="files-list">
1853
+ ${relatedFiles.fileTree.slice(0, 10).map(file => `
1854
+ <div class="file-item">
1855
+ <i data-lucide="file" class="w-3 h-3"></i>
1856
+ <span class="file-path">${escapeHtml(typeof file === 'string' ? file : file.path || file.name)}</span>
1857
+ </div>
1858
+ `).join('')}
1859
+ ${relatedFiles.fileTree.length > 10 ? `
1860
+ <div class="files-more">+${relatedFiles.fileTree.length - 10} ${t('common.more') || 'more'}</div>
1861
+ ` : ''}
1862
+ </div>
1863
+ ` : ''}
1864
+ ${relatedFiles.impactSummary?.length ? `
1865
+ <div class="impact-summary">
1866
+ <h5>${t('multiCli.impactSummary') || 'Impact Summary'}</h5>
1867
+ <ul>
1868
+ ${relatedFiles.impactSummary.slice(0, 5).map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
1869
+ </ul>
1870
+ </div>
1871
+ ` : ''}
1872
+ </div>
1873
+ </div>
1874
+ `);
1875
+ }
1876
+
1877
+ return `
1878
+ <div class="multi-cli-association-section">
1879
+ ${sections.join('')}
1880
+ </div>
1881
+ `;
1882
+ }
1883
+
71
1884
  // Lite Task Detail Page
72
1885
  function showLiteTaskDetailPage(sessionKey) {
73
1886
  const session = liteTaskDataStore[sessionKey];