mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat: Review Session增加Fix进度跟踪卡片,移除独立Dashboard模板
- 新增Fix Progress跟踪卡片(走马灯样式)显示修复进度 - 添加/api/file端点支持读取fix-plan.json - 移除review-fix/module-cycle/session-cycle中的独立dashboard生成 - 删除废弃的workflow-dashboard.html和review-cycle-dashboard.html模板 - 统一使用ccw view命令查看进度 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -46,8 +46,7 @@ Automated fix orchestrator with **two-phase architecture**: AI-powered planning
|
|||||||
1. **Intelligent Planning**: AI-powered analysis identifies optimal grouping and execution strategy
|
1. **Intelligent Planning**: AI-powered analysis identifies optimal grouping and execution strategy
|
||||||
2. **Multi-stage Coordination**: Supports complex parallel + serial execution with dependency management
|
2. **Multi-stage Coordination**: Supports complex parallel + serial execution with dependency management
|
||||||
3. **Conservative Safety**: Mandatory test verification with automatic rollback on failure
|
3. **Conservative Safety**: Mandatory test verification with automatic rollback on failure
|
||||||
4. **Real-time Visibility**: Dashboard shows planning progress, stage timeline, and active agents
|
4. **Resume Support**: Checkpoint-based recovery for interrupted sessions
|
||||||
5. **Resume Support**: Checkpoint-based recovery for interrupted sessions
|
|
||||||
|
|
||||||
### Orchestrator Boundary (CRITICAL)
|
### Orchestrator Boundary (CRITICAL)
|
||||||
- **ONLY command** for automated review finding fixes
|
- **ONLY command** for automated review finding fixes
|
||||||
@@ -59,14 +58,14 @@ Automated fix orchestrator with **two-phase architecture**: AI-powered planning
|
|||||||
|
|
||||||
```
|
```
|
||||||
Phase 1: Discovery & Initialization
|
Phase 1: Discovery & Initialization
|
||||||
└─ Validate export file, create fix session structure, initialize state files → Generate fix-dashboard.html
|
└─ Validate export file, create fix session structure, initialize state files
|
||||||
|
|
||||||
Phase 2: Planning Coordination (@cli-planning-agent)
|
Phase 2: Planning Coordination (@cli-planning-agent)
|
||||||
├─ Analyze findings for patterns and dependencies
|
├─ Analyze findings for patterns and dependencies
|
||||||
├─ Group by file + dimension + root cause similarity
|
├─ Group by file + dimension + root cause similarity
|
||||||
├─ Determine execution strategy (parallel/serial/hybrid)
|
├─ Determine execution strategy (parallel/serial/hybrid)
|
||||||
├─ Generate fix timeline with stages
|
├─ Generate fix timeline with stages
|
||||||
└─ Output: fix-plan.json (dashboard auto-polls for status)
|
└─ Output: fix-plan.json
|
||||||
|
|
||||||
Phase 3: Execution Orchestration (Stage-based)
|
Phase 3: Execution Orchestration (Stage-based)
|
||||||
For each timeline stage:
|
For each timeline stage:
|
||||||
@@ -198,12 +197,10 @@ if (result.passRate < 100%) {
|
|||||||
- Session creation: Generate fix-session-id (`fix-{timestamp}`)
|
- Session creation: Generate fix-session-id (`fix-{timestamp}`)
|
||||||
- Directory structure: Create `{review-dir}/fixes/{fix-session-id}/` with subdirectories
|
- Directory structure: Create `{review-dir}/fixes/{fix-session-id}/` with subdirectories
|
||||||
- State files: Initialize active-fix-session.json (session marker)
|
- State files: Initialize active-fix-session.json (session marker)
|
||||||
- Dashboard generation: Create fix-dashboard.html from template (see Dashboard Generation below)
|
|
||||||
- TodoWrite initialization: Set up 4-phase tracking
|
- TodoWrite initialization: Set up 4-phase tracking
|
||||||
|
|
||||||
**Phase 2: Planning Coordination**
|
**Phase 2: Planning Coordination**
|
||||||
- Launch @cli-planning-agent with findings data and project context
|
- Launch @cli-planning-agent with findings data and project context
|
||||||
- Monitor planning progress (dashboard shows "Planning fixes..." indicator)
|
|
||||||
- Validate fix-plan.json output (schema conformance, includes metadata with session status)
|
- Validate fix-plan.json output (schema conformance, includes metadata with session status)
|
||||||
- Load plan into memory for execution phase
|
- Load plan into memory for execution phase
|
||||||
- TodoWrite update: Mark planning complete, start execution
|
- TodoWrite update: Mark planning complete, start execution
|
||||||
@@ -216,7 +213,6 @@ if (result.passRate < 100%) {
|
|||||||
- Assign agent IDs (agents update their fix-progress-{N}.json)
|
- Assign agent IDs (agents update their fix-progress-{N}.json)
|
||||||
- Handle agent failures gracefully (mark group as failed, continue)
|
- Handle agent failures gracefully (mark group as failed, continue)
|
||||||
- Advance to next stage only when current stage complete
|
- Advance to next stage only when current stage complete
|
||||||
- Dashboard polls and aggregates fix-progress-{N}.json files for display
|
|
||||||
|
|
||||||
**Phase 4: Completion & Aggregation**
|
**Phase 4: Completion & Aggregation**
|
||||||
- Collect final status from all fix-progress-{N}.json files
|
- Collect final status from all fix-progress-{N}.json files
|
||||||
@@ -224,7 +220,7 @@ if (result.passRate < 100%) {
|
|||||||
- Update fix-history.json with new session entry
|
- Update fix-history.json with new session entry
|
||||||
- Remove active-fix-session.json
|
- Remove active-fix-session.json
|
||||||
- TodoWrite completion: Mark all phases done
|
- TodoWrite completion: Mark all phases done
|
||||||
- Output summary to user with dashboard link
|
- Output summary to user
|
||||||
|
|
||||||
**Phase 5: Session Completion (Optional)**
|
**Phase 5: Session Completion (Optional)**
|
||||||
- If all findings fixed successfully (no failures):
|
- If all findings fixed successfully (no failures):
|
||||||
@@ -234,53 +230,12 @@ if (result.passRate < 100%) {
|
|||||||
- Output: "Some findings failed. Review fix-summary.md before completing session."
|
- Output: "Some findings failed. Review fix-summary.md before completing session."
|
||||||
- Do NOT auto-complete session
|
- Do NOT auto-complete session
|
||||||
|
|
||||||
### Dashboard Generation
|
|
||||||
|
|
||||||
**MANDATORY**: Dashboard MUST be generated from template during Phase 1 initialization
|
|
||||||
|
|
||||||
**Template Location**: `~/.claude/templates/fix-dashboard.html`
|
|
||||||
|
|
||||||
**⚠️ POST-GENERATION**: Orchestrator and agents MUST NOT read/write/modify fix-dashboard.html after creation
|
|
||||||
|
|
||||||
**Generation Steps**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Copy template to fix session directory
|
|
||||||
cp ~/.claude/templates/fix-dashboard.html ${sessionDir}/fixes/${fixSessionId}/fix-dashboard.html
|
|
||||||
|
|
||||||
# 2. Replace SESSION_ID placeholder
|
|
||||||
sed -i "s|{{SESSION_ID}}|${sessionId}|g" ${sessionDir}/fixes/${fixSessionId}/fix-dashboard.html
|
|
||||||
|
|
||||||
# 3. Replace REVIEW_DIR placeholder
|
|
||||||
sed -i "s|{{REVIEW_DIR}}|${reviewDir}|g" ${sessionDir}/fixes/${fixSessionId}/fix-dashboard.html
|
|
||||||
|
|
||||||
# 4. Start local server and output dashboard URL
|
|
||||||
cd ${sessionDir}/fixes/${fixSessionId} && python -m http.server 8766 --bind 127.0.0.1 &
|
|
||||||
echo "🔧 Fix Dashboard: http://127.0.0.1:8766/fix-dashboard.html"
|
|
||||||
echo " (Press Ctrl+C to stop server when done)"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Dashboard Features**:
|
|
||||||
- Real-time progress tracking via JSON polling (3-second interval)
|
|
||||||
- Stage timeline visualization with parallel/serial execution modes
|
|
||||||
- Active groups and agents monitoring
|
|
||||||
- Flow control steps tracking for each agent
|
|
||||||
- Fix history drawer with session summaries
|
|
||||||
- Consumes new JSON structure (fix-plan.json with metadata + fix-progress-{N}.json)
|
|
||||||
|
|
||||||
**JSON Consumption**:
|
|
||||||
- `fix-plan.json`: Reads metadata field for session info, timeline stages, groups configuration
|
|
||||||
- `fix-progress-{N}.json`: Polls all progress files to aggregate real-time status
|
|
||||||
- `active-fix-session.json`: Detects active session on load
|
|
||||||
- `fix-history.json`: Loads historical fix sessions
|
|
||||||
|
|
||||||
### Output File Structure
|
### Output File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.workflow/active/WFS-{session-id}/.review/
|
.workflow/active/WFS-{session-id}/.review/
|
||||||
├── fix-export-{timestamp}.json # Exported findings (input)
|
├── fix-export-{timestamp}.json # Exported findings (input)
|
||||||
└── fixes/{fix-session-id}/
|
└── fixes/{fix-session-id}/
|
||||||
├── fix-dashboard.html # Interactive dashboard (generated once, auto-polls JSON)
|
|
||||||
├── fix-plan.json # Planning agent output (execution plan with metadata)
|
├── fix-plan.json # Planning agent output (execution plan with metadata)
|
||||||
├── fix-progress-1.json # Group 1 progress (planning agent init → agent updates)
|
├── fix-progress-1.json # Group 1 progress (planning agent init → agent updates)
|
||||||
├── fix-progress-2.json # Group 2 progress (planning agent init → agent updates)
|
├── fix-progress-2.json # Group 2 progress (planning agent init → agent updates)
|
||||||
@@ -291,10 +246,8 @@ echo " (Press Ctrl+C to stop server when done)"
|
|||||||
```
|
```
|
||||||
|
|
||||||
**File Producers**:
|
**File Producers**:
|
||||||
- **Orchestrator**: `fix-dashboard.html` (generated once from template during Phase 1)
|
|
||||||
- **Planning Agent**: `fix-plan.json` (with metadata), all `fix-progress-*.json` (initial state)
|
- **Planning Agent**: `fix-plan.json` (with metadata), all `fix-progress-*.json` (initial state)
|
||||||
- **Execution Agents**: Update assigned `fix-progress-{N}.json` in real-time
|
- **Execution Agents**: Update assigned `fix-progress-{N}.json` in real-time
|
||||||
- **Dashboard (Browser)**: Reads `fix-plan.json` + all `fix-progress-*.json`, aggregates in-memory every 3 seconds via JavaScript polling
|
|
||||||
|
|
||||||
|
|
||||||
### Agent Invocation Template
|
### Agent Invocation Template
|
||||||
@@ -347,7 +300,7 @@ For each group (G1, G2, G3, ...), generate fix-progress-{N}.json following templ
|
|||||||
- Flow control: Empty implementation_approach array
|
- Flow control: Empty implementation_approach array
|
||||||
- Errors: Empty array
|
- Errors: Empty array
|
||||||
|
|
||||||
**CRITICAL**: Ensure complete template structure for Dashboard consumption - all fields must be present.
|
**CRITICAL**: Ensure complete template structure - all fields must be present.
|
||||||
|
|
||||||
## Analysis Requirements
|
## Analysis Requirements
|
||||||
|
|
||||||
@@ -419,7 +372,7 @@ Task({
|
|||||||
description: `Fix ${group.findings.length} issues: ${group.group_name}`,
|
description: `Fix ${group.findings.length} issues: ${group.group_name}`,
|
||||||
prompt: `
|
prompt: `
|
||||||
## Task Objective
|
## Task Objective
|
||||||
Execute fixes for code review findings in group ${group.group_id}. Update progress file in real-time with flow control tracking for dashboard visibility.
|
Execute fixes for code review findings in group ${group.group_id}. Update progress file in real-time with flow control tracking.
|
||||||
|
|
||||||
## Assignment
|
## Assignment
|
||||||
- Group ID: ${group.group_id}
|
- Group ID: ${group.group_id}
|
||||||
@@ -549,7 +502,6 @@ When all findings processed:
|
|||||||
|
|
||||||
### Progress File Updates
|
### Progress File Updates
|
||||||
- **MUST update after every significant action** (before/after each step)
|
- **MUST update after every significant action** (before/after each step)
|
||||||
- **Dashboard polls every 3 seconds** - ensure writes are atomic
|
|
||||||
- **Always maintain complete structure** - never write partial updates
|
- **Always maintain complete structure** - never write partial updates
|
||||||
- **Use ISO 8601 timestamps** - e.g., "2025-01-25T14:36:00Z"
|
- **Use ISO 8601 timestamps** - e.g., "2025-01-25T14:36:00Z"
|
||||||
|
|
||||||
@@ -638,9 +590,17 @@ TodoWrite({
|
|||||||
1. **Trust AI Planning**: Planning agent's grouping and execution strategy are based on dependency analysis
|
1. **Trust AI Planning**: Planning agent's grouping and execution strategy are based on dependency analysis
|
||||||
2. **Conservative Approach**: Test verification is mandatory - no fixes kept without passing tests
|
2. **Conservative Approach**: Test verification is mandatory - no fixes kept without passing tests
|
||||||
3. **Parallel Efficiency**: Default 3 concurrent agents balances speed and resource usage
|
3. **Parallel Efficiency**: Default 3 concurrent agents balances speed and resource usage
|
||||||
4. **Monitor Dashboard**: Real-time stage timeline and agent status provide execution visibility
|
4. **Resume Support**: Fix sessions can resume from checkpoints after interruption
|
||||||
5. **Resume Support**: Fix sessions can resume from checkpoints after interruption
|
5. **Manual Review**: Always review failed fixes manually - may require architectural changes
|
||||||
6. **Manual Review**: Always review failed fixes manually - may require architectural changes
|
6. **Incremental Fixing**: Start with small batches (5-10 findings) before large-scale fixes
|
||||||
7. **Incremental Fixing**: Start with small batches (5-10 findings) before large-scale fixes
|
|
||||||
|
## Related Commands
|
||||||
|
|
||||||
|
### View Fix Progress
|
||||||
|
Use `ccw view` to open the workflow dashboard in browser:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccw view
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,14 +51,12 @@ Independent multi-dimensional code review orchestrator with **hybrid parallel-it
|
|||||||
2. **Session-Integrated**: Review results tracked within workflow session for unified management
|
2. **Session-Integrated**: Review results tracked within workflow session for unified management
|
||||||
3. **Comprehensive Coverage**: Same 7 specialized dimensions as session review
|
3. **Comprehensive Coverage**: Same 7 specialized dimensions as session review
|
||||||
4. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
4. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
||||||
5. **Real-time Visibility**: JSON-based progress tracking with interactive HTML dashboard
|
5. **Unified Archive**: Review results archived with session for historical reference
|
||||||
6. **Unified Archive**: Review results archived with session for historical reference
|
|
||||||
|
|
||||||
### Orchestrator Boundary (CRITICAL)
|
### Orchestrator Boundary (CRITICAL)
|
||||||
- **ONLY command** for independent multi-dimensional module review
|
- **ONLY command** for independent multi-dimensional module review
|
||||||
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
||||||
- Delegates: Code exploration and analysis to @cli-explore-agent, dimension-specific reviews via Deep Scan mode
|
- Delegates: Code exploration and analysis to @cli-explore-agent, dimension-specific reviews via Deep Scan mode
|
||||||
- **⚠️ DASHBOARD CONSTRAINT**: Dashboard is generated ONCE during Phase 1 initialization. After initialization, orchestrator and agents MUST NOT read, write, or modify dashboard.html - it remains static for user interaction only.
|
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
@@ -66,7 +64,7 @@ Independent multi-dimensional code review orchestrator with **hybrid parallel-it
|
|||||||
|
|
||||||
```
|
```
|
||||||
Phase 1: Discovery & Initialization
|
Phase 1: Discovery & Initialization
|
||||||
└─ Resolve file patterns, validate paths, initialize state, create output structure → Generate dashboard.html
|
└─ Resolve file patterns, validate paths, initialize state, create output structure
|
||||||
|
|
||||||
Phase 2: Parallel Reviews (for each dimension)
|
Phase 2: Parallel Reviews (for each dimension)
|
||||||
├─ Launch 7 review agents simultaneously
|
├─ Launch 7 review agents simultaneously
|
||||||
@@ -90,7 +88,7 @@ Phase 4: Iterative Deep-Dive (optional)
|
|||||||
└─ Loop until no critical findings OR max iterations
|
└─ Loop until no critical findings OR max iterations
|
||||||
|
|
||||||
Phase 5: Completion
|
Phase 5: Completion
|
||||||
└─ Finalize review-progress.json → Output dashboard path
|
└─ Finalize review-progress.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Agent Roles
|
### Agent Roles
|
||||||
@@ -219,37 +217,9 @@ done
|
|||||||
|
|
||||||
**Step 4: Initialize Review State**
|
**Step 4: Initialize Review State**
|
||||||
- State initialization: Create `review-state.json` with metadata, dimensions, max_iterations, resolved_files (merged metadata + state)
|
- State initialization: Create `review-state.json` with metadata, dimensions, max_iterations, resolved_files (merged metadata + state)
|
||||||
- Progress tracking: Create `review-progress.json` for dashboard polling
|
- Progress tracking: Create `review-progress.json` for progress tracking
|
||||||
|
|
||||||
**Step 5: Dashboard Generation**
|
**Step 5: TodoWrite Initialization**
|
||||||
|
|
||||||
**Constraints**:
|
|
||||||
- **MANDATORY**: Dashboard MUST be generated from template: `~/.claude/templates/review-cycle-dashboard.html`
|
|
||||||
- **PROHIBITED**: Direct creation or custom generation without template
|
|
||||||
- **POST-GENERATION**: Orchestrator and agents MUST NOT read/write/modify dashboard.html after creation
|
|
||||||
|
|
||||||
**Generation Commands** (3 independent steps):
|
|
||||||
```bash
|
|
||||||
# Step 1: Copy template to output location
|
|
||||||
cp ~/.claude/templates/review-cycle-dashboard.html ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Step 2: Replace SESSION_ID placeholder
|
|
||||||
sed -i "s|{{SESSION_ID}}|${sessionId}|g" ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Step 3: Replace REVIEW_TYPE placeholder
|
|
||||||
sed -i "s|{{REVIEW_TYPE}}|module|g" ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Step 4: Replace REVIEW_DIR placeholder
|
|
||||||
sed -i "s|{{REVIEW_DIR}}|${reviewDir}|g" ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Output: Start local server and output dashboard URL
|
|
||||||
# Use Python HTTP server (available on most systems)
|
|
||||||
cd ${sessionDir}/.review && python -m http.server 8765 --bind 127.0.0.1 &
|
|
||||||
echo "📊 Dashboard: http://127.0.0.1:8765/dashboard.html"
|
|
||||||
echo " (Press Ctrl+C to stop server when done)"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 6: TodoWrite Initialization**
|
|
||||||
- Set up progress tracking with hierarchical structure
|
- Set up progress tracking with hierarchical structure
|
||||||
- Mark Phase 1 completed, Phase 2 in_progress
|
- Mark Phase 1 completed, Phase 2 in_progress
|
||||||
|
|
||||||
@@ -280,7 +250,6 @@ echo " (Press Ctrl+C to stop server when done)"
|
|||||||
- Finalize review-progress.json with completion statistics
|
- Finalize review-progress.json with completion statistics
|
||||||
- Update review-state.json with completion_time and phase=complete
|
- Update review-state.json with completion_time and phase=complete
|
||||||
- TodoWrite completion: Mark all tasks done
|
- TodoWrite completion: Mark all tasks done
|
||||||
- Output: Dashboard path to user
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -301,12 +270,11 @@ echo " (Press Ctrl+C to stop server when done)"
|
|||||||
├── iterations/ # Deep-dive results
|
├── iterations/ # Deep-dive results
|
||||||
│ ├── iteration-1-finding-{uuid}.json
|
│ ├── iteration-1-finding-{uuid}.json
|
||||||
│ └── iteration-2-finding-{uuid}.json
|
│ └── iteration-2-finding-{uuid}.json
|
||||||
├── reports/ # Human-readable reports
|
└── reports/ # Human-readable reports
|
||||||
│ ├── security-analysis.md
|
├── security-analysis.md
|
||||||
│ ├── security-cli-output.txt
|
├── security-cli-output.txt
|
||||||
│ ├── deep-dive-1-{uuid}.md
|
├── deep-dive-1-{uuid}.md
|
||||||
│ └── ...
|
└── ...
|
||||||
└── dashboard.html # Interactive dashboard (primary output)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Session Context**:
|
**Session Context**:
|
||||||
@@ -772,23 +740,25 @@ TodoWrite({
|
|||||||
3. **Use Glob Wisely**: `src/auth/**` is more efficient than `src/**` with lots of irrelevant files
|
3. **Use Glob Wisely**: `src/auth/**` is more efficient than `src/**` with lots of irrelevant files
|
||||||
4. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
4. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
||||||
5. **Monitor Logs**: Check reports/ directory for CLI analysis insights
|
5. **Monitor Logs**: Check reports/ directory for CLI analysis insights
|
||||||
6. **Dashboard Polling**: Refresh every 5 seconds for real-time updates
|
|
||||||
7. **Export Results**: Use dashboard export for external tracking tools
|
|
||||||
|
|
||||||
## Related Commands
|
## Related Commands
|
||||||
|
|
||||||
|
### View Review Progress
|
||||||
|
Use `ccw view` to open the review dashboard in browser:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccw view
|
||||||
|
```
|
||||||
|
|
||||||
### Automated Fix Workflow
|
### Automated Fix Workflow
|
||||||
After completing a module review, use the dashboard to select findings and export them for automated fixing:
|
After completing a module review, use the generated findings JSON for automated fixing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Step 1: Complete review (this command)
|
# Step 1: Complete review (this command)
|
||||||
/workflow:review-module-cycle src/auth/**
|
/workflow:review-module-cycle src/auth/**
|
||||||
|
|
||||||
# Step 2: Open dashboard, select findings, and export
|
# Step 2: Run automated fixes using dimension findings
|
||||||
# Dashboard generates: fix-export-{timestamp}.json
|
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/
|
||||||
|
|
||||||
# Step 3: Run automated fixes
|
|
||||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/fix-export-{timestamp}.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
||||||
|
|||||||
@@ -45,13 +45,11 @@ Session-based multi-dimensional code review orchestrator with **hybrid parallel-
|
|||||||
1. **Comprehensive Coverage**: 7 specialized dimensions analyze all quality aspects simultaneously
|
1. **Comprehensive Coverage**: 7 specialized dimensions analyze all quality aspects simultaneously
|
||||||
2. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
2. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
||||||
3. **Actionable Insights**: Deep-dive iterations provide step-by-step remediation plans
|
3. **Actionable Insights**: Deep-dive iterations provide step-by-step remediation plans
|
||||||
4. **Real-time Visibility**: JSON-based progress tracking with interactive HTML dashboard
|
|
||||||
|
|
||||||
### Orchestrator Boundary (CRITICAL)
|
### Orchestrator Boundary (CRITICAL)
|
||||||
- **ONLY command** for comprehensive multi-dimensional review
|
- **ONLY command** for comprehensive multi-dimensional review
|
||||||
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
||||||
- Delegates: Code exploration and analysis to @cli-explore-agent, dimension-specific reviews via Deep Scan mode
|
- Delegates: Code exploration and analysis to @cli-explore-agent, dimension-specific reviews via Deep Scan mode
|
||||||
- **⚠️ DASHBOARD CONSTRAINT**: Dashboard is generated ONCE during Phase 1 initialization. After initialization, orchestrator and agents MUST NOT read, write, or modify dashboard.html - it remains static for user interaction only.
|
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
@@ -59,7 +57,7 @@ Session-based multi-dimensional code review orchestrator with **hybrid parallel-
|
|||||||
|
|
||||||
```
|
```
|
||||||
Phase 1: Discovery & Initialization
|
Phase 1: Discovery & Initialization
|
||||||
└─ Validate session, initialize state, create output structure → Generate dashboard.html
|
└─ Validate session, initialize state, create output structure
|
||||||
|
|
||||||
Phase 2: Parallel Reviews (for each dimension)
|
Phase 2: Parallel Reviews (for each dimension)
|
||||||
├─ Launch 7 review agents simultaneously
|
├─ Launch 7 review agents simultaneously
|
||||||
@@ -83,7 +81,7 @@ Phase 4: Iterative Deep-Dive (optional)
|
|||||||
└─ Loop until no critical findings OR max iterations
|
└─ Loop until no critical findings OR max iterations
|
||||||
|
|
||||||
Phase 5: Completion
|
Phase 5: Completion
|
||||||
└─ Finalize review-progress.json → Output dashboard path
|
└─ Finalize review-progress.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Agent Roles
|
### Agent Roles
|
||||||
@@ -199,36 +197,9 @@ git log --since="${sessionCreatedAt}" --name-only --pretty=format: | sort -u
|
|||||||
|
|
||||||
**Step 5: Initialize Review State**
|
**Step 5: Initialize Review State**
|
||||||
- State initialization: Create `review-state.json` with metadata, dimensions, max_iterations (merged metadata + state)
|
- State initialization: Create `review-state.json` with metadata, dimensions, max_iterations (merged metadata + state)
|
||||||
- Progress tracking: Create `review-progress.json` for dashboard polling
|
- Progress tracking: Create `review-progress.json` for progress tracking
|
||||||
|
|
||||||
**Step 6: Dashboard Generation**
|
**Step 6: TodoWrite Initialization**
|
||||||
|
|
||||||
**Constraints**:
|
|
||||||
- **MANDATORY**: Dashboard MUST be generated from template: `~/.claude/templates/review-cycle-dashboard.html`
|
|
||||||
- **PROHIBITED**: Direct creation or custom generation without template
|
|
||||||
- **POST-GENERATION**: Orchestrator and agents MUST NOT read/write/modify dashboard.html after creation
|
|
||||||
|
|
||||||
**Generation Commands** (3 independent steps):
|
|
||||||
```bash
|
|
||||||
# Step 1: Copy template to output location
|
|
||||||
cp ~/.claude/templates/review-cycle-dashboard.html ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Step 2: Replace SESSION_ID placeholder
|
|
||||||
sed -i "s|{{SESSION_ID}}|${sessionId}|g" ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Step 3: Replace REVIEW_TYPE placeholder
|
|
||||||
sed -i "s|{{REVIEW_TYPE}}|session|g" ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Step 4: Replace REVIEW_DIR placeholder
|
|
||||||
sed -i "s|{{REVIEW_DIR}}|${reviewDir}|g" ${sessionDir}/.review/dashboard.html
|
|
||||||
|
|
||||||
# Output: Start local server and output dashboard URL
|
|
||||||
cd ${sessionDir}/.review && python -m http.server 8765 --bind 127.0.0.1 &
|
|
||||||
echo "📊 Dashboard: http://127.0.0.1:8765/dashboard.html"
|
|
||||||
echo " (Press Ctrl+C to stop server when done)"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 7: TodoWrite Initialization**
|
|
||||||
- Set up progress tracking with hierarchical structure
|
- Set up progress tracking with hierarchical structure
|
||||||
- Mark Phase 1 completed, Phase 2 in_progress
|
- Mark Phase 1 completed, Phase 2 in_progress
|
||||||
|
|
||||||
@@ -259,7 +230,6 @@ echo " (Press Ctrl+C to stop server when done)"
|
|||||||
- Finalize review-progress.json with completion statistics
|
- Finalize review-progress.json with completion statistics
|
||||||
- Update review-state.json with completion_time and phase=complete
|
- Update review-state.json with completion_time and phase=complete
|
||||||
- TodoWrite completion: Mark all tasks done
|
- TodoWrite completion: Mark all tasks done
|
||||||
- Output: Dashboard path to user
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -280,12 +250,11 @@ echo " (Press Ctrl+C to stop server when done)"
|
|||||||
├── iterations/ # Deep-dive results
|
├── iterations/ # Deep-dive results
|
||||||
│ ├── iteration-1-finding-{uuid}.json
|
│ ├── iteration-1-finding-{uuid}.json
|
||||||
│ └── iteration-2-finding-{uuid}.json
|
│ └── iteration-2-finding-{uuid}.json
|
||||||
├── reports/ # Human-readable reports
|
└── reports/ # Human-readable reports
|
||||||
│ ├── security-analysis.md
|
├── security-analysis.md
|
||||||
│ ├── security-cli-output.txt
|
├── security-cli-output.txt
|
||||||
│ ├── deep-dive-1-{uuid}.md
|
├── deep-dive-1-{uuid}.md
|
||||||
│ └── ...
|
└── ...
|
||||||
└── dashboard.html # Interactive dashboard (primary output)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Session Context**:
|
**Session Context**:
|
||||||
@@ -782,23 +751,25 @@ TodoWrite({
|
|||||||
2. **Parallel Execution**: ~60 minutes for full initial review (7 dimensions)
|
2. **Parallel Execution**: ~60 minutes for full initial review (7 dimensions)
|
||||||
3. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
3. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
||||||
4. **Monitor Logs**: Check reports/ directory for CLI analysis insights
|
4. **Monitor Logs**: Check reports/ directory for CLI analysis insights
|
||||||
5. **Dashboard Polling**: Refresh every 5 seconds for real-time updates
|
|
||||||
6. **Export Results**: Use dashboard export for external tracking tools
|
|
||||||
|
|
||||||
## Related Commands
|
## Related Commands
|
||||||
|
|
||||||
|
### View Review Progress
|
||||||
|
Use `ccw view` to open the review dashboard in browser:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccw view
|
||||||
|
```
|
||||||
|
|
||||||
### Automated Fix Workflow
|
### Automated Fix Workflow
|
||||||
After completing a review, use the dashboard to select findings and export them for automated fixing:
|
After completing a review, use the generated findings JSON for automated fixing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Step 1: Complete review (this command)
|
# Step 1: Complete review (this command)
|
||||||
/workflow:review-session-cycle
|
/workflow:review-session-cycle
|
||||||
|
|
||||||
# Step 2: Open dashboard, select findings, and export
|
# Step 2: Run automated fixes using dimension findings
|
||||||
# Dashboard generates: fix-export-{timestamp}.json
|
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/
|
||||||
|
|
||||||
# Step 3: Run automated fixes
|
|
||||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/fix-export-{timestamp}.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
||||||
|
|||||||
@@ -1,352 +0,0 @@
|
|||||||
---
|
|
||||||
name: workflow:status
|
|
||||||
description: Generate on-demand views for project overview and workflow tasks with optional task-id filtering for detailed view
|
|
||||||
argument-hint: "[optional: --project|task-id|--validate|--dashboard]"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Workflow Status Command (/workflow:status)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Generates on-demand views from project and session data. Supports multiple modes:
|
|
||||||
1. **Project Overview** (`--project`): Shows completed features and project statistics
|
|
||||||
2. **Workflow Tasks** (default): Shows current session task progress
|
|
||||||
3. **HTML Dashboard** (`--dashboard`): Generates interactive HTML task board with active and archived sessions
|
|
||||||
|
|
||||||
No synchronization needed - all views are calculated from current JSON state.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
```bash
|
|
||||||
/workflow:status # Show current workflow session overview
|
|
||||||
/workflow:status --project # Show project-level feature registry
|
|
||||||
/workflow:status impl-1 # Show specific task details
|
|
||||||
/workflow:status --validate # Validate workflow integrity
|
|
||||||
/workflow:status --dashboard # Generate HTML dashboard board
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution Process
|
|
||||||
|
|
||||||
```
|
|
||||||
Input Parsing:
|
|
||||||
└─ Decision (mode detection):
|
|
||||||
├─ --project flag → Project Overview Mode
|
|
||||||
├─ --dashboard flag → Dashboard Mode
|
|
||||||
├─ task-id argument → Task Details Mode
|
|
||||||
└─ No flags → Workflow Session Mode (default)
|
|
||||||
|
|
||||||
Project Overview Mode:
|
|
||||||
├─ Check project.json exists
|
|
||||||
├─ Read project data
|
|
||||||
├─ Parse and display overview + features
|
|
||||||
└─ Show recent archived sessions
|
|
||||||
|
|
||||||
Workflow Session Mode (default):
|
|
||||||
├─ Find active session
|
|
||||||
├─ Load session data
|
|
||||||
├─ Scan task files
|
|
||||||
└─ Display task progress
|
|
||||||
|
|
||||||
Dashboard Mode:
|
|
||||||
├─ Collect active sessions
|
|
||||||
├─ Collect archived sessions
|
|
||||||
├─ Generate HTML from template
|
|
||||||
└─ Write dashboard.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Flow
|
|
||||||
|
|
||||||
### Mode Selection
|
|
||||||
|
|
||||||
**Check for --project flag**:
|
|
||||||
- If `--project` flag present → Execute **Project Overview Mode**
|
|
||||||
- Otherwise → Execute **Workflow Session Mode** (default)
|
|
||||||
|
|
||||||
## Project Overview Mode
|
|
||||||
|
|
||||||
### Step 1: Check Project State
|
|
||||||
```bash
|
|
||||||
bash(test -f .workflow/project.json && echo "EXISTS" || echo "NOT_FOUND")
|
|
||||||
```
|
|
||||||
|
|
||||||
**If NOT_FOUND**:
|
|
||||||
```
|
|
||||||
No project state found.
|
|
||||||
Run /workflow:session:start to initialize project.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Read Project Data
|
|
||||||
```bash
|
|
||||||
bash(cat .workflow/project.json)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Parse and Display
|
|
||||||
|
|
||||||
**Data Processing**:
|
|
||||||
```javascript
|
|
||||||
const projectData = JSON.parse(Read('.workflow/project.json'));
|
|
||||||
const features = projectData.features || [];
|
|
||||||
const stats = projectData.statistics || {};
|
|
||||||
const overview = projectData.overview || null;
|
|
||||||
|
|
||||||
// Sort features by implementation date (newest first)
|
|
||||||
const sortedFeatures = features.sort((a, b) =>
|
|
||||||
new Date(b.implemented_at) - new Date(a.implemented_at)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output Format** (with extended overview):
|
|
||||||
```
|
|
||||||
## Project: ${projectData.project_name}
|
|
||||||
Initialized: ${projectData.initialized_at}
|
|
||||||
|
|
||||||
${overview ? `
|
|
||||||
### Overview
|
|
||||||
${overview.description}
|
|
||||||
|
|
||||||
**Technology Stack**:
|
|
||||||
${overview.technology_stack.languages.map(l => `- ${l.name}${l.primary ? ' (primary)' : ''}: ${l.file_count} files`).join('\n')}
|
|
||||||
Frameworks: ${overview.technology_stack.frameworks.join(', ')}
|
|
||||||
|
|
||||||
**Architecture**:
|
|
||||||
Style: ${overview.architecture.style}
|
|
||||||
Patterns: ${overview.architecture.patterns.join(', ')}
|
|
||||||
|
|
||||||
**Key Components** (${overview.key_components.length}):
|
|
||||||
${overview.key_components.map(c => `- ${c.name} (${c.path})\n ${c.description}`).join('\n')}
|
|
||||||
|
|
||||||
---
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
### Completed Features (${stats.total_features})
|
|
||||||
|
|
||||||
${sortedFeatures.map(f => `
|
|
||||||
- ${f.title} (${f.timeline?.implemented_at || f.implemented_at})
|
|
||||||
${f.description}
|
|
||||||
Tags: ${f.tags?.join(', ') || 'none'}
|
|
||||||
Session: ${f.traceability?.session_id || f.session_id}
|
|
||||||
Archive: ${f.traceability?.archive_path || 'unknown'}
|
|
||||||
${f.traceability?.commit_hash ? `Commit: ${f.traceability.commit_hash}` : ''}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
### Project Statistics
|
|
||||||
- Total Features: ${stats.total_features}
|
|
||||||
- Total Sessions: ${stats.total_sessions}
|
|
||||||
- Last Updated: ${stats.last_updated}
|
|
||||||
|
|
||||||
### Quick Access
|
|
||||||
- View session details: /workflow:status
|
|
||||||
- Archive query: jq '.archives[] | select(.session_id == "SESSION_ID")' .workflow/archives/manifest.json
|
|
||||||
- Documentation: .workflow/docs/${projectData.project_name}/
|
|
||||||
|
|
||||||
### Query Commands
|
|
||||||
# Find by tag
|
|
||||||
cat .workflow/project.json | jq '.features[] | select(.tags[] == "auth")'
|
|
||||||
|
|
||||||
# View archive
|
|
||||||
cat ${feature.traceability.archive_path}/IMPL_PLAN.md
|
|
||||||
|
|
||||||
# List all tags
|
|
||||||
cat .workflow/project.json | jq -r '.features[].tags[]' | sort -u
|
|
||||||
```
|
|
||||||
|
|
||||||
**Empty State**:
|
|
||||||
```
|
|
||||||
## Project: ${projectData.project_name}
|
|
||||||
Initialized: ${projectData.initialized_at}
|
|
||||||
|
|
||||||
No features completed yet.
|
|
||||||
|
|
||||||
Complete your first workflow session to add features:
|
|
||||||
1. /workflow:plan "feature description"
|
|
||||||
2. /workflow:execute
|
|
||||||
3. /workflow:session:complete
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Show Recent Sessions (Optional)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List 5 most recent archived sessions
|
|
||||||
bash(ls -1t .workflow/archives/WFS-* 2>/dev/null | head -5 | xargs -I {} basename {})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output**:
|
|
||||||
```
|
|
||||||
### Recent Sessions
|
|
||||||
- WFS-auth-system (archived)
|
|
||||||
- WFS-payment-flow (archived)
|
|
||||||
- WFS-user-dashboard (archived)
|
|
||||||
|
|
||||||
Use /workflow:session:complete to archive current session.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Workflow Session Mode (Default)
|
|
||||||
|
|
||||||
### Step 1: Find Active Session
|
|
||||||
```bash
|
|
||||||
find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | head -1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Load Session Data
|
|
||||||
```bash
|
|
||||||
cat .workflow/active/WFS-session/workflow-session.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Scan Task Files
|
|
||||||
```bash
|
|
||||||
find .workflow/active/WFS-session/.task/ -name "*.json" -type f 2>/dev/null
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Generate Task Status
|
|
||||||
```bash
|
|
||||||
cat .workflow/active/WFS-session/.task/impl-1.json | jq -r '.status'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Count Task Progress
|
|
||||||
```bash
|
|
||||||
find .workflow/active/WFS-session/.task/ -name "*.json" -type f | wc -l
|
|
||||||
find .workflow/active/WFS-session/.summaries/ -name "*.md" -type f 2>/dev/null | wc -l
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Display Overview
|
|
||||||
```markdown
|
|
||||||
# Workflow Overview
|
|
||||||
**Session**: WFS-session-name
|
|
||||||
**Progress**: 3/8 tasks completed
|
|
||||||
|
|
||||||
## Active Tasks
|
|
||||||
- [IN PROGRESS] impl-1: Current task in progress
|
|
||||||
- [ ] impl-2: Next pending task
|
|
||||||
|
|
||||||
## Completed Tasks
|
|
||||||
- [COMPLETED] impl-0: Setup completed
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dashboard Mode (HTML Board)
|
|
||||||
|
|
||||||
### Step 1: Check for --dashboard flag
|
|
||||||
```bash
|
|
||||||
# If --dashboard flag present → Execute Dashboard Mode
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Collect Workflow Data
|
|
||||||
|
|
||||||
**Collect Active Sessions**:
|
|
||||||
```bash
|
|
||||||
# Find all active sessions
|
|
||||||
find .workflow/active/ -name "WFS-*" -type d 2>/dev/null
|
|
||||||
|
|
||||||
# For each active session, read metadata and tasks
|
|
||||||
for session in $(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null); do
|
|
||||||
cat "$session/workflow-session.json"
|
|
||||||
find "$session/.task/" -name "*.json" -type f 2>/dev/null
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
**Collect Archived Sessions**:
|
|
||||||
```bash
|
|
||||||
# Find all archived sessions
|
|
||||||
find .workflow/archives/ -name "WFS-*" -type d 2>/dev/null
|
|
||||||
|
|
||||||
# Read manifest if exists
|
|
||||||
cat .workflow/archives/manifest.json 2>/dev/null
|
|
||||||
|
|
||||||
# For each archived session, read metadata
|
|
||||||
for archive in $(find .workflow/archives/ -name "WFS-*" -type d 2>/dev/null); do
|
|
||||||
cat "$archive/workflow-session.json" 2>/dev/null
|
|
||||||
# Count completed tasks
|
|
||||||
find "$archive/.task/" -name "*.json" -type f 2>/dev/null | wc -l
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Process and Structure Data
|
|
||||||
|
|
||||||
**Build data structure for dashboard**:
|
|
||||||
```javascript
|
|
||||||
const dashboardData = {
|
|
||||||
activeSessions: [],
|
|
||||||
archivedSessions: [],
|
|
||||||
generatedAt: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Process active sessions
|
|
||||||
for each active_session in active_sessions:
|
|
||||||
const sessionData = JSON.parse(Read(active_session/workflow-session.json));
|
|
||||||
const tasks = [];
|
|
||||||
|
|
||||||
// Load all tasks for this session
|
|
||||||
for each task_file in find(active_session/.task/*.json):
|
|
||||||
const taskData = JSON.parse(Read(task_file));
|
|
||||||
tasks.push({
|
|
||||||
task_id: taskData.task_id,
|
|
||||||
title: taskData.title,
|
|
||||||
status: taskData.status,
|
|
||||||
type: taskData.type
|
|
||||||
});
|
|
||||||
|
|
||||||
dashboardData.activeSessions.push({
|
|
||||||
session_id: sessionData.session_id,
|
|
||||||
project: sessionData.project,
|
|
||||||
status: sessionData.status,
|
|
||||||
created_at: sessionData.created_at || sessionData.initialized_at,
|
|
||||||
tasks: tasks
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process archived sessions
|
|
||||||
for each archived_session in archived_sessions:
|
|
||||||
const sessionData = JSON.parse(Read(archived_session/workflow-session.json));
|
|
||||||
const taskCount = bash(find archived_session/.task/*.json | wc -l);
|
|
||||||
|
|
||||||
dashboardData.archivedSessions.push({
|
|
||||||
session_id: sessionData.session_id,
|
|
||||||
project: sessionData.project,
|
|
||||||
archived_at: sessionData.completed_at || sessionData.archived_at,
|
|
||||||
taskCount: parseInt(taskCount),
|
|
||||||
archive_path: archived_session
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Generate HTML from Template
|
|
||||||
|
|
||||||
**Load template and inject data**:
|
|
||||||
```javascript
|
|
||||||
// Read the HTML template
|
|
||||||
const template = Read("~/.claude/templates/workflow-dashboard.html");
|
|
||||||
|
|
||||||
// Prepare data for injection
|
|
||||||
const dataJson = JSON.stringify(dashboardData, null, 2);
|
|
||||||
|
|
||||||
// Replace placeholder with actual data
|
|
||||||
const htmlContent = template.replace('{{WORKFLOW_DATA}}', dataJson);
|
|
||||||
|
|
||||||
// Ensure .workflow directory exists
|
|
||||||
bash(mkdir -p .workflow);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Write HTML File
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Write the generated HTML to .workflow/dashboard.html
|
|
||||||
Write({
|
|
||||||
file_path: ".workflow/dashboard.html",
|
|
||||||
content: htmlContent
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Display Success Message
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
Dashboard generated successfully!
|
|
||||||
|
|
||||||
Location: .workflow/dashboard.html
|
|
||||||
|
|
||||||
Open in browser:
|
|
||||||
file://$(pwd)/.workflow/dashboard.html
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- 📊 Active sessions overview
|
|
||||||
- 📦 Archived sessions history
|
|
||||||
- 🔍 Search and filter
|
|
||||||
- 📈 Progress tracking
|
|
||||||
- 🎨 Dark/light theme
|
|
||||||
|
|
||||||
Refresh data: Re-run /workflow:status --dashboard
|
|
||||||
```
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,664 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Workflow Dashboard - Task Board</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-primary: #f5f7fa;
|
|
||||||
--bg-secondary: #ffffff;
|
|
||||||
--bg-card: #ffffff;
|
|
||||||
--text-primary: #1a202c;
|
|
||||||
--text-secondary: #718096;
|
|
||||||
--border-color: #e2e8f0;
|
|
||||||
--accent-color: #4299e1;
|
|
||||||
--success-color: #48bb78;
|
|
||||||
--warning-color: #ed8936;
|
|
||||||
--danger-color: #f56565;
|
|
||||||
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] {
|
|
||||||
--bg-primary: #1a202c;
|
|
||||||
--bg-secondary: #2d3748;
|
|
||||||
--bg-card: #2d3748;
|
|
||||||
--text-primary: #f7fafc;
|
|
||||||
--text-secondary: #a0aec0;
|
|
||||||
--border-color: #4a5568;
|
|
||||||
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
|
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
line-height: 1.6;
|
|
||||||
transition: background-color 0.3s, color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 250px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.2s;
|
|
||||||
background-color: var(--bg-card);
|
|
||||||
color: var(--text-primary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.active {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background-color: var(--bg-card);
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sessions-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card {
|
|
||||||
background-color: var(--bg-card);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
padding: 20px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: start;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-status {
|
|
||||||
padding: 4px 12px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-active {
|
|
||||||
background-color: #c6f6d5;
|
|
||||||
color: #22543d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-archived {
|
|
||||||
background-color: #e2e8f0;
|
|
||||||
color: #4a5568;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .status-active {
|
|
||||||
background-color: #22543d;
|
|
||||||
color: #c6f6d5;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .status-archived {
|
|
||||||
background-color: #4a5568;
|
|
||||||
color: #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, var(--accent-color), var(--success-color));
|
|
||||||
transition: width 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-list {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
border-radius: 6px;
|
|
||||||
border-left: 3px solid var(--border-color);
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item:hover {
|
|
||||||
transform: translateX(4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.completed {
|
|
||||||
border-left-color: var(--success-color);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.in_progress {
|
|
||||||
border-left-color: var(--warning-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.pending {
|
|
||||||
border-left-color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-checkbox {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
margin-right: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.completed .task-checkbox {
|
|
||||||
background-color: var(--success-color);
|
|
||||||
border-color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.completed .task-checkbox::after {
|
|
||||||
content: '✓';
|
|
||||||
color: white;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.in_progress .task-checkbox {
|
|
||||||
border-color: var(--warning-color);
|
|
||||||
background-color: var(--warning-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.in_progress .task-checkbox::after {
|
|
||||||
content: '⟳';
|
|
||||||
color: white;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-title {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-id {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-family: monospace;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 30px;
|
|
||||||
right: 30px;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
transition: all 0.3s;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.sessions-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-controls {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-count {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-footer {
|
|
||||||
margin-top: 15px;
|
|
||||||
padding-top: 15px;
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header>
|
|
||||||
<h1>🚀 Workflow Dashboard</h1>
|
|
||||||
<p style="color: var(--text-secondary);">Task Board - Active and Archived Sessions</p>
|
|
||||||
|
|
||||||
<div class="header-controls">
|
|
||||||
<div class="search-box">
|
|
||||||
<input type="text" id="searchInput" placeholder="🔍 Search tasks or sessions..." />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-group">
|
|
||||||
<button class="btn active" data-filter="all">All</button>
|
|
||||||
<button class="btn" data-filter="active">Active</button>
|
|
||||||
<button class="btn" data-filter="archived">Archived</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="stats-grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="totalSessions">0</div>
|
|
||||||
<div class="stat-label">Total Sessions</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="activeSessions">0</div>
|
|
||||||
<div class="stat-label">Active Sessions</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="totalTasks">0</div>
|
|
||||||
<div class="stat-label">Total Tasks</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="completedTasks">0</div>
|
|
||||||
<div class="stat-label">Completed Tasks</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section" id="activeSectionContainer">
|
|
||||||
<div class="section-header">
|
|
||||||
<h2 class="section-title">📋 Active Sessions</h2>
|
|
||||||
</div>
|
|
||||||
<div class="sessions-grid" id="activeSessions"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section" id="archivedSectionContainer">
|
|
||||||
<div class="section-header">
|
|
||||||
<h2 class="section-title">📦 Archived Sessions</h2>
|
|
||||||
</div>
|
|
||||||
<div class="sessions-grid" id="archivedSessions"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="theme-toggle" id="themeToggle">🌙</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Workflow data will be injected here
|
|
||||||
const workflowData = {{WORKFLOW_DATA}};
|
|
||||||
|
|
||||||
// Theme management
|
|
||||||
function initTheme() {
|
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
||||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
||||||
updateThemeIcon(savedTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
||||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
||||||
document.documentElement.setAttribute('data-theme', newTheme);
|
|
||||||
localStorage.setItem('theme', newTheme);
|
|
||||||
updateThemeIcon(newTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateThemeIcon(theme) {
|
|
||||||
document.getElementById('themeToggle').textContent = theme === 'dark' ? '☀️' : '🌙';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statistics calculation
|
|
||||||
function updateStatistics() {
|
|
||||||
const stats = {
|
|
||||||
totalSessions: workflowData.activeSessions.length + workflowData.archivedSessions.length,
|
|
||||||
activeSessions: workflowData.activeSessions.length,
|
|
||||||
totalTasks: 0,
|
|
||||||
completedTasks: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
workflowData.activeSessions.forEach(session => {
|
|
||||||
stats.totalTasks += session.tasks.length;
|
|
||||||
stats.completedTasks += session.tasks.filter(t => t.status === 'completed').length;
|
|
||||||
});
|
|
||||||
|
|
||||||
workflowData.archivedSessions.forEach(session => {
|
|
||||||
stats.totalTasks += session.taskCount || 0;
|
|
||||||
stats.completedTasks += session.taskCount || 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('totalSessions').textContent = stats.totalSessions;
|
|
||||||
document.getElementById('activeSessions').textContent = stats.activeSessions;
|
|
||||||
document.getElementById('totalTasks').textContent = stats.totalTasks;
|
|
||||||
document.getElementById('completedTasks').textContent = stats.completedTasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render session card
|
|
||||||
function createSessionCard(session, isActive) {
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'session-card';
|
|
||||||
card.dataset.sessionType = isActive ? 'active' : 'archived';
|
|
||||||
|
|
||||||
const completedTasks = isActive
|
|
||||||
? session.tasks.filter(t => t.status === 'completed').length
|
|
||||||
: (session.taskCount || 0);
|
|
||||||
const totalTasks = isActive ? session.tasks.length : (session.taskCount || 0);
|
|
||||||
const progress = totalTasks > 0 ? (completedTasks / totalTasks * 100) : 0;
|
|
||||||
|
|
||||||
let tasksHtml = '';
|
|
||||||
if (isActive && session.tasks.length > 0) {
|
|
||||||
tasksHtml = `
|
|
||||||
<div class="tasks-list">
|
|
||||||
${session.tasks.map(task => `
|
|
||||||
<div class="task-item ${task.status}">
|
|
||||||
<div class="task-checkbox"></div>
|
|
||||||
<div class="task-title">${task.title || 'Untitled Task'}</div>
|
|
||||||
<span class="task-id">${task.task_id || ''}</span>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
card.innerHTML = `
|
|
||||||
<div class="session-header">
|
|
||||||
<div>
|
|
||||||
<h3 class="session-title">${session.session_id || 'Unknown Session'}</h3>
|
|
||||||
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 5px;">
|
|
||||||
${session.project || ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="session-status ${isActive ? 'status-active' : 'status-archived'}">
|
|
||||||
${isActive ? 'Active' : 'Archived'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="session-meta">
|
|
||||||
<span>📅 ${session.created_at || session.archived_at || 'N/A'}</span>
|
|
||||||
<span>📊 ${completedTasks}/${totalTasks} tasks</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${totalTasks > 0 ? `
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" style="width: ${progress}%"></div>
|
|
||||||
</div>
|
|
||||||
<div style="text-align: center; font-size: 0.85rem; color: var(--text-secondary);">
|
|
||||||
${Math.round(progress)}% Complete
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
${tasksHtml}
|
|
||||||
|
|
||||||
${!isActive && session.archive_path ? `
|
|
||||||
<div class="session-footer">
|
|
||||||
📁 Archive: ${session.archive_path}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render all sessions
|
|
||||||
function renderSessions(filter = 'all') {
|
|
||||||
const activeContainer = document.getElementById('activeSessions');
|
|
||||||
const archivedContainer = document.getElementById('archivedSessions');
|
|
||||||
|
|
||||||
activeContainer.innerHTML = '';
|
|
||||||
archivedContainer.innerHTML = '';
|
|
||||||
|
|
||||||
if (filter === 'all' || filter === 'active') {
|
|
||||||
if (workflowData.activeSessions.length === 0) {
|
|
||||||
activeContainer.innerHTML = `
|
|
||||||
<div class="empty-state">
|
|
||||||
<div class="empty-state-icon">📭</div>
|
|
||||||
<p>No active sessions</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
workflowData.activeSessions.forEach(session => {
|
|
||||||
activeContainer.appendChild(createSessionCard(session, true));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter === 'all' || filter === 'archived') {
|
|
||||||
if (workflowData.archivedSessions.length === 0) {
|
|
||||||
archivedContainer.innerHTML = `
|
|
||||||
<div class="empty-state">
|
|
||||||
<div class="empty-state-icon">📦</div>
|
|
||||||
<p>No archived sessions</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
workflowData.archivedSessions.forEach(session => {
|
|
||||||
archivedContainer.appendChild(createSessionCard(session, false));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show/hide sections
|
|
||||||
document.getElementById('activeSectionContainer').style.display =
|
|
||||||
(filter === 'all' || filter === 'active') ? 'block' : 'none';
|
|
||||||
document.getElementById('archivedSectionContainer').style.display =
|
|
||||||
(filter === 'all' || filter === 'archived') ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search functionality
|
|
||||||
function setupSearch() {
|
|
||||||
const searchInput = document.getElementById('searchInput');
|
|
||||||
searchInput.addEventListener('input', (e) => {
|
|
||||||
const query = e.target.value.toLowerCase();
|
|
||||||
const cards = document.querySelectorAll('.session-card');
|
|
||||||
|
|
||||||
cards.forEach(card => {
|
|
||||||
const text = card.textContent.toLowerCase();
|
|
||||||
card.style.display = text.includes(query) ? 'block' : 'none';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter functionality
|
|
||||||
function setupFilters() {
|
|
||||||
const filterButtons = document.querySelectorAll('[data-filter]');
|
|
||||||
filterButtons.forEach(btn => {
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
filterButtons.forEach(b => b.classList.remove('active'));
|
|
||||||
btn.classList.add('active');
|
|
||||||
renderSessions(btn.dataset.filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
initTheme();
|
|
||||||
updateStatistics();
|
|
||||||
renderSessions();
|
|
||||||
setupSearch();
|
|
||||||
setupFilters();
|
|
||||||
|
|
||||||
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, promises as fsPromises } from 'fs';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
@@ -139,6 +139,27 @@ export async function startServer(options = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API: Read a JSON file (for fix progress tracking)
|
||||||
|
if (pathname === '/api/file') {
|
||||||
|
const filePath = url.searchParams.get('path');
|
||||||
|
if (!filePath) {
|
||||||
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: 'File path is required' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fsPromises.readFile(filePath, 'utf-8');
|
||||||
|
const json = JSON.parse(content);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify(json));
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: 'File not found or invalid JSON' }));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// API: Get session detail data (context, summaries, impl-plan, review)
|
// API: Get session detail data (context, summaries, impl-plan, review)
|
||||||
if (pathname === '/api/session-detail') {
|
if (pathname === '/api/session-detail') {
|
||||||
const sessionPath = url.searchParams.get('path');
|
const sessionPath = url.searchParams.get('path');
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ function renderReviewSessionDetailPage(session) {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Fix Progress Section (dynamically populated) -->
|
||||||
|
<div id="fixProgressSection" class="fix-progress-section-container"></div>
|
||||||
|
|
||||||
<!-- Enhanced Findings Section -->
|
<!-- Enhanced Findings Section -->
|
||||||
<div class="review-enhanced-container">
|
<div class="review-enhanced-container">
|
||||||
<!-- Header with Stats & Controls -->
|
<!-- Header with Stats & Controls -->
|
||||||
@@ -697,6 +700,11 @@ function initReviewSessionPage(session) {
|
|||||||
// Reset state when page loads
|
// Reset state when page loads
|
||||||
reviewSessionState.session = session;
|
reviewSessionState.session = session;
|
||||||
// Event handlers are inline onclick - no additional setup needed
|
// Event handlers are inline onclick - no additional setup needed
|
||||||
|
|
||||||
|
// Start fix progress polling if in server mode
|
||||||
|
if (window.SERVER_MODE && session?.session_id) {
|
||||||
|
startFixProgressPolling(session.session_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy filter function for compatibility
|
// Legacy filter function for compatibility
|
||||||
@@ -709,3 +717,314 @@ function filterReviewFindings(severity) {
|
|||||||
}
|
}
|
||||||
applyReviewSessionFilters();
|
applyReviewSessionFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// FIX PROGRESS TRACKING
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
// Fix progress state
|
||||||
|
let fixProgressState = {
|
||||||
|
fixPlan: null,
|
||||||
|
progressData: null,
|
||||||
|
pollInterval: null,
|
||||||
|
currentSlide: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover and load fix-plan.json for the current review session
|
||||||
|
* Searches in: .review/fixes/{fix-session-id}/fix-plan.json
|
||||||
|
*/
|
||||||
|
async function loadFixProgress(sessionId) {
|
||||||
|
if (!window.SERVER_MODE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// First, discover active fix session
|
||||||
|
const activeFixResponse = await fetch(`/api/file?path=${encodeURIComponent(projectPath + '/.review/fixes/active-fix-session.json')}`);
|
||||||
|
if (!activeFixResponse.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const activeFixSession = await activeFixResponse.json();
|
||||||
|
const fixSessionId = activeFixSession.fix_session_id;
|
||||||
|
|
||||||
|
// Load fix-plan.json
|
||||||
|
const planPath = `${projectPath}/.review/fixes/${fixSessionId}/fix-plan.json`;
|
||||||
|
const planResponse = await fetch(`/api/file?path=${encodeURIComponent(planPath)}`);
|
||||||
|
if (!planResponse.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const fixPlan = await planResponse.json();
|
||||||
|
|
||||||
|
// Load progress files for each group
|
||||||
|
const progressPromises = (fixPlan.groups || []).map(async (group) => {
|
||||||
|
const progressPath = `${projectPath}/.review/fixes/${fixSessionId}/${group.progress_file}`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/file?path=${encodeURIComponent(progressPath)}`);
|
||||||
|
return response.ok ? await response.json() : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const progressDataArray = await Promise.all(progressPromises);
|
||||||
|
|
||||||
|
// Aggregate progress data
|
||||||
|
const aggregated = aggregateFixProgress(fixPlan, progressDataArray.filter(d => d !== null));
|
||||||
|
|
||||||
|
fixProgressState.fixPlan = fixPlan;
|
||||||
|
fixProgressState.progressData = aggregated;
|
||||||
|
|
||||||
|
return aggregated;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load fix progress:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregate progress from multiple group progress files
|
||||||
|
*/
|
||||||
|
function aggregateFixProgress(fixPlan, progressDataArray) {
|
||||||
|
let totalFindings = 0;
|
||||||
|
let fixedCount = 0;
|
||||||
|
let failedCount = 0;
|
||||||
|
let inProgressCount = 0;
|
||||||
|
let pendingCount = 0;
|
||||||
|
const activeAgents = [];
|
||||||
|
|
||||||
|
progressDataArray.forEach(progress => {
|
||||||
|
if (progress.findings) {
|
||||||
|
progress.findings.forEach(f => {
|
||||||
|
totalFindings++;
|
||||||
|
if (f.result === 'fixed') fixedCount++;
|
||||||
|
else if (f.result === 'failed') failedCount++;
|
||||||
|
else if (f.status === 'in-progress') inProgressCount++;
|
||||||
|
else pendingCount++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (progress.assigned_agent && progress.status === 'in-progress') {
|
||||||
|
activeAgents.push({
|
||||||
|
agent_id: progress.assigned_agent,
|
||||||
|
group_id: progress.group_id,
|
||||||
|
current_finding: progress.current_finding
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine phase
|
||||||
|
let phase = 'planning';
|
||||||
|
if (fixPlan.metadata?.status === 'executing' || inProgressCount > 0 || fixedCount > 0 || failedCount > 0) {
|
||||||
|
phase = 'execution';
|
||||||
|
}
|
||||||
|
if (totalFindings > 0 && pendingCount === 0 && inProgressCount === 0) {
|
||||||
|
phase = 'completion';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate stage progress
|
||||||
|
const stages = (fixPlan.timeline?.stages || []).map(stage => {
|
||||||
|
const groupStatuses = stage.groups.map(groupId => {
|
||||||
|
const progress = progressDataArray.find(p => p.group_id === groupId);
|
||||||
|
return progress ? progress.status : 'pending';
|
||||||
|
});
|
||||||
|
let status = 'pending';
|
||||||
|
if (groupStatuses.every(s => s === 'completed' || s === 'failed')) status = 'completed';
|
||||||
|
else if (groupStatuses.some(s => s === 'in-progress')) status = 'in-progress';
|
||||||
|
return { stage: stage.stage, status, groups: stage.groups };
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentStage = stages.findIndex(s => s.status === 'in-progress' || s.status === 'pending') + 1 || stages.length;
|
||||||
|
const percentComplete = totalFindings > 0 ? ((fixedCount + failedCount) / totalFindings) * 100 : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
fix_session_id: fixPlan.metadata?.fix_session_id,
|
||||||
|
phase,
|
||||||
|
total_findings: totalFindings,
|
||||||
|
fixed_count: fixedCount,
|
||||||
|
failed_count: failedCount,
|
||||||
|
in_progress_count: inProgressCount,
|
||||||
|
pending_count: pendingCount,
|
||||||
|
percent_complete: percentComplete,
|
||||||
|
current_stage: currentStage,
|
||||||
|
total_stages: stages.length,
|
||||||
|
stages,
|
||||||
|
active_agents: activeAgents
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render fix progress tracking card (carousel style)
|
||||||
|
*/
|
||||||
|
function renderFixProgressCard(progressData) {
|
||||||
|
if (!progressData) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { phase, total_findings, fixed_count, failed_count, in_progress_count, pending_count, percent_complete, current_stage, total_stages, stages, active_agents, fix_session_id } = progressData;
|
||||||
|
|
||||||
|
// Phase badge class
|
||||||
|
const phaseClass = phase === 'planning' ? 'phase-planning' : phase === 'execution' ? 'phase-execution' : 'phase-completion';
|
||||||
|
const phaseIcon = phase === 'planning' ? '📝' : phase === 'execution' ? '⚡' : '✅';
|
||||||
|
|
||||||
|
// Build stage dots
|
||||||
|
const stageDots = stages.map((s, i) => {
|
||||||
|
const dotClass = s.status === 'completed' ? 'completed' : s.status === 'in-progress' ? 'active' : '';
|
||||||
|
return `<span class="fix-stage-dot ${dotClass}" title="Stage ${i + 1}: ${s.status}"></span>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Build carousel slides
|
||||||
|
const slides = [];
|
||||||
|
|
||||||
|
// Slide 1: Overview
|
||||||
|
slides.push(`
|
||||||
|
<div class="fix-carousel-slide">
|
||||||
|
<div class="fix-slide-header">
|
||||||
|
<span class="fix-phase-badge ${phaseClass}">${phaseIcon} ${phase.toUpperCase()}</span>
|
||||||
|
<span class="fix-session-id">${fix_session_id || 'Fix Session'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="fix-progress-bar-mini">
|
||||||
|
<div class="fix-progress-fill" style="width: ${percent_complete}%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="fix-progress-text">${percent_complete.toFixed(0)}% Complete · Stage ${current_stage}/${total_stages}</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Slide 2: Stats
|
||||||
|
slides.push(`
|
||||||
|
<div class="fix-carousel-slide">
|
||||||
|
<div class="fix-stats-row">
|
||||||
|
<div class="fix-stat">
|
||||||
|
<span class="fix-stat-value">${total_findings}</span>
|
||||||
|
<span class="fix-stat-label">Total</span>
|
||||||
|
</div>
|
||||||
|
<div class="fix-stat fixed">
|
||||||
|
<span class="fix-stat-value">${fixed_count}</span>
|
||||||
|
<span class="fix-stat-label">Fixed</span>
|
||||||
|
</div>
|
||||||
|
<div class="fix-stat failed">
|
||||||
|
<span class="fix-stat-value">${failed_count}</span>
|
||||||
|
<span class="fix-stat-label">Failed</span>
|
||||||
|
</div>
|
||||||
|
<div class="fix-stat pending">
|
||||||
|
<span class="fix-stat-value">${pending_count + in_progress_count}</span>
|
||||||
|
<span class="fix-stat-label">Pending</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Slide 3: Active agents (if any)
|
||||||
|
if (active_agents.length > 0) {
|
||||||
|
const agentItems = active_agents.slice(0, 2).map(a => `
|
||||||
|
<div class="fix-agent-item">
|
||||||
|
<span class="fix-agent-icon">🤖</span>
|
||||||
|
<span class="fix-agent-info">${a.current_finding?.finding_title || 'Working...'}</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
slides.push(`
|
||||||
|
<div class="fix-carousel-slide">
|
||||||
|
<div class="fix-agents-header">${active_agents.length} Active Agent${active_agents.length > 1 ? 's' : ''}</div>
|
||||||
|
${agentItems}
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build carousel navigation
|
||||||
|
const navDots = slides.map((_, i) => `
|
||||||
|
<span class="fix-nav-dot ${i === 0 ? 'active' : ''}" onclick="navigateFixCarousel(${i})"></span>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="fix-progress-card" id="fixProgressCard">
|
||||||
|
<div class="fix-card-header">
|
||||||
|
<span class="fix-card-title">🔧 Fix Progress</span>
|
||||||
|
<div class="fix-stage-dots">${stageDots}</div>
|
||||||
|
</div>
|
||||||
|
<div class="fix-carousel-container">
|
||||||
|
<div class="fix-carousel-track" id="fixCarouselTrack">
|
||||||
|
${slides.join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fix-carousel-nav">
|
||||||
|
<button class="fix-nav-btn prev" onclick="navigateFixCarousel('prev')">‹</button>
|
||||||
|
<div class="fix-nav-dots">${navDots}</div>
|
||||||
|
<button class="fix-nav-btn next" onclick="navigateFixCarousel('next')">›</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate fix progress carousel
|
||||||
|
*/
|
||||||
|
function navigateFixCarousel(direction) {
|
||||||
|
const track = document.getElementById('fixCarouselTrack');
|
||||||
|
if (!track) return;
|
||||||
|
|
||||||
|
const slides = track.querySelectorAll('.fix-carousel-slide');
|
||||||
|
const totalSlides = slides.length;
|
||||||
|
|
||||||
|
if (typeof direction === 'number') {
|
||||||
|
fixProgressState.currentSlide = direction;
|
||||||
|
} else if (direction === 'next') {
|
||||||
|
fixProgressState.currentSlide = (fixProgressState.currentSlide + 1) % totalSlides;
|
||||||
|
} else if (direction === 'prev') {
|
||||||
|
fixProgressState.currentSlide = (fixProgressState.currentSlide - 1 + totalSlides) % totalSlides;
|
||||||
|
}
|
||||||
|
|
||||||
|
track.style.transform = `translateX(-${fixProgressState.currentSlide * 100}%)`;
|
||||||
|
|
||||||
|
// Update nav dots
|
||||||
|
document.querySelectorAll('.fix-nav-dot').forEach((dot, i) => {
|
||||||
|
dot.classList.toggle('active', i === fixProgressState.currentSlide);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start polling for fix progress updates
|
||||||
|
*/
|
||||||
|
function startFixProgressPolling(sessionId) {
|
||||||
|
if (fixProgressState.pollInterval) {
|
||||||
|
clearInterval(fixProgressState.pollInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial load
|
||||||
|
loadFixProgress(sessionId).then(data => {
|
||||||
|
if (data) {
|
||||||
|
updateFixProgressUI(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Poll every 5 seconds
|
||||||
|
fixProgressState.pollInterval = setInterval(async () => {
|
||||||
|
const data = await loadFixProgress(sessionId);
|
||||||
|
if (data) {
|
||||||
|
updateFixProgressUI(data);
|
||||||
|
// Stop polling if completed
|
||||||
|
if (data.phase === 'completion') {
|
||||||
|
clearInterval(fixProgressState.pollInterval);
|
||||||
|
fixProgressState.pollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update fix progress UI
|
||||||
|
*/
|
||||||
|
function updateFixProgressUI(progressData) {
|
||||||
|
const container = document.getElementById('fixProgressSection');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = renderFixProgressCard(progressData);
|
||||||
|
fixProgressState.currentSlide = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop fix progress polling
|
||||||
|
*/
|
||||||
|
function stopFixProgressPolling() {
|
||||||
|
if (fixProgressState.pollInterval) {
|
||||||
|
clearInterval(fixProgressState.pollInterval);
|
||||||
|
fixProgressState.pollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7658,3 +7658,274 @@ code.ctx-meta-chip-value {
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
color: hsl(var(--muted-foreground));
|
color: hsl(var(--muted-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================================
|
||||||
|
Fix Progress Tracking Card (Carousel)
|
||||||
|
=================================== */
|
||||||
|
|
||||||
|
.fix-progress-section-container {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-progress-card {
|
||||||
|
background: hsl(var(--card));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stage-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.375rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stage-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stage-dot.active {
|
||||||
|
background: hsl(var(--primary));
|
||||||
|
animation: pulse-dot 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stage-dot.completed {
|
||||||
|
background: hsl(var(--success));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-dot {
|
||||||
|
0%, 100% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: 0.6; transform: scale(1.2); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Carousel Container */
|
||||||
|
.fix-carousel-container {
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-carousel-track {
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-carousel-slide {
|
||||||
|
min-width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Header */
|
||||||
|
.fix-slide-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-phase-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-phase-badge.phase-planning {
|
||||||
|
background: hsl(270 60% 90%);
|
||||||
|
color: hsl(270 60% 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-phase-badge.phase-execution {
|
||||||
|
background: hsl(220 80% 90%);
|
||||||
|
color: hsl(220 80% 40%);
|
||||||
|
animation: pulse-badge 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-phase-badge.phase-completion {
|
||||||
|
background: hsl(var(--success-light));
|
||||||
|
color: hsl(var(--success));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-badge {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-session-id {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar Mini */
|
||||||
|
.fix-progress-bar-mini {
|
||||||
|
height: 6px;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--success)));
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-progress-text {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Row (Slide 2) */
|
||||||
|
.fix-stats-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stat {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stat-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stat.fixed .fix-stat-value {
|
||||||
|
color: hsl(var(--success));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stat.failed .fix-stat-value {
|
||||||
|
color: hsl(var(--destructive));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-stat.pending .fix-stat-value {
|
||||||
|
color: hsl(var(--warning));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active Agents (Slide 3) */
|
||||||
|
.fix-agents-header {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-agent-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.375rem 0.5rem;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
margin-bottom: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-agent-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-agent-icon {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
animation: spin-agent 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-agent {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-agent-info {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Carousel Navigation */
|
||||||
|
.fix-carousel-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-nav-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-nav-btn:hover {
|
||||||
|
background: hsl(var(--hover));
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-nav-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-nav-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-nav-dot:hover {
|
||||||
|
background: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-nav-dot.active {
|
||||||
|
background: hsl(var(--primary));
|
||||||
|
width: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|||||||
1947
package-lock.json
generated
Normal file
1947
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user