diff --git a/.claude/agents/cli-discuss-agent.md b/.claude/agents/cli-discuss-agent.md index e55d04e4..a9173738 100644 --- a/.claude/agents/cli-discuss-agent.md +++ b/.claude/agents/cli-discuss-agent.md @@ -45,220 +45,19 @@ You are a multi-CLI collaborative discussion agent. You orchestrate multiple CLI Write to: `{session.folder}/rounds/{round_number}/synthesis.json` -### Core Types - -```typescript -/** Multi-language label for UI display */ -interface I18nLabel { - en: string; - zh: string; -} - -/** Discussion status */ -type Status = 'exploring' | 'analyzing' | 'debating' | 'decided' | 'blocked'; - -/** Priority/Impact levels */ -type Level = 'critical' | 'high' | 'medium' | 'low'; - -/** Decision reversibility */ -type Reversibility = 'easily_reversible' | 'requires_refactoring' | 'irreversible'; - -/** Agent identifier */ -interface AgentIdentifier { - name: 'Gemini' | 'Codex' | 'Qwen' | 'Human'; - id: string; -} +**Schema Reference**: Load schema before generating output: +```bash +cat ~/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json ``` -### Main Artifact Structure - -```typescript -interface DiscussionArtifact { - metadata: ArtifactMetadata; - discussionTopic: DiscussionTopicSection; - relatedFiles: RelatedFilesSection; - planning: PlanningRequirementsSection; - decision: DecisionSection; - decisionRecords: DecisionRecordsSection; - - // Internal analysis data (for debugging/auditing) - _internal: { - cli_analyses: CLIAnalysis[]; - cross_verification: CrossVerification; - convergence: ConvergenceMetrics; - }; -} -``` - -### Section 1: Metadata - -```typescript -interface ArtifactMetadata { - artifactId: string; // e.g., "MCP-auth-refactor-2026-01-13-round-1" - roundId: number; - timestamp: string; // ISO 8601 - contributingAgents: AgentIdentifier[]; - durationSeconds: number; - exportFormats: ('markdown' | 'html')[]; -} -``` - -### Section 2: Discussion Topic (讨论主题) - -```typescript -interface DiscussionTopicSection { - title: I18nLabel; - description: I18nLabel; - scope: { - included: I18nLabel[]; // What's in scope - excluded: I18nLabel[]; // What's explicitly out of scope - }; - keyQuestions: I18nLabel[]; // Questions being explored - status: Status; - tags: string[]; // For filtering: ["auth", "security", "api"] -} -``` - -### Section 3: Related Files (关联文件) - -```typescript -interface RelatedFilesSection { - fileTree: FileNode[]; - dependencyGraph: DependencyEdge[]; - impactSummary: FileImpact[]; -} - -interface FileNode { - path: string; - type: 'file' | 'directory'; - modificationStatus: 'added' | 'modified' | 'deleted' | 'unchanged'; - impactScore?: Level; - children?: FileNode[]; - codeSnippet?: CodeSnippet; -} - -interface DependencyEdge { - source: string; // File path - target: string; // File path - relationship: string; // 'imports' | 'calls' | 'inherits' | 'uses' -} - -interface FileImpact { - filePath: string; - line?: number; - score: Level; - reasoning: I18nLabel; -} - -interface CodeSnippet { - startLine: number; - endLine: number; - code: string; - language: string; - comment?: I18nLabel; -} -``` - -### Section 4: Planning Requirements (规划要求) - -```typescript -interface PlanningRequirementsSection { - functional: Requirement[]; - nonFunctional: Requirement[]; - acceptanceCriteria: AcceptanceCriterion[]; -} - -interface Requirement { - id: string; // e.g., "FR-01", "NFR-01" - description: I18nLabel; - priority: Level; - source: string; // "User Request", "Technical Debt", etc. -} - -interface AcceptanceCriterion { - id: string; // e.g., "AC-01" - description: I18nLabel; - isMet: boolean; -} -``` - -### Section 5: Decision (决策) - -```typescript -interface DecisionSection { - status: 'pending' | 'decided' | 'conflict'; - summary: I18nLabel; - selectedSolution?: Solution; - rejectedAlternatives: RejectedSolution[]; - confidenceScore: number; // 0.0 to 1.0 -} - -interface Solution { - id: string; // e.g., "sol-jwt-01" - title: I18nLabel; - description: I18nLabel; - pros: I18nLabel[]; - cons: I18nLabel[]; - estimatedEffort: I18nLabel; // e.g., "3 developer-days" - risk: Level; - affectedFiles: FileImpact[]; - sourceCLIs: string[]; // Which CLIs proposed this -} - -interface RejectedSolution extends Solution { - rejectionReason: I18nLabel; -} -``` - -### Section 6: Decision Records (决策记录) - -```typescript -interface DecisionRecordsSection { - timeline: DecisionEvent[]; -} - -interface DecisionEvent { - eventId: string; // e.g., "evt-proposal-001" - timestamp: string; // ISO 8601 - type: 'proposal' | 'argument' | 'agreement' | 'disagreement' | 'decision' | 'reversal'; - contributor: AgentIdentifier; - summary: I18nLabel; - evidence: Evidence[]; - reversibility?: Reversibility; -} - -interface Evidence { - type: 'link' | 'code_snippet' | 'log_output' | 'benchmark' | 'reference'; - content: string | CodeSnippet; - description: I18nLabel; -} -``` - -### Internal Analysis Data - -```typescript -interface CLIAnalysis { - tool: 'gemini' | 'codex' | 'qwen'; - perspective: string; - feasibility_score: number; - findings: string[]; - implementation_approaches: ImplementationApproach[]; - technical_concerns: string[]; - code_locations: FileImpact[]; -} - -interface CrossVerification { - agreements: string[]; - disagreements: string[]; - resolution: string; -} - -interface ConvergenceMetrics { - score: number; - new_insights: boolean; - recommendation: 'continue' | 'converged' | 'user_input_needed'; -} -``` +**Main Sections**: +- `metadata`: Artifact ID, round, timestamp, contributing agents +- `discussionTopic`: Title, description, scope (included/excluded), key questions, status, tags +- `relatedFiles`: File tree, dependency graph, impact summary +- `planning`: Functional requirements, non-functional requirements, acceptance criteria +- `decision`: Status, summary, selected solution, rejected alternatives, confidence score +- `decisionRecords`: Timeline of decision events (proposals, agreements, disagreements) +- `_internal`: CLI analyses, cross-verification results, convergence metrics ## Execution Flow @@ -1118,46 +917,3 @@ function validateAnalysis(analysis) { - Generate more than 4 clarification questions - Ignore previous round context -## UI Component Mapping - -For dashboard visualization, map artifact sections to UI components: - -| Section | Component | Library Example | Notes | -|---------|-----------|-----------------|-------| -| **metadata** | | | | -| `roundId`, `timestamp` | `Tag`, `Badge` | Ant Design `Tag` | Header indicators | -| `contributingAgents` | `Avatar.Group` | Ant Design `Avatar.Group` | Agent icons with tooltips | -| `exportFormats` | `Dropdown` + `Button` | Material-UI `Menu` | Export actions | -| **discussionTopic** | `Card` | Bootstrap `Card` | Main section container | -| `title`, `description` | `Typography` | Any UI library | Standard text | -| `scope` | `List` with icons | Heroicons | Included/Excluded lists | -| `keyQuestions` | `Collapse` | Ant Design `Collapse` | Expandable Q&A | -| `status` | `Steps`, `Timeline` | Ant Design `Steps` | Progress indicator | -| **relatedFiles** | | | | -| `fileTree` | `Tree` | Ant Design `Tree` | Hierarchical file view | -| `dependencyGraph` | `Graph` | `vis-network`, `react-flow` | Interactive graph | -| `impactSummary` | `Table` | Ant Design `Table` | Sortable impact list | -| `codeSnippet` | `SyntaxHighlighter` | `react-syntax-highlighter` | Code with line numbers | -| **planning** | `Tabs` | Bootstrap `Navs` | FR/NFR/AC tabs | -| `functional/nonFunctional` | `Table` | Material-UI `Table` | Priority-sortable | -| `acceptanceCriteria` | `List` + `Checkbox` | Ant Design `List` | Checkable items | -| `priority` | `Tag` (color-coded) | Ant Design `Tag` | critical=red, high=orange | -| **decision** | | | | -| `summary` | `Alert`, `Callout` | Ant Design `Alert` | Prominent decision box | -| `selectedSolution` | `Card` (highlighted) | Bootstrap `Card` | Winner card | -| `rejectedAlternatives` | `Collapse` of `Card`s | Ant Design `Collapse` | Collapsed alternatives | -| `pros/cons` | `List` with icons | ThumbUp/ThumbDown | Visual indicators | -| `confidenceScore` | `Progress`, `Gauge` | Ant Design `Progress` | 0-100% visual | -| **decisionRecords** | | | | -| `timeline` | `Timeline` | Ant Design `Timeline`, `react-chrono` | Chronological events | -| `contributor` | `Avatar` + `Tooltip` | Ant Design `Avatar` | Who contributed | -| `evidence` | `Popover`, `Modal` | Ant Design `Popover` | Click to expand | -| `reversibility` | `Tag` with icon | SyncOutlined | Reversibility indicator | - -### Visualization Recommendations - -1. **Real-time Updates**: Use WebSocket or SSE for live synthesis.json updates -2. **Responsive Layout**: Card grid → stacked on mobile -3. **Dark/Light Theme**: CSS variables for theme switching -4. **Export**: Generate Markdown via template, HTML via React-to-static -5. **i18n Toggle**: Language switch button in header, read `en`/`zh` from I18nLabel diff --git a/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json b/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json new file mode 100644 index 00000000..c7a25d25 --- /dev/null +++ b/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json @@ -0,0 +1,421 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Multi-CLI Discussion Artifact Schema", + "description": "Visualization-friendly output for multi-CLI collaborative discussion agent", + "type": "object", + "required": ["metadata", "discussionTopic", "relatedFiles", "planning", "decision", "decisionRecords"], + "properties": { + "metadata": { + "type": "object", + "required": ["artifactId", "roundId", "timestamp", "contributingAgents"], + "properties": { + "artifactId": { + "type": "string", + "description": "Unique ID for this artifact (e.g., 'MCP-auth-refactor-2026-01-13-round-1')" + }, + "roundId": { + "type": "integer", + "minimum": 1, + "description": "Discussion round number" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp" + }, + "contributingAgents": { + "type": "array", + "items": { + "$ref": "#/definitions/AgentIdentifier" + }, + "description": "Agents that contributed to this artifact" + }, + "durationSeconds": { + "type": "integer", + "description": "Total duration in seconds" + }, + "exportFormats": { + "type": "array", + "items": { + "type": "string", + "enum": ["markdown", "html"] + }, + "description": "Supported export formats" + } + } + }, + "discussionTopic": { + "type": "object", + "required": ["title", "description", "status"], + "properties": { + "title": { + "$ref": "#/definitions/I18nLabel" + }, + "description": { + "$ref": "#/definitions/I18nLabel" + }, + "scope": { + "type": "object", + "properties": { + "included": { + "type": "array", + "items": { "$ref": "#/definitions/I18nLabel" }, + "description": "What's in scope" + }, + "excluded": { + "type": "array", + "items": { "$ref": "#/definitions/I18nLabel" }, + "description": "What's explicitly out of scope" + } + } + }, + "keyQuestions": { + "type": "array", + "items": { "$ref": "#/definitions/I18nLabel" }, + "description": "Questions being explored" + }, + "status": { + "type": "string", + "enum": ["exploring", "analyzing", "debating", "decided", "blocked"], + "description": "Discussion status" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Tags for filtering (e.g., ['auth', 'security', 'api'])" + } + } + }, + "relatedFiles": { + "type": "object", + "properties": { + "fileTree": { + "type": "array", + "items": { "$ref": "#/definitions/FileNode" }, + "description": "File tree structure" + }, + "dependencyGraph": { + "type": "array", + "items": { "$ref": "#/definitions/DependencyEdge" }, + "description": "Dependency relationships" + }, + "impactSummary": { + "type": "array", + "items": { "$ref": "#/definitions/FileImpact" }, + "description": "File impact summary" + } + } + }, + "planning": { + "type": "object", + "properties": { + "functional": { + "type": "array", + "items": { "$ref": "#/definitions/Requirement" }, + "description": "Functional requirements" + }, + "nonFunctional": { + "type": "array", + "items": { "$ref": "#/definitions/Requirement" }, + "description": "Non-functional requirements" + }, + "acceptanceCriteria": { + "type": "array", + "items": { "$ref": "#/definitions/AcceptanceCriterion" }, + "description": "Acceptance criteria" + } + } + }, + "decision": { + "type": "object", + "required": ["status", "confidenceScore"], + "properties": { + "status": { + "type": "string", + "enum": ["pending", "decided", "conflict"], + "description": "Decision status" + }, + "summary": { + "$ref": "#/definitions/I18nLabel" + }, + "selectedSolution": { + "$ref": "#/definitions/Solution" + }, + "rejectedAlternatives": { + "type": "array", + "items": { "$ref": "#/definitions/RejectedSolution" } + }, + "confidenceScore": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score (0.0 to 1.0)" + } + } + }, + "decisionRecords": { + "type": "object", + "properties": { + "timeline": { + "type": "array", + "items": { "$ref": "#/definitions/DecisionEvent" }, + "description": "Timeline of decision events" + } + } + }, + "_internal": { + "type": "object", + "description": "Internal analysis data (for debugging)", + "properties": { + "cli_analyses": { + "type": "array", + "items": { "$ref": "#/definitions/CLIAnalysis" } + }, + "cross_verification": { + "$ref": "#/definitions/CrossVerification" + }, + "convergence": { + "$ref": "#/definitions/ConvergenceMetrics" + } + } + } + }, + "definitions": { + "I18nLabel": { + "type": "object", + "required": ["en", "zh"], + "properties": { + "en": { "type": "string" }, + "zh": { "type": "string" } + }, + "description": "Multi-language label for UI display" + }, + "AgentIdentifier": { + "type": "object", + "required": ["name", "id"], + "properties": { + "name": { + "type": "string", + "enum": ["Gemini", "Codex", "Qwen", "Human", "System"] + }, + "id": { "type": "string" } + } + }, + "FileNode": { + "type": "object", + "required": ["path", "type"], + "properties": { + "path": { "type": "string" }, + "type": { + "type": "string", + "enum": ["file", "directory"] + }, + "modificationStatus": { + "type": "string", + "enum": ["added", "modified", "deleted", "unchanged"] + }, + "impactScore": { + "type": "string", + "enum": ["critical", "high", "medium", "low"] + }, + "children": { + "type": "array", + "items": { "$ref": "#/definitions/FileNode" } + }, + "codeSnippet": { "$ref": "#/definitions/CodeSnippet" } + } + }, + "DependencyEdge": { + "type": "object", + "required": ["source", "target", "relationship"], + "properties": { + "source": { "type": "string" }, + "target": { "type": "string" }, + "relationship": { "type": "string" } + } + }, + "FileImpact": { + "type": "object", + "required": ["filePath", "score", "reasoning"], + "properties": { + "filePath": { "type": "string" }, + "line": { "type": "integer" }, + "score": { + "type": "string", + "enum": ["critical", "high", "medium", "low"] + }, + "reasoning": { "$ref": "#/definitions/I18nLabel" } + } + }, + "CodeSnippet": { + "type": "object", + "required": ["startLine", "endLine", "code"], + "properties": { + "startLine": { "type": "integer" }, + "endLine": { "type": "integer" }, + "code": { "type": "string" }, + "language": { "type": "string" }, + "comment": { "$ref": "#/definitions/I18nLabel" } + } + }, + "Requirement": { + "type": "object", + "required": ["id", "description", "priority"], + "properties": { + "id": { "type": "string" }, + "description": { "$ref": "#/definitions/I18nLabel" }, + "priority": { + "type": "string", + "enum": ["critical", "high", "medium", "low"] + }, + "source": { "type": "string" } + } + }, + "AcceptanceCriterion": { + "type": "object", + "required": ["id", "description", "isMet"], + "properties": { + "id": { "type": "string" }, + "description": { "$ref": "#/definitions/I18nLabel" }, + "isMet": { "type": "boolean" } + } + }, + "Solution": { + "type": "object", + "required": ["id", "title", "description"], + "properties": { + "id": { "type": "string" }, + "title": { "$ref": "#/definitions/I18nLabel" }, + "description": { "$ref": "#/definitions/I18nLabel" }, + "pros": { + "type": "array", + "items": { "$ref": "#/definitions/I18nLabel" } + }, + "cons": { + "type": "array", + "items": { "$ref": "#/definitions/I18nLabel" } + }, + "estimatedEffort": { "$ref": "#/definitions/I18nLabel" }, + "risk": { + "type": "string", + "enum": ["critical", "high", "medium", "low"] + }, + "affectedFiles": { + "type": "array", + "items": { "$ref": "#/definitions/FileImpact" } + }, + "sourceCLIs": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "RejectedSolution": { + "allOf": [ + { "$ref": "#/definitions/Solution" }, + { + "type": "object", + "required": ["rejectionReason"], + "properties": { + "rejectionReason": { "$ref": "#/definitions/I18nLabel" } + } + } + ] + }, + "DecisionEvent": { + "type": "object", + "required": ["eventId", "timestamp", "type", "contributor", "summary"], + "properties": { + "eventId": { "type": "string" }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "type": { + "type": "string", + "enum": ["proposal", "argument", "agreement", "disagreement", "decision", "reversal"] + }, + "contributor": { "$ref": "#/definitions/AgentIdentifier" }, + "summary": { "$ref": "#/definitions/I18nLabel" }, + "evidence": { + "type": "array", + "items": { "$ref": "#/definitions/Evidence" } + }, + "reversibility": { + "type": "string", + "enum": ["easily_reversible", "requires_refactoring", "irreversible"] + } + } + }, + "Evidence": { + "type": "object", + "required": ["type", "content", "description"], + "properties": { + "type": { + "type": "string", + "enum": ["link", "code_snippet", "log_output", "benchmark", "reference"] + }, + "content": {}, + "description": { "$ref": "#/definitions/I18nLabel" } + } + }, + "CLIAnalysis": { + "type": "object", + "required": ["tool", "perspective", "feasibility_score"], + "properties": { + "tool": { + "type": "string", + "enum": ["gemini", "codex", "qwen"] + }, + "perspective": { "type": "string" }, + "feasibility_score": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "findings": { + "type": "array", + "items": { "type": "string" } + }, + "implementation_approaches": { "type": "array" }, + "technical_concerns": { + "type": "array", + "items": { "type": "string" } + }, + "code_locations": { + "type": "array", + "items": { "$ref": "#/definitions/FileImpact" } + } + } + }, + "CrossVerification": { + "type": "object", + "properties": { + "agreements": { + "type": "array", + "items": { "type": "string" } + }, + "disagreements": { + "type": "array", + "items": { "type": "string" } + }, + "resolution": { "type": "string" } + } + }, + "ConvergenceMetrics": { + "type": "object", + "properties": { + "score": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "new_insights": { "type": "boolean" }, + "recommendation": { + "type": "string", + "enum": ["continue", "converged", "user_input_needed"] + } + } + } + } +} diff --git a/ccw/src/core/lite-scanner.ts b/ccw/src/core/lite-scanner.ts index 8b6fef9f..fdb74ad4 100644 --- a/ccw/src/core/lite-scanner.ts +++ b/ccw/src/core/lite-scanner.ts @@ -63,6 +63,7 @@ interface LiteSession { interface LiteTasks { litePlan: LiteSession[]; liteFix: LiteSession[]; + multiCliPlan: LiteSession[]; } interface LiteTaskDetail { @@ -84,13 +85,15 @@ interface LiteTaskDetail { export async function scanLiteTasks(workflowDir: string): Promise { const litePlanDir = join(workflowDir, '.lite-plan'); const liteFixDir = join(workflowDir, '.lite-fix'); + const multiCliDir = join(workflowDir, '.multi-cli-plan'); - const [litePlan, liteFix] = await Promise.all([ + const [litePlan, liteFix, multiCliPlan] = await Promise.all([ scanLiteDir(litePlanDir, 'lite-plan'), scanLiteDir(liteFixDir, 'lite-fix'), + scanMultiCliDir(multiCliDir), ]); - return { litePlan, liteFix }; + return { litePlan, liteFix, multiCliPlan }; } /** @@ -142,6 +145,141 @@ async function scanLiteDir(dir: string, type: string): Promise { } } +/** + * Scan multi-cli-plan directory for sessions + * @param dir - Directory path to .multi-cli-plan + * @returns Array of multi-cli sessions + */ +async function scanMultiCliDir(dir: string): Promise { + try { + const entries = await readdir(dir, { withFileTypes: true }); + + const sessions = (await Promise.all( + entries + .filter((entry) => entry.isDirectory()) + .map(async (entry) => { + const sessionPath = join(dir, entry.name); + + const [createdAt, syntheses] = await Promise.all([ + getCreatedTime(sessionPath), + loadRoundSyntheses(sessionPath), + ]); + + // Extract plan from latest synthesis if available + const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null; + + // Calculate progress based on round count and convergence + const progress = calculateMultiCliProgress(syntheses); + + const session: LiteSession = { + id: entry.name, + type: 'multi-cli-plan', + path: sessionPath, + createdAt, + plan: latestSynthesis, + tasks: extractTasksFromSyntheses(syntheses), + progress, + }; + + return session; + }), + )) + .filter((session): session is LiteSession => session !== null) + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + + return sessions; + } catch (err: any) { + if (err?.code === 'ENOENT') return []; + console.error(`Error scanning ${dir}:`, err?.message || String(err)); + return []; + } +} + +interface RoundSynthesis { + round: number; + converged?: boolean; + tasks?: unknown[]; + synthesis?: unknown; + [key: string]: unknown; +} + +/** + * Load all synthesis.json files from rounds subdirectories + * @param sessionPath - Session directory path + * @returns Array of synthesis objects sorted by round number + */ +async function loadRoundSyntheses(sessionPath: string): Promise { + const roundsDir = join(sessionPath, 'rounds'); + const syntheses: RoundSynthesis[] = []; + + try { + const roundEntries = await readdir(roundsDir, { withFileTypes: true }); + + const roundDirs = roundEntries + .filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name)) + .map((entry) => ({ + name: entry.name, + num: parseInt(entry.name, 10), + })) + .sort((a, b) => a.num - b.num); + + for (const roundDir of roundDirs) { + const synthesisPath = join(roundsDir, roundDir.name, 'synthesis.json'); + try { + const content = await readFile(synthesisPath, 'utf8'); + const synthesis = JSON.parse(content) as RoundSynthesis; + synthesis.round = roundDir.num; + syntheses.push(synthesis); + } catch { + // Skip if synthesis.json doesn't exist or can't be parsed + } + } + } catch { + // Return empty array if rounds directory doesn't exist + } + + return syntheses; +} + +/** + * Calculate progress for multi-cli-plan sessions + * @param syntheses - Array of round syntheses + * @returns Progress info + */ +function calculateMultiCliProgress(syntheses: RoundSynthesis[]): Progress { + if (syntheses.length === 0) { + return { total: 0, completed: 0, percentage: 0 }; + } + + const latestSynthesis = syntheses[syntheses.length - 1]; + const isConverged = latestSynthesis.converged === true; + + // Total is based on expected rounds or actual rounds + const total = syntheses.length; + const completed = isConverged ? total : Math.max(0, total - 1); + const percentage = isConverged ? 100 : Math.round((completed / Math.max(total, 1)) * 100); + + return { total, completed, percentage }; +} + +/** + * Extract tasks from synthesis objects + * @param syntheses - Array of round syntheses + * @returns Normalized tasks from latest synthesis + */ +function extractTasksFromSyntheses(syntheses: RoundSynthesis[]): NormalizedTask[] { + if (syntheses.length === 0) return []; + + const latestSynthesis = syntheses[syntheses.length - 1]; + const tasks = latestSynthesis.tasks; + + if (!Array.isArray(tasks)) return []; + + return tasks + .map((task) => normalizeTask(task)) + .filter((task): task is NormalizedTask => task !== null); +} + /** * Load plan.json or fix-plan.json from session directory * @param sessionPath - Session directory path @@ -368,14 +506,19 @@ function calculateProgress(tasks: NormalizedTask[]): Progress { /** * Get detailed lite task info * @param workflowDir - Workflow directory - * @param type - 'lite-plan' or 'lite-fix' + * @param type - 'lite-plan', 'lite-fix', or 'multi-cli-plan' * @param sessionId - Session ID * @returns Detailed task info */ export async function getLiteTaskDetail(workflowDir: string, type: string, sessionId: string): Promise { - const dir = type === 'lite-plan' - ? join(workflowDir, '.lite-plan', sessionId) - : join(workflowDir, '.lite-fix', sessionId); + let dir: string; + if (type === 'lite-plan') { + dir = join(workflowDir, '.lite-plan', sessionId); + } else if (type === 'multi-cli-plan') { + dir = join(workflowDir, '.multi-cli-plan', sessionId); + } else { + dir = join(workflowDir, '.lite-fix', sessionId); + } try { const stats = await stat(dir); @@ -384,6 +527,29 @@ export async function getLiteTaskDetail(workflowDir: string, type: string, sessi return null; } + // For multi-cli-plan, use synthesis-based loading + if (type === 'multi-cli-plan') { + const [syntheses, explorations, clarifications] = await Promise.all([ + loadRoundSyntheses(dir), + loadExplorations(dir), + loadClarifications(dir), + ]); + + const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null; + + const detail: LiteTaskDetail = { + id: sessionId, + type, + path: dir, + plan: latestSynthesis, + tasks: extractTasksFromSyntheses(syntheses), + explorations, + clarifications, + }; + + return detail; + } + const [plan, tasks, explorations, clarifications, diagnoses] = await Promise.all([ loadPlanJson(dir), loadTaskJsons(dir), diff --git a/ccw/src/core/routes/session-routes.ts b/ccw/src/core/routes/session-routes.ts index 0a502f2d..b9a865f0 100644 --- a/ccw/src/core/routes/session-routes.ts +++ b/ccw/src/core/routes/session-routes.ts @@ -7,9 +7,9 @@ import { join } from 'path'; import type { RouteContext } from './types.js'; /** - * Get session detail data (context, summaries, impl-plan, review) + * Get session detail data (context, summaries, impl-plan, review, multi-cli) * @param {string} sessionPath - Path to session directory - * @param {string} dataType - Type of data to load ('all', 'context', 'tasks', 'summary', 'plan', 'explorations', 'conflict', 'impl-plan', 'review') + * @param {string} dataType - Type of data to load ('all', 'context', 'tasks', 'summary', 'plan', 'explorations', 'conflict', 'impl-plan', 'review', 'multi-cli', 'discussions') * @returns {Promise} */ async function getSessionDetailData(sessionPath: string, dataType: string): Promise> { @@ -251,6 +251,44 @@ async function getSessionDetailData(sessionPath: string, dataType: string): Prom } } + // Load multi-cli discussion rounds (rounds/*/synthesis.json) + if (dataType === 'multi-cli' || dataType === 'discussions' || dataType === 'all') { + result.multiCli = { + sessionId: normalizedPath.split('/').pop() || '', + type: 'multi-cli-plan', + rounds: [] as Array<{ roundNumber: number; synthesis: Record | null }> + }; + + const roundsDir = join(normalizedPath, 'rounds'); + if (existsSync(roundsDir)) { + try { + const roundDirs = readdirSync(roundsDir) + .filter(d => /^\d+$/.test(d)) // Only numeric directories + .sort((a, b) => parseInt(a) - parseInt(b)); + + for (const roundDir of roundDirs) { + const synthesisFile = join(roundsDir, roundDir, 'synthesis.json'); + let synthesis: Record | null = null; + + if (existsSync(synthesisFile)) { + try { + synthesis = JSON.parse(readFileSync(synthesisFile, 'utf8')); + } catch (e) { + // Skip unreadable synthesis files + } + } + + result.multiCli.rounds.push({ + roundNumber: parseInt(roundDir), + synthesis + }); + } + } catch (e) { + // Directory read failed + } + } + } + // Load review data from .review/ if (dataType === 'review' || dataType === 'all') { const reviewDir = join(normalizedPath, '.review'); diff --git a/ccw/src/templates/dashboard-css/01-base.css b/ccw/src/templates/dashboard-css/01-base.css index 08d8925a..e3b8b7fb 100644 --- a/ccw/src/templates/dashboard-css/01-base.css +++ b/ccw/src/templates/dashboard-css/01-base.css @@ -119,6 +119,14 @@ body { color: hsl(var(--orange)); } +.nav-item[data-lite="multi-cli-plan"].active { + background-color: hsl(var(--purple-light, 280 60% 95%)); +} + +.nav-item[data-lite="multi-cli-plan"].active .nav-icon { + color: hsl(var(--purple, 280 60% 50%)); +} + .sidebar.collapsed .toggle-icon { transform: rotate(180deg); } diff --git a/ccw/src/templates/dashboard-css/04-lite-tasks.css b/ccw/src/templates/dashboard-css/04-lite-tasks.css index 64014d28..5a7cd810 100644 --- a/ccw/src/templates/dashboard-css/04-lite-tasks.css +++ b/ccw/src/templates/dashboard-css/04-lite-tasks.css @@ -1179,3 +1179,799 @@ line-height: 1.5; } +/* =================================== + Multi-CLI Discussion View Styles + =================================== */ + +/* Multi-CLI Card (List View) */ +.multi-cli-card { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 1rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.multi-cli-card:hover { + border-color: hsl(var(--purple, 280 60% 50%) / 0.5); + box-shadow: 0 4px 12px hsl(var(--purple, 280 60% 50%) / 0.1); +} + +.multi-cli-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.75rem; +} + +.multi-cli-card-title { + font-weight: 600; + font-size: 0.95rem; + color: hsl(var(--foreground)); + line-height: 1.4; + flex: 1; +} + +.multi-cli-card-meta { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; + font-size: 0.8rem; + color: hsl(var(--muted-foreground)); +} + +.multi-cli-round-count { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + background: hsl(var(--purple-light, 280 60% 95%)); + color: hsl(var(--purple, 280 60% 50%)); + border-radius: 0.25rem; + font-size: 0.75rem; + font-weight: 500; +} + +/* Multi-CLI Status Badges */ +.multi-cli-status { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + font-weight: 500; +} + +.multi-cli-status.converged { + background: hsl(var(--success-light, 142 70% 95%)); + color: hsl(var(--success, 142 70% 45%)); +} + +.multi-cli-status.analyzing { + background: hsl(var(--warning-light, 45 90% 95%)); + color: hsl(var(--warning, 45 90% 40%)); +} + +.multi-cli-status.blocked { + background: hsl(var(--destructive) / 0.1); + color: hsl(var(--destructive)); +} + +.multi-cli-status.pending { + background: hsl(var(--muted)); + color: hsl(var(--muted-foreground)); +} + +/* Multi-CLI Detail Page */ +.multi-cli-detail-page { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.multi-cli-detail-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid hsl(var(--border)); +} + +.multi-cli-detail-info { + flex: 1; +} + +.multi-cli-detail-title { + font-size: 1.25rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.5rem; +} + +.multi-cli-detail-meta { + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; + font-size: 0.85rem; + color: hsl(var(--muted-foreground)); +} + +/* Multi-CLI Tabs */ +.multi-cli-tabs { + display: flex; + gap: 0.25rem; + border-bottom: 1px solid hsl(var(--border)); + margin-bottom: 1rem; + overflow-x: auto; +} + +.multi-cli-tab { + padding: 0.75rem 1rem; + font-size: 0.875rem; + font-weight: 500; + color: hsl(var(--muted-foreground)); + background: transparent; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.multi-cli-tab:hover { + color: hsl(var(--foreground)); + background: hsl(var(--hover)); +} + +.multi-cli-tab.active { + color: hsl(var(--purple, 280 60% 50%)); + border-bottom-color: hsl(var(--purple, 280 60% 50%)); +} + +.multi-cli-tab-content { + display: none; +} + +.multi-cli-tab-content.active { + display: block; +} + +/* Multi-CLI Topic Tab */ +.multi-cli-topic-section { + background: hsl(var(--muted) / 0.3); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 1rem; + margin-bottom: 1rem; +} + +.multi-cli-topic-title { + font-size: 1rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.multi-cli-topic-description { + font-size: 0.875rem; + color: hsl(var(--foreground)); + line-height: 1.6; + white-space: pre-wrap; +} + +.multi-cli-complexity-badge { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + font-weight: 500; +} + +.multi-cli-complexity-badge.high { + background: hsl(var(--destructive) / 0.1); + color: hsl(var(--destructive)); +} + +.multi-cli-complexity-badge.medium { + background: hsl(var(--warning-light, 45 90% 95%)); + color: hsl(var(--warning, 45 90% 40%)); +} + +.multi-cli-complexity-badge.low { + background: hsl(var(--success-light, 142 70% 95%)); + color: hsl(var(--success, 142 70% 45%)); +} + +/* Multi-CLI Files Tab */ +.multi-cli-files-section { + margin-bottom: 1rem; +} + +.multi-cli-files-title { + font-size: 0.9rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.75rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.file-tree { + background: hsl(var(--muted) / 0.3); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 0.75rem; +} + +.file-tree-node { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.5rem; + font-size: 0.85rem; + border-radius: 0.25rem; + transition: background 0.15s; +} + +.file-tree-node:hover { + background: hsl(var(--hover)); +} + +.file-tree-node.directory { + color: hsl(var(--primary)); + font-weight: 500; +} + +.file-tree-node.file { + color: hsl(var(--foreground)); + padding-left: 1.5rem; +} + +.file-tree-node .file-icon { + width: 1rem; + height: 1rem; + color: hsl(var(--muted-foreground)); +} + +.file-tree-node.directory .file-icon { + color: hsl(var(--primary)); +} + +.file-purpose { + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); + margin-left: auto; +} + +/* Multi-CLI Planning Tab */ +.multi-cli-planning-section { + margin-bottom: 1.5rem; +} + +.planning-section-title { + font-size: 0.9rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.75rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.requirements-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.requirement-item { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 0.75rem; +} + +.requirement-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.requirement-id { + font-weight: 600; + font-size: 0.8rem; + color: hsl(var(--primary)); +} + +.requirement-priority { + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + font-size: 0.7rem; + font-weight: 500; +} + +.requirement-priority.high { + background: hsl(var(--destructive) / 0.1); + color: hsl(var(--destructive)); +} + +.requirement-priority.medium { + background: hsl(var(--warning-light, 45 90% 95%)); + color: hsl(var(--warning, 45 90% 40%)); +} + +.requirement-priority.low { + background: hsl(var(--success-light, 142 70% 95%)); + color: hsl(var(--success, 142 70% 45%)); +} + +.requirement-title { + font-size: 0.875rem; + font-weight: 500; + color: hsl(var(--foreground)); + margin-bottom: 0.25rem; +} + +.requirement-description { + font-size: 0.8rem; + color: hsl(var(--muted-foreground)); + line-height: 1.5; +} + +.impact-items { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.impact-item { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + background: hsl(var(--muted)); + border-radius: 0.25rem; + font-size: 0.75rem; + color: hsl(var(--foreground)); +} + +.impact-item .impact-icon { + width: 0.875rem; + height: 0.875rem; + color: hsl(var(--muted-foreground)); +} + +/* Multi-CLI Decision Tab */ +.multi-cli-decision-section { + margin-bottom: 1.5rem; +} + +.decision-section-title { + font-size: 0.9rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.75rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.solutions-grid { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.solution-card { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 1rem; + transition: all 0.2s ease; +} + +.solution-card.selected { + border-color: hsl(var(--success, 142 70% 45%)); + background: hsl(var(--success, 142 70% 45%) / 0.05); +} + +.solution-card.rejected { + border-color: hsl(var(--destructive) / 0.5); + background: hsl(var(--destructive) / 0.03); + opacity: 0.8; +} + +.solution-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.75rem; +} + +.solution-title { + font-weight: 600; + font-size: 0.95rem; + color: hsl(var(--foreground)); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.solution-status { + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + font-weight: 500; +} + +.solution-status.selected { + background: hsl(var(--success, 142 70% 45%) / 0.15); + color: hsl(var(--success, 142 70% 45%)); +} + +.solution-status.rejected { + background: hsl(var(--destructive) / 0.1); + color: hsl(var(--destructive)); +} + +.solution-status.considering { + background: hsl(var(--warning-light, 45 90% 95%)); + color: hsl(var(--warning, 45 90% 40%)); +} + +.solution-description { + font-size: 0.85rem; + color: hsl(var(--foreground)); + line-height: 1.6; + margin-bottom: 0.75rem; +} + +.solution-meta { + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; +} + +.solution-meta-item { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: 0.8rem; + color: hsl(var(--muted-foreground)); +} + +.confidence-meter { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.confidence-bar { + width: 60px; + height: 6px; + background: hsl(var(--muted)); + border-radius: 3px; + overflow: hidden; +} + +.confidence-fill { + height: 100%; + border-radius: 3px; + transition: width 0.3s ease; +} + +.confidence-fill.high { + background: hsl(var(--success, 142 70% 45%)); +} + +.confidence-fill.medium { + background: hsl(var(--warning, 45 90% 50%)); +} + +.confidence-fill.low { + background: hsl(var(--destructive)); +} + +.confidence-value { + font-size: 0.75rem; + font-weight: 500; + color: hsl(var(--foreground)); +} + +/* Multi-CLI Timeline Tab */ +.multi-cli-timeline { + display: flex; + flex-direction: column; + gap: 0; +} + +.timeline-event { + display: flex; + gap: 1rem; + position: relative; +} + +.timeline-marker { + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; + width: 2rem; +} + +.timeline-dot { + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + flex-shrink: 0; +} + +.timeline-dot.proposal { + background: hsl(var(--primary)); + color: white; +} + +.timeline-dot.analysis { + background: hsl(var(--info, 220 80% 55%)); + color: white; +} + +.timeline-dot.decision { + background: hsl(var(--success, 142 70% 45%)); + color: white; +} + +.timeline-dot.conflict { + background: hsl(var(--warning, 45 90% 50%)); + color: hsl(var(--foreground)); +} + +.timeline-dot.resolution { + background: hsl(var(--purple, 280 60% 50%)); + color: white; +} + +.timeline-dot i { + width: 0.75rem; + height: 0.75rem; +} + +.timeline-line { + width: 2px; + flex: 1; + min-height: 1rem; + background: hsl(var(--border)); + margin: 0.25rem 0; +} + +.timeline-content { + flex: 1; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 0.75rem; + margin-bottom: 0.75rem; +} + +.timeline-content:hover { + border-color: hsl(var(--primary) / 0.3); +} + +.timeline-event-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.timeline-event-type { + font-weight: 600; + font-size: 0.85rem; + color: hsl(var(--foreground)); +} + +.timeline-event-time { + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); +} + +.timeline-event-agent { + font-size: 0.75rem; + color: hsl(var(--purple, 280 60% 50%)); + margin-bottom: 0.25rem; +} + +.timeline-event-description { + font-size: 0.85rem; + color: hsl(var(--foreground)); + line-height: 1.5; +} + +/* Multi-CLI Rounds Tab */ +.multi-cli-rounds-nav { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid hsl(var(--border)); +} + +.round-nav-btn { + padding: 0.5rem 1rem; + background: hsl(var(--muted)); + border: 1px solid hsl(var(--border)); + border-radius: 0.375rem; + font-size: 0.85rem; + font-weight: 500; + color: hsl(var(--foreground)); + cursor: pointer; + transition: all 0.2s ease; +} + +.round-nav-btn:hover { + background: hsl(var(--hover)); + border-color: hsl(var(--purple, 280 60% 50%) / 0.5); +} + +.round-nav-btn.active { + background: hsl(var(--purple, 280 60% 50%)); + border-color: hsl(var(--purple, 280 60% 50%)); + color: white; +} + +.round-content { + background: hsl(var(--muted) / 0.3); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 1rem; +} + +.round-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid hsl(var(--border)); +} + +.round-title { + font-weight: 600; + font-size: 1rem; + color: hsl(var(--foreground)); +} + +.round-timestamp { + font-size: 0.8rem; + color: hsl(var(--muted-foreground)); +} + +.round-section { + margin-bottom: 1rem; +} + +.round-section:last-child { + margin-bottom: 0; +} + +.round-section-title { + font-size: 0.85rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.round-agents { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.round-agent-item { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.375rem; + padding: 0.75rem; +} + +.round-agent-name { + font-weight: 500; + font-size: 0.85rem; + color: hsl(var(--purple, 280 60% 50%)); + margin-bottom: 0.25rem; +} + +.round-agent-response { + font-size: 0.8rem; + color: hsl(var(--foreground)); + line-height: 1.5; + white-space: pre-wrap; +} + +.round-convergence { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.375rem; +} + +.convergence-indicator { + font-weight: 500; + font-size: 0.85rem; +} + +.convergence-indicator.converged { + color: hsl(var(--success, 142 70% 45%)); +} + +.convergence-indicator.not-converged { + color: hsl(var(--warning, 45 90% 40%)); +} + +.round-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + color: hsl(var(--muted-foreground)); + font-size: 0.9rem; +} + +/* Multi-CLI Empty States */ +.multi-cli-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem; + text-align: center; + color: hsl(var(--muted-foreground)); +} + +.multi-cli-empty-icon { + width: 3rem; + height: 3rem; + margin-bottom: 1rem; + color: hsl(var(--muted-foreground) / 0.5); +} + +.multi-cli-empty-title { + font-size: 1rem; + font-weight: 500; + margin-bottom: 0.5rem; + color: hsl(var(--foreground)); +} + +.multi-cli-empty-description { + font-size: 0.875rem; +} + diff --git a/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css b/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css index 3380f1ca..f5452109 100644 --- a/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +++ b/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css @@ -699,6 +699,28 @@ color: hsl(var(--foreground)); } +.file-browser-path:focus { + outline: none; + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2); +} + +.file-browser-drives { + display: flex; + gap: 0.25rem; +} + +.btn-xs { + padding: 0.25rem 0.5rem; + font-size: 0.6875rem; + border-radius: 0.25rem; +} + +.drive-btn { + font-family: monospace; + font-weight: 600; +} + .file-browser-hidden-toggle { display: flex; align-items: center; diff --git a/ccw/src/templates/dashboard-js/components/navigation.js b/ccw/src/templates/dashboard-js/components/navigation.js index 8e92a040..bffcde11 100644 --- a/ccw/src/templates/dashboard-js/components/navigation.js +++ b/ccw/src/templates/dashboard-js/components/navigation.js @@ -216,8 +216,10 @@ function updateContentTitle() { } else if (currentView === 'issue-discovery') { titleEl.textContent = t('title.issueDiscovery'); } else if (currentView === 'liteTasks') { - const names = { 'lite-plan': t('title.litePlanSessions'), 'lite-fix': t('title.liteFixSessions') }; + const names = { 'lite-plan': t('title.litePlanSessions'), 'lite-fix': t('title.liteFixSessions'), 'multi-cli-plan': t('title.multiCliPlanSessions') || 'Multi-CLI Plan Sessions' }; titleEl.textContent = names[currentLiteType] || t('title.liteTasks'); + } else if (currentView === 'multiCliDetail') { + titleEl.textContent = t('title.multiCliDetail') || 'Multi-CLI Discussion Detail'; } else if (currentView === 'sessionDetail') { titleEl.textContent = t('title.sessionDetail'); } else if (currentView === 'liteTaskDetail') { @@ -322,9 +324,11 @@ function updateSidebarCounts(data) { // Update lite task counts const litePlanCount = document.querySelector('.nav-item[data-lite="lite-plan"] .nav-count'); const liteFixCount = document.querySelector('.nav-item[data-lite="lite-fix"] .nav-count'); + const multiCliPlanCount = document.querySelector('.nav-item[data-lite="multi-cli-plan"] .nav-count'); if (litePlanCount) litePlanCount.textContent = data.liteTasks?.litePlan?.length || 0; if (liteFixCount) liteFixCount.textContent = data.liteTasks?.liteFix?.length || 0; + if (multiCliPlanCount) multiCliPlanCount.textContent = data.liteTasks?.multiCliPlan?.length || 0; } // ========== Navigation Badge Aggregation ========== diff --git a/ccw/src/templates/dashboard-js/i18n.js b/ccw/src/templates/dashboard-js/i18n.js index e5a1068a..46ad486d 100644 --- a/ccw/src/templates/dashboard-js/i18n.js +++ b/ccw/src/templates/dashboard-js/i18n.js @@ -83,7 +83,8 @@ const i18n = { 'nav.liteTasks': 'Lite Tasks', 'nav.litePlan': 'Lite Plan', 'nav.liteFix': 'Lite Fix', - + 'nav.multiCliPlan': 'Multi-CLI Plan', + // Sidebar - MCP section 'nav.mcpServers': 'MCP Servers', 'nav.manage': 'Manage', @@ -119,9 +120,11 @@ const i18n = { 'title.cliHistory': 'CLI Execution History', 'title.litePlanSessions': 'Lite Plan Sessions', 'title.liteFixSessions': 'Lite Fix Sessions', + 'title.multiCliPlanSessions': 'Multi-CLI Plan Sessions', 'title.liteTasks': 'Lite Tasks', 'title.sessionDetail': 'Session Detail', 'title.liteTaskDetail': 'Lite Task Detail', + 'title.multiCliDetail': 'Multi-CLI Discussion Detail', 'title.hookManager': 'Hook Manager', 'title.memoryModule': 'Memory Module', 'title.promptHistory': 'Prompt History', @@ -268,6 +271,7 @@ const i18n = { 'cli.envFilePlaceholder': 'Path to .env file (e.g., ~/.gemini-env or C:/Users/xxx/.env)', 'cli.envFileHint': 'Load environment variables (e.g., API keys) before CLI execution. Supports ~ for home directory.', 'cli.envFileBrowse': 'Browse', + 'cli.envFilePathHint': 'Please verify or complete the file path (e.g., ~/.gemini-env)', 'cli.fileBrowser': 'File Browser', 'cli.fileBrowserSelect': 'Select', 'cli.fileBrowserCancel': 'Cancel', @@ -1201,7 +1205,62 @@ const i18n = { 'lite.diagnosisDetails': 'Diagnosis Details', 'lite.totalDiagnoses': 'Total Diagnoses:', 'lite.angles': 'Angles:', - + 'lite.multiCli': 'Multi-CLI', + + // Multi-CLI Plan + 'multiCli.rounds': 'rounds', + 'multiCli.backToList': 'Back to Multi-CLI Plan', + 'multiCli.roundCount': 'Rounds', + 'multiCli.topic': 'Topic', + 'multiCli.tab.topic': 'Discussion Topic', + 'multiCli.tab.files': 'Related Files', + 'multiCli.tab.planning': 'Planning', + 'multiCli.tab.decision': 'Decision', + 'multiCli.tab.timeline': 'Timeline', + 'multiCli.tab.rounds': 'Rounds', + 'multiCli.scope': 'Scope', + 'multiCli.scope.included': 'Included', + 'multiCli.scope.excluded': 'Excluded', + 'multiCli.keyQuestions': 'Key Questions', + 'multiCli.fileTree': 'File Tree', + 'multiCli.impactSummary': 'Impact Summary', + 'multiCli.dependencies': 'Dependencies', + 'multiCli.functional': 'Functional Requirements', + 'multiCli.nonFunctional': 'Non-Functional Requirements', + 'multiCli.acceptanceCriteria': 'Acceptance Criteria', + 'multiCli.source': 'Source', + 'multiCli.confidence': 'Confidence', + 'multiCli.selectedSolution': 'Selected Solution', + 'multiCli.rejectedAlternatives': 'Rejected Alternatives', + 'multiCli.rejectionReason': 'Reason', + 'multiCli.pros': 'Pros', + 'multiCli.cons': 'Cons', + 'multiCli.effort': 'Effort', + 'multiCli.sources': 'Sources', + 'multiCli.currentRound': 'Current', + 'multiCli.singleRoundInfo': 'This is a single-round discussion. View other tabs for details.', + 'multiCli.noRoundData': 'No data for this round.', + 'multiCli.roundId': 'Round', + 'multiCli.timestamp': 'Time', + 'multiCli.duration': 'Duration', + 'multiCli.contributors': 'Contributors', + 'multiCli.convergence': 'Convergence', + 'multiCli.newInsights': 'New Insights', + 'multiCli.crossVerification': 'Cross-Verification', + 'multiCli.agreements': 'Agreements', + 'multiCli.disagreements': 'Disagreements', + 'multiCli.resolution': 'Resolution', + 'multiCli.empty.topic': 'No Discussion Topic', + 'multiCli.empty.topicText': 'No discussion topic data available for this session.', + 'multiCli.empty.files': 'No Related Files', + 'multiCli.empty.filesText': 'No file analysis data available for this session.', + 'multiCli.empty.planning': 'No Planning Data', + 'multiCli.empty.planningText': 'No planning requirements available for this session.', + 'multiCli.empty.decision': 'No Decision Yet', + 'multiCli.empty.decisionText': 'No decision has been made for this discussion yet.', + 'multiCli.empty.timeline': 'No Timeline Events', + 'multiCli.empty.timelineText': 'No decision timeline available for this session.', + // Modals 'modal.contentPreview': 'Content Preview', 'modal.raw': 'Raw', @@ -2263,7 +2322,8 @@ const i18n = { 'nav.liteTasks': '轻量任务', 'nav.litePlan': '轻量规划', 'nav.liteFix': '轻量修复', - + 'nav.multiCliPlan': '多CLI规划', + // Sidebar - MCP section 'nav.mcpServers': 'MCP 服务器', 'nav.manage': '管理', @@ -2299,9 +2359,11 @@ const i18n = { 'title.cliHistory': 'CLI 执行历史', 'title.litePlanSessions': '轻量规划会话', 'title.liteFixSessions': '轻量修复会话', + 'title.multiCliPlanSessions': '多CLI规划会话', 'title.liteTasks': '轻量任务', 'title.sessionDetail': '会话详情', 'title.liteTaskDetail': '轻量任务详情', + 'title.multiCliDetail': '多CLI讨论详情', 'title.hookManager': '钩子管理', 'title.memoryModule': '记忆模块', 'title.promptHistory': '提示历史', @@ -2448,6 +2510,7 @@ const i18n = { 'cli.envFilePlaceholder': '.env 文件路径(如 ~/.gemini-env 或 C:/Users/xxx/.env)', 'cli.envFileHint': '在 CLI 执行前加载环境变量(如 API 密钥)。支持 ~ 表示用户目录。', 'cli.envFileBrowse': '浏览', + 'cli.envFilePathHint': '请确认或补全文件路径(如 ~/.gemini-env)', 'cli.fileBrowser': '文件浏览器', 'cli.fileBrowserSelect': '选择', 'cli.fileBrowserCancel': '取消', @@ -3360,7 +3423,62 @@ const i18n = { 'lite.diagnosisDetails': '诊断详情', 'lite.totalDiagnoses': '总诊断数:', 'lite.angles': '分析角度:', - + 'lite.multiCli': '多CLI', + + // Multi-CLI Plan + 'multiCli.rounds': '轮', + 'multiCli.backToList': '返回多CLI计划', + 'multiCli.roundCount': '轮数', + 'multiCli.topic': '主题', + 'multiCli.tab.topic': '讨论主题', + 'multiCli.tab.files': '相关文件', + 'multiCli.tab.planning': '规划', + 'multiCli.tab.decision': '决策', + 'multiCli.tab.timeline': '时间线', + 'multiCli.tab.rounds': '轮次', + 'multiCli.scope': '范围', + 'multiCli.scope.included': '包含', + 'multiCli.scope.excluded': '排除', + 'multiCli.keyQuestions': '关键问题', + 'multiCli.fileTree': '文件树', + 'multiCli.impactSummary': '影响摘要', + 'multiCli.dependencies': '依赖关系', + 'multiCli.functional': '功能需求', + 'multiCli.nonFunctional': '非功能需求', + 'multiCli.acceptanceCriteria': '验收标准', + 'multiCli.source': '来源', + 'multiCli.confidence': '置信度', + 'multiCli.selectedSolution': '选定方案', + 'multiCli.rejectedAlternatives': '被拒绝的备选方案', + 'multiCli.rejectionReason': '原因', + 'multiCli.pros': '优点', + 'multiCli.cons': '缺点', + 'multiCli.effort': '工作量', + 'multiCli.sources': '来源', + 'multiCli.currentRound': '当前', + 'multiCli.singleRoundInfo': '这是单轮讨论。查看其他标签页获取详情。', + 'multiCli.noRoundData': '此轮无数据。', + 'multiCli.roundId': '轮次', + 'multiCli.timestamp': '时间', + 'multiCli.duration': '持续时间', + 'multiCli.contributors': '贡献者', + 'multiCli.convergence': '收敛度', + 'multiCli.newInsights': '新发现', + 'multiCli.crossVerification': '交叉验证', + 'multiCli.agreements': '一致意见', + 'multiCli.disagreements': '分歧', + 'multiCli.resolution': '决议', + 'multiCli.empty.topic': '无讨论主题', + 'multiCli.empty.topicText': '此会话无可用的讨论主题数据。', + 'multiCli.empty.files': '无相关文件', + 'multiCli.empty.filesText': '此会话无可用的文件分析数据。', + 'multiCli.empty.planning': '无规划数据', + 'multiCli.empty.planningText': '此会话无可用的规划需求。', + 'multiCli.empty.decision': '暂无决策', + 'multiCli.empty.decisionText': '此讨论尚未做出决策。', + 'multiCli.empty.timeline': '无时间线事件', + 'multiCli.empty.timelineText': '此会话无可用的决策时间线。', + // Modals 'modal.contentPreview': '内容预览', 'modal.raw': '原始', diff --git a/ccw/src/templates/dashboard-js/views/cli-manager.js b/ccw/src/templates/dashboard-js/views/cli-manager.js index 830a7fbf..1516fcf5 100644 --- a/ccw/src/templates/dashboard-js/views/cli-manager.js +++ b/ccw/src/templates/dashboard-js/views/cli-manager.js @@ -584,6 +584,17 @@ function showFileBrowserModal(onSelect) { } function buildFileBrowserModalContent() { + // Detect if Windows + var isWindows = navigator.platform.indexOf('Win') > -1; + var driveButtons = ''; + if (isWindows) { + driveButtons = '
' + + '' + + '' + + '' + + '
'; + } + return '